Merge branch 'main' into feat/proxy-full-url

This commit is contained in:
YoVinchen
2026-03-27 16:20:18 +08:00
34 changed files with 1136 additions and 233 deletions
+253 -23
View File
@@ -150,9 +150,76 @@ jobs:
fi
echo "✅ Tauri signing key prepared"
- name: Import Apple signing certificate
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
# Decode .p12 certificate from base64
CERT_PATH="$RUNNER_TEMP/certificate.p12"
printf '%s' "${{ secrets.APPLE_CERTIFICATE }}" | (base64 --decode 2>/dev/null || base64 -D) > "$CERT_PATH"
# Save original default keychain for cleanup
ORIGINAL_DEFAULT_KEYCHAIN=$(security default-keychain -d user | tr -d '"' | xargs)
echo "ORIGINAL_DEFAULT_KEYCHAIN=$ORIGINAL_DEFAULT_KEYCHAIN" >> "$GITHUB_ENV"
# Create temporary keychain
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db"
security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH"
# Import certificate
security import "$CERT_PATH" \
-k "$KEYCHAIN_PATH" \
-P "${{ secrets.APPLE_CERTIFICATE_PASSWORD }}" \
-T /usr/bin/codesign \
-T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${{ secrets.KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH"
# Dynamically resolve signing identity (must be "Developer ID Application")
IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" \
| grep "Developer ID Application" | grep -oE '"[^"]+"' | head -1 | tr -d '"')
if [ -z "$IDENTITY" ]; then
echo "❌ No 'Developer ID Application' identity found — listing all identities:" >&2
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
exit 1
fi
echo "✅ Signing identity: $IDENTITY"
echo "APPLE_SIGNING_IDENTITY=$IDENTITY" >> "$GITHUB_ENV"
# Cleanup certificate file
rm -f "$CERT_PATH"
- name: Build Tauri App (macOS)
if: runner.os == 'macOS'
run: pnpm tauri build --target universal-apple-darwin
shell: bash
timeout-minutes: 60
env:
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
set -euo pipefail
max_attempts=3
for attempt in $(seq 1 "$max_attempts"); do
echo "=== macOS build/notarization attempt ${attempt}/${max_attempts} ==="
if pnpm tauri build --target universal-apple-darwin; then
echo "✅ macOS build/notarization succeeded"
exit 0
fi
if [ "$attempt" -eq "$max_attempts" ]; then
echo "❌ macOS build/notarization failed after ${max_attempts} attempts" >&2
exit 1
fi
sleep_seconds=$((attempt * 60))
echo "⚠️ macOS build/notarization failed, retrying in ${sleep_seconds}s..."
sleep "$sleep_seconds"
done
- name: Build Tauri App (Windows)
if: runner.os == 'Windows'
@@ -169,7 +236,8 @@ jobs:
set -euxo pipefail
mkdir -p release-assets
VERSION="${GITHUB_REF_NAME}" # e.g., v3.5.0
echo "Looking for updater artifact (.tar.gz) and .app for zip..."
# Locate bundle artifacts
TAR_GZ=""; APP_PATH=""
for path in \
"src-tauri/target/universal-apple-darwin/release/bundle/macos" \
@@ -177,28 +245,150 @@ jobs:
"src-tauri/target/x86_64-apple-darwin/release/bundle/macos" \
"src-tauri/target/release/bundle/macos"; do
if [ -d "$path" ]; then
[ -z "$TAR_GZ" ] && TAR_GZ=$(find "$path" -maxdepth 1 -name "*.tar.gz" -type f | head -1 || true)
[ -z "$TAR_GZ" ] && TAR_GZ=$(find "$path" -maxdepth 1 -name "*.tar.gz" -type f | head -1 || true)
[ -z "$APP_PATH" ] && APP_PATH=$(find "$path" -maxdepth 1 -name "*.app" -type d | head -1 || true)
fi
done
if [ -z "$TAR_GZ" ]; then
echo "No macOS .tar.gz updater artifact found" >&2
echo "No macOS .tar.gz updater artifact found" >&2
exit 1
fi
# 重命名 tar.gz 为统一格式
if [ -z "$APP_PATH" ]; then
echo "❌ No .app found" >&2
exit 1
fi
# Staple notarization ticket to .app (Tauri already notarized it)
xcrun stapler staple "$APP_PATH"
echo "✅ .app stapled"
# 1) Collect .tar.gz (updater artifact)
NEW_TAR_GZ="CC-Switch-${VERSION}-macOS.tar.gz"
cp "$TAR_GZ" "release-assets/$NEW_TAR_GZ"
[ -f "$TAR_GZ.sig" ] && cp "$TAR_GZ.sig" "release-assets/$NEW_TAR_GZ.sig" || echo ".sig for macOS not found yet"
echo "macOS updater artifact copied: $NEW_TAR_GZ"
if [ -n "$APP_PATH" ]; then
APP_DIR=$(dirname "$APP_PATH"); APP_NAME=$(basename "$APP_PATH")
NEW_ZIP="CC-Switch-${VERSION}-macOS.zip"
cd "$APP_DIR"
ditto -c -k --sequesterRsrc --keepParent "$APP_NAME" "$NEW_ZIP"
mv "$NEW_ZIP" "$GITHUB_WORKSPACE/release-assets/"
echo "macOS zip ready: $NEW_ZIP"
# 2) Collect .app as zip
NEW_ZIP="CC-Switch-${VERSION}-macOS.zip"
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "release-assets/$NEW_ZIP"
echo "macOS zip ready: $NEW_ZIP"
# 3) Create styled DMG with create-dmg (Tauri's built-in DMG styling doesn't work on CI)
if [ -z "${APPLE_SIGNING_IDENTITY:-}" ]; then
echo "❌ APPLE_SIGNING_IDENTITY is missing before DMG creation" >&2
exit 1
fi
HOMEBREW_NO_AUTO_UPDATE=1 brew install create-dmg
NEW_DMG="CC-Switch-${VERSION}-macOS.dmg"
DMG_STAGE_DIR="$RUNNER_TEMP/dmg-stage"
rm -rf "$DMG_STAGE_DIR"
mkdir -p "$DMG_STAGE_DIR"
ditto "$APP_PATH" "$DMG_STAGE_DIR/CC Switch.app"
create-dmg \
--volname "CC Switch" \
--background "src-tauri/icons/dmg-background.png" \
--window-size 660 400 \
--window-pos 200 120 \
--icon-size 80 \
--icon "CC Switch.app" 180 220 \
--hide-extension "CC Switch.app" \
--app-drop-link 480 220 \
--codesign "$APPLE_SIGNING_IDENTITY" \
--no-internet-enable \
"release-assets/$NEW_DMG" \
"$DMG_STAGE_DIR"
rm -rf "$DMG_STAGE_DIR"
echo "✅ Styled DMG created: $NEW_DMG"
- name: Notarize macOS DMG
if: runner.os == 'macOS'
shell: bash
timeout-minutes: 30
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
set -euo pipefail
DMG_PATH=$(find release-assets -maxdepth 1 -name "*.dmg" -type f | head -1 || true)
if [ -z "$DMG_PATH" ]; then
echo "❌ No .dmg found in release-assets/ to notarize" >&2
exit 1
fi
echo "=== Notarizing DMG: $DMG_PATH ==="
max_attempts=3
for attempt in $(seq 1 "$max_attempts"); do
echo "=== DMG notarization attempt ${attempt}/${max_attempts} ==="
if xcrun notarytool submit "$DMG_PATH" \
--apple-id "$APPLE_ID" \
--password "$APPLE_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait; then
echo "✅ DMG notarization succeeded"
xcrun stapler staple "$DMG_PATH"
echo "✅ DMG stapled"
break
fi
if [ "$attempt" -eq "$max_attempts" ]; then
echo "❌ DMG notarization failed after ${max_attempts} attempts" >&2
exit 1
fi
sleep_seconds=$((attempt * 60))
echo "⚠️ DMG notarization failed, retrying in ${sleep_seconds}s..."
sleep "$sleep_seconds"
done
- name: Verify macOS code signing and notarization
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
# Verify .app (from Tauri bundle)
APP_PATH=""
for path in \
"src-tauri/target/universal-apple-darwin/release/bundle/macos" \
"src-tauri/target/aarch64-apple-darwin/release/bundle/macos" \
"src-tauri/target/x86_64-apple-darwin/release/bundle/macos" \
"src-tauri/target/release/bundle/macos"; do
if [ -d "$path" ]; then
[ -z "$APP_PATH" ] && APP_PATH=$(find "$path" -maxdepth 1 -name "*.app" -type d | head -1 || true)
fi
done
if [ -z "$APP_PATH" ]; then
echo "❌ No .app found for verification" >&2
exit 1
fi
echo "=== Verifying .app: $APP_PATH ==="
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
echo "✅ codesign verification passed"
spctl -a -t exec -vv "$APP_PATH"
echo "✅ spctl assessment passed"
xcrun stapler validate "$APP_PATH"
echo "✅ .app stapler validation passed"
# Verify .dmg (from release-assets/, created by create-dmg + notarized)
DMG_PATH=$(find release-assets -maxdepth 1 -name "*.dmg" -type f | head -1 || true)
if [ -n "$DMG_PATH" ]; then
echo "=== Verifying .dmg: $DMG_PATH ==="
codesign --verify --verbose=2 "$DMG_PATH"
echo "✅ .dmg codesign verification passed"
spctl -a -t open --context context:primary-signature -vv "$DMG_PATH"
echo "✅ .dmg spctl assessment passed"
xcrun stapler validate "$DMG_PATH"
echo "✅ .dmg stapler validation passed"
else
echo "No .app found to zip (optional)" >&2
echo "No .dmg found for verification — release would ship without verified DMG" >&2
exit 1
fi
- name: Prepare Windows Assets
@@ -299,6 +489,51 @@ jobs:
echo "Collected signatures (if any alongside artifacts):"
ls -la release-assets/*.sig || echo "No signatures found"
- name: Upload release artifacts to workflow
uses: actions/upload-artifact@v4
with:
name: release-assets-${{ runner.os }}-${{ matrix.arch || runner.arch }}
path: release-assets/*
if-no-files-found: error
- name: List generated bundles (debug)
if: always()
shell: bash
run: |
echo "Listing bundles in src-tauri/target..."
find src-tauri/target -maxdepth 4 -type f -name "*.*" 2>/dev/null || true
- name: Clean up Apple signing keychain
if: runner.os == 'macOS' && always()
shell: bash
run: |
if [ -n "${ORIGINAL_DEFAULT_KEYCHAIN:-}" ]; then
security default-keychain -s "$ORIGINAL_DEFAULT_KEYCHAIN" || true
fi
if [ -f "$RUNNER_TEMP/build.keychain-db" ]; then
security delete-keychain "$RUNNER_TEMP/build.keychain-db" || true
fi
publish-release:
name: Publish GitHub Release
runs-on: ubuntu-22.04
needs: release
permissions:
contents: write
steps:
- name: Download built release artifacts
uses: actions/download-artifact@v4
with:
pattern: release-assets-*
path: release-assets
merge-multiple: true
- name: List downloaded release artifacts
shell: bash
run: |
set -euo pipefail
ls -la release-assets
- name: Upload Release Assets
uses: softprops/action-gh-release@v2
with:
@@ -312,28 +547,23 @@ jobs:
### 下载
- **macOS**: `CC-Switch-${{ github.ref_name }}-macOS.zip`(解压即用)或 `CC-Switch-${{ github.ref_name }}-macOS.tar.gz`Homebrew
- **macOS**: `CC-Switch-${{ github.ref_name }}-macOS.dmg`(推荐)或 `CC-Switch-${{ github.ref_name }}-macOS.zip`(解压即用
- **Windows**: `CC-Switch-${{ github.ref_name }}-Windows.msi`(安装版)或 `CC-Switch-${{ github.ref_name }}-Windows-Portable.zip`(绿色版)
- **Linux (x86_64)**: `CC-Switch-${{ github.ref_name }}-Linux-x86_64.AppImage` / `.deb` / `.rpm`
- **Linux (ARM64)**: `CC-Switch-${{ github.ref_name }}-Linux-arm64.AppImage` / `.deb` / `.rpm`
> `.tar.gz` 为 Tauri updater 自动更新专用,无需手动下载。
---
提示:macOS 如遇"已损坏"提示,可在终端执行:`xattr -cr "/Applications/CC Switch.app"`
macOS 版本已通过 Apple 代码签名和公证,可直接安装使用。
files: release-assets/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: List generated bundles (debug)
if: always()
shell: bash
run: |
echo "Listing bundles in src-tauri/target..."
find src-tauri/target -maxdepth 4 -type f -name "*.*" 2>/dev/null || true
assemble-latest-json:
name: Assemble latest.json
runs-on: ubuntu-22.04
needs: release
needs: publish-release
permissions:
contents: write
steps:
+31 -5
View File
@@ -5,30 +5,51 @@ 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.12.3] - 2026-03-15
## [Unreleased]
Post-v3.12.2 work adds a Tool Search domain restriction bypass, skill backup/restore lifecycle, proxy compatibility for OpenAI o-series models and gzip compression, and robustness fixes for Skills import, provider forms, and terminal session restore.
---
**Stats**: 17 commits | 61 files changed | +3,335 insertions | -194 deletions
## [3.12.3] - 2026-03-24
Major release adding GitHub Copilot reverse proxy support, macOS code signing & Apple notarization, intelligent reasoning effort mapping for o-series models, skill backup/restore lifecycle, proxy gzip compression, and critical fixes for WebDAV password safety, tool message parsing, and dark mode.
**Stats**: 36 commits | 107 files changed | +9,124 insertions | -802 deletions
### Added
- **Tool Search Domain Bypass**: Added setting to bypass Claude CLI Tool Search domain whitelist via equal-length binary patching; backups stored in `~/.cc-switch/toolsearch-backups/` with auto-reapply on startup when enabled
- **GitHub Copilot Reverse Proxy**: Full GitHub Copilot integration as a Claude Code provider via OAuth Device Code flow; includes multi-account management, automatic token refresh, Anthropic ↔ OpenAI format conversion, real-time model list fetching, and usage statistics (#930)
- **Copilot Auth Center**: New Auth Center panel in Settings for managing GitHub accounts globally, with per-provider account binding via `meta.authBinding`
- **Tool Search Toggle**: Added `ENABLE_TOOL_SEARCH` env var support for Claude 2.1.76+; exposed as a checkbox in the provider Common Config editor (#930)
- **Reasoning Effort Mapping**: Two-tier `resolve_reasoning_effort()` for OpenAI o-series and GPT-5+ models — explicit `output_config.effort` takes priority, falling back to thinking `budget_tokens` thresholds (<4 000→low, 4 00016 000→medium, ≥16 000→high); covers both Chat Completions and Responses API paths with 17 unit tests
- **OpenCode SQLite Backend**: Added SQLite session storage support for OpenCode alongside existing JSON backend; dual-backend scan with SQLite priority on ID conflicts, atomic session deletion, and path validation (#1401)
- **Skill Auto-Backup**: Skill files are automatically backed up to `~/.cc-switch/skill-backups/` before uninstall, with metadata preserved in `meta.json`; old backups pruned to keep at most 20
- **Skill Backup Restore & Delete**: Added list/restore/delete commands for skill backups; restore copies files back to SSOT, saves the DB record, and syncs to the current app with rollback on failure
- **macOS Code Signing & Notarization**: CI now imports an Apple Developer ID certificate, signs the universal binary, submits for Apple notarization, and staples the ticket to both `.app` and `.dmg`; a hard-fail verification step (`codesign --verify` + `spctl -a` + `stapler validate`) gates the release for both artifacts
- **Codex 1M Context Window Toggle**: One-click checkbox in Codex config editor to set `model_context_window = 1000000` with auto-populated `model_auto_compact_token_limit = 900000`; unchecking removes both fields
- **Disable Auto-Upgrade Toggle**: Added `DISABLE_AUTOUPDATER` env var checkbox in the Claude Common Config editor to prevent Claude Code from auto-upgrading
### Changed
- **Skills Cache Strategy**: Replaced `invalidateQueries` with direct `setQueryData` updates for skill install/uninstall/import operations; added `staleTime: Infinity` with `keepPreviousData` to eliminate loading flicker (#1573)
- **Proxy Gzip Compression**: Non-streaming proxy requests now auto-negotiate gzip compression instead of forcing `identity`; streaming requests conservatively keep `identity` to avoid SSE decompression errors
- **o1/o3 Model Compatibility**: Chat Completions proxy forwarding now correctly uses `max_completion_tokens` instead of `max_tokens` for OpenAI o-series models such as o1/o3/o4-mini (#1451)
- **OpenCode Model Variants**: Placed OpenCode model variants at top level instead of inside options for better discoverability (#1317)
- **Skills Import Flow**: Replaced implicit filesystem-based app inference with explicit `ImportSkillSelection` to prevent incorrect multi-app activation; added reconciliation to remove disabled/orphaned symlinks and MCP servers from live config
- **Claude 4.6 Context Window**: Updated Claude Opus 4.6 and Sonnet 4.6 context window from 200K to 1M across OpenClaw and OpenCode presets (GA release)
- **MiniMax Model Upgrade**: Updated MiniMax presets from M2.5 to M2.7 across Claude, OpenClaw, and OpenCode configurations with updated partner descriptions in all three locales
- **Xiaomi MiMo Model Upgrade**: Updated MiMo presets from mimo-v2-flash to mimo-v2-pro across all supported applications
- **AddProviderDialog Simplification**: Removed redundant OAuth tab, reducing dialog from 3 tabs to 2 (app-specific + universal)
- **Provider Form Advanced Options Collapse**: Model mapping, API format, and other advanced fields in the Claude provider form now auto-collapse when empty; auto-expands when any value is set or when a preset fills them in
### Fixed
- **WebDAV Password Silent Clear**: Fixed WebDAV password being silently wiped when ProviderList or UsageScriptModal saved settings by stripping `webdavSync` from frontend payloads and adding backend backfill logic in `merge_settings_for_save()` to preserve existing passwords
- **Tool Message Parsing**: Fixed tool_use/tool_result message classification across Claude (tool_result content blocks), Codex (function_call/function_call_output payloads), and Gemini (array content + toolCalls extraction) session providers (#1401)
- **Dark Mode Selector**: Changed Tailwind `darkMode` from `["selector", "class"]` to `["selector", ".dark"]` to ensure correct dark mode activation (#1596)
- **Copilot Request Fingerprint**: Unified Copilot request fingerprint headers across all API call sites to prevent User-Agent leakage and stream check mismatches
- **o-series Responses API Tokens**: Kept Responses API on the correct `max_output_tokens` field for o-series models instead of incorrectly injecting `max_completion_tokens`
- **Provider Form Double Submit**: Prevented duplicate submissions on rapid button clicks in provider add/edit forms (#1352)
- **Ghostty Session Restore**: Fixed Claude session restore in Ghostty terminal (#1506, thanks @canyonsehun)
- **Ghostty Session Restore**: Fixed Claude session restore in Ghostty terminal (#1506)
- **Skill ZIP Import Extension**: Added `.skill` file extension support in ZIP import dialog (#1240, #1455)
- **Skill ZIP Install Target App**: ZIP skill installs now use the currently active app instead of always defaulting to Claude
- **OpenClaw Active Card Highlight**: Fixed active OpenClaw provider card not being highlighted (#1419)
@@ -36,6 +57,11 @@ Post-v3.12.2 work adds a Tool Search domain restriction bypass, skill backup/res
- **Import Skills Dialog White Screen**: Added missing TooltipProvider in ImportSkillsDialog to prevent runtime crash when opening the dialog
- **Panel Bottom Blank Area**: Replaced hardcoded `h-[calc(100vh-8rem)]` with `flex-1 min-h-0` across all content panels to eliminate bottom gap caused by mismatched offset values
### Docs
- **Pricing Model ID Normalization**: Added documentation section explaining model ID normalization rules (prefix stripping, suffix trimming, `@``-` replacement) in EN/ZH/JA user manuals (#1591)
- **macOS Signed & Notarized**: Removed all `xattr` workaround instructions and "unidentified developer" warnings from README, README_ZH, installation guides (EN/ZH/JA), and FAQ pages (EN/ZH/JA); replaced with "signed and notarized by Apple" messaging
---
## [3.12.2] - 2026-03-12
+4 -4
View File
@@ -184,9 +184,9 @@ CC Switch provides a "Shared Config Snippet" feature to pass common data (beyond
</details>
<details>
<summary><strong>macOS shows "unidentified developer" warning — how do I fix it?</strong></summary>
<summary><strong>macOS installation</strong></summary>
The author doesn't have an Apple Developer account yet (registration in progress). Close the warning, then go to **System Settings → Privacy & Security → Open Anyway**. After that, the app will open normally.
CC Switch for macOS is code-signed and notarized by Apple. You can download and install it directly — no extra steps needed. We recommend using the `.dmg` installer.
</details>
@@ -268,9 +268,9 @@ brew upgrade --cask cc-switch
**Method 2: Manual Download**
Download `CC-Switch-v{version}-macOS.zip` from the [Releases](../../releases) page and extract to use.
Download `CC-Switch-v{version}-macOS.dmg` (recommended) or `.zip` from the [Releases](../../releases) page.
> **Note**: Since the author doesn't have an Apple Developer account, you may see an "unidentified developer" warning on first launch. Please close it first, then go to "System Settings" → "Privacy & Security" → click "Open Anyway", and you'll be able to open it normally afterwards.
> **Note**: CC Switch for macOS is code-signed and notarized by Apple. You can install and open it directly.
### Arch Linux Users
+4 -4
View File
@@ -185,9 +185,9 @@ CC Switch 使用“通用配置片段”功能,在不同的供应商之间传
</details>
<details>
<summary><strong>macOS 提示"未知开发者"警告 — 如何解决?</strong></summary>
<summary><strong>macOS 安装</strong></summary>
这是由于作者没有苹果开发者账号(正在注册中)。关闭警告后,前往**系统设置 → 隐私与安全性 → 仍要打开**。之后应用即可正常打开
CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接下载安装,无需额外操作。推荐使用 `.dmg` 安装包
</details>
@@ -271,9 +271,9 @@ brew upgrade --cask cc-switch
**方式二:手动下载**
从 [Releases](../../releases) 页面下载 `CC-Switch-v{版本号}-macOS.zip` 解压使用
从 [Releases](../../releases) 页面下载 `CC-Switch-v{版本号}-macOS.dmg`(推荐)或 `.zip`
> **注意**由于作者没有苹果开发者账号,首次打开可能出现"未知开发者"警告,请先关闭,然后前往"系统设置" → "隐私与安全性" → 点击"仍要打开",之后便可以正常打开。
> **注意**CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接安装打开。
### Arch Linux 用户
+157 -21
View File
@@ -1,6 +1,6 @@
# CC Switch v3.12.3
> Tool Search Domain Bypass, Skill Backup/Restore Lifecycle, Proxy Gzip & o-Series Compatibility
> GitHub Copilot Reverse Proxy, macOS Code Signing & Notarization, Reasoning Effort Mapping, OpenCode SQLite Backend
**[中文版 →](v3.12.3-zh.md) | [日本語版 →](v3.12.3-ja.md)**
@@ -8,19 +8,25 @@
## Overview
CC Switch v3.12.3 adds a Tool Search domain restriction bypass via binary patching, introduces a full skill backup/restore lifecycle, improves proxy compatibility for OpenAI o-series models and gzip compression, and delivers robustness fixes for Skills import, provider forms, and terminal session restore. Skills are now automatically backed up before uninstall with restore and delete management, and the import flow has been reworked from implicit filesystem inference to explicit app selection.
CC Switch v3.12.3 is a major feature release that adds GitHub Copilot reverse proxy support with a dedicated Auth Center, introduces macOS code signing and Apple notarization for a seamless install experience, maps reasoning effort levels across providers, migrates OpenCode to a SQLite backend, enables Tool Search via the native `ENABLE_TOOL_SEARCH` environment variable toggle, and delivers a full skill backup/restore lifecycle. Additional improvements include proxy gzip compression, o-series model compatibility, Skills import rework, Ghostty terminal fix, Skills cache strategy optimization, Claude 4.6 context window update, and multiple bug fixes.
**Release Date**: 2026-03-16
**Release Date**: 2026-03-24
**Update Scale**: 17 commits | 61 files changed | +3,335 / -194 lines
**Update Scale**: 36 commits | 107 files changed | +9,124 / -802 lines
---
## Highlights
- **Tool Search domain bypass**: New setting to remove Claude CLI Tool Search domain whitelist via equal-length binary patching, with automatic backup and reapply on startup
- **GitHub Copilot reverse proxy**: Full Copilot proxy support with OAuth device flow authentication, token refresh, and request fingerprint emulation ([⚠️ Risk Notice](#-risk-notice))
- **Copilot Auth Center**: Dedicated authentication management UI for GitHub Copilot OAuth flow with token status display and one-click refresh
- **macOS code signing & notarization**: macOS builds are now code-signed and notarized by Apple, eliminating the "unidentified developer" warning entirely
- **Reasoning Effort mapping**: Proxy-layer auto-mapping — explicit `output_config.effort` takes priority, falling back to `budget_tokens` thresholds (<4 000→low, 4 00016 000→medium, ≥16 000→high) for o-series and GPT-5+ models
- **OpenCode SQLite backend**: Added SQLite session storage for OpenCode alongside existing JSON backend; dual-backend scan with SQLite priority on ID conflicts
- **Codex 1M context window toggle**: One-click checkbox to set `model_context_window = 1000000` with auto-populated `model_auto_compact_token_limit`
- **Disable Auto-Upgrade toggle**: Added `DISABLE_AUTOUPDATER` env var checkbox in the Claude Common Config editor to prevent Claude Code from auto-upgrading
- **Tool Search env var toggle**: Tool Search enabled via Claude 2.1.76+ native `ENABLE_TOOL_SEARCH` environment variable in the Common Config editor — no binary patching required
- **Skill backup/restore lifecycle**: Skills are automatically backed up before uninstall; backup list with restore and delete management added
- **Proxy gzip compression**: Non-streaming proxy requests now auto-negotiate gzip compression, reducing bandwidth usage
- **o-series model compatibility**: Chat Completions proxy correctly uses `max_completion_tokens` for o1/o3/o4-mini models; Responses API kept on the correct `max_output_tokens` field
- **Skills import rework**: Replaced implicit filesystem-based app inference with explicit `ImportSkillSelection` to prevent incorrect multi-app activation
@@ -30,14 +36,70 @@ CC Switch v3.12.3 adds a Tool Search domain restriction bypass via binary patchi
## New Features
### Tool Search Domain Bypass
### GitHub Copilot Reverse Proxy
Added a setting to bypass Claude CLI Tool Search domain whitelist restrictions.
Added full reverse proxy support for GitHub Copilot, enabling Copilot-authenticated requests to be forwarded through CC Switch.
- Resolves the active `claude` command from PATH and applies an equal-length byte patch to remove the domain whitelist check
- Backups stored in `~/.cc-switch/toolsearch-backups/` (SHA-256 of path) so they survive Claude Code version upgrades
- The patch auto-reapplies on app startup when the setting is enabled
- Frontend checks patch result and rolls back the setting on failure
- Implements OAuth device flow authentication for GitHub Copilot
- Automatic token refresh and session management
- Request fingerprint emulation for seamless compatibility
- Integrated into the existing proxy infrastructure alongside Claude, Codex, and Gemini handlers
### Copilot Auth Center
A dedicated authentication management UI for GitHub Copilot.
- OAuth device flow with code display and browser-based authorization
- Token status display showing expiration and validity
- One-click token refresh without re-authentication
- Integrated into the settings panel for easy access
### Reasoning Effort Mapping
Proxy-layer auto-mapping of reasoning effort for OpenAI o-series and GPT-5+ models.
- Two-tier resolution: explicit `output_config.effort` takes priority, falling back to thinking `budget_tokens` thresholds (<4 000→low, 4 00016 000→medium, ≥16 000→high)
- Covers both Chat Completions and Responses API paths with 17 unit tests
### OpenCode SQLite Backend
Added SQLite session storage support for OpenCode alongside the existing JSON backend.
- Dual-backend scan with SQLite priority on ID conflicts
- Atomic session deletion and path validation
- JSON backend remains functional for backwards compatibility
### Codex 1M Context Window Toggle
Added a one-click toggle for Codex 1M context window in the config editor.
- Checkbox sets `model_context_window = 1000000` in `config.toml`
- Auto-populates `model_auto_compact_token_limit = 900000` when enabled
- Unchecking removes both fields cleanly
### Disable Auto-Upgrade Toggle
Added a checkbox in the Claude Common Config editor to disable Claude Code auto-upgrades.
- Sets `DISABLE_AUTOUPDATER=1` in the environment configuration when enabled
- Displayed alongside Teammates mode, Tool Search, and High Effort toggles
### Tool Search Environment Variable Toggle
Tool Search is now enabled via the native `ENABLE_TOOL_SEARCH` environment variable introduced in Claude 2.1.76+.
- Toggle available in the Common Config editor under environment variables
- Sets `ENABLE_TOOL_SEARCH=1` in the Claude environment configuration
- No binary patching required — uses Claude's built-in support
### macOS Code Signing & Notarization
macOS builds are now code-signed and notarized by Apple.
- Application signed with a valid Apple Developer certificate
- Notarized through Apple's notarization service for Gatekeeper approval
- DMG installer also signed and notarized
- Eliminates the "unidentified developer" warning on first launch
### Skill Auto-Backup on Uninstall
@@ -60,6 +122,37 @@ Added management commands for skill backups created during uninstall.
## Changes
### Skills Cache Strategy Optimization
Optimized the Skills cache invalidation strategy for better performance.
- Reduced unnecessary cache refreshes during skill operations
- Improved cache coherence between skill install/uninstall and list queries
### Claude 4.6 Context Window Update
Updated Claude 4.6 model preset with the latest context window size.
- Reflects the expanded context window for Claude 4.6 models
- Updated in provider presets for accurate model information display
### MiniMax M2.7 Upgrade
- Updated MiniMax provider preset to M2.7 model variant
### Xiaomi MiMo Upgrade
- Updated Xiaomi MiMo provider preset to the latest model version
### AddProviderDialog Simplification
- Removed redundant OAuth tab, reducing dialog from 3 tabs to 2 (app-specific + universal)
### Provider Form Advanced Options Collapse
- Model mapping, API format, and other advanced fields in the Claude provider form now auto-collapse when empty
- Auto-expands when any value is set or when a preset fills them in; does not auto-collapse when manually cleared
### Proxy Gzip Compression
Non-streaming proxy requests now support gzip compression for reduced bandwidth usage.
@@ -71,7 +164,7 @@ Non-streaming proxy requests now support gzip compression for reduced bandwidth
Proxy forwarding now handles OpenAI o-series model token parameters correctly.
- Chat Completions path uses `max_completion_tokens` instead of `max_tokens` for o1/o3/o4-mini models (#1451)
- Chat Completions path uses `max_completion_tokens` instead of `max_tokens` for o1/o3/o4-mini models (#1451, thanks @Hemilt0n)
- Responses API path kept on the correct `max_output_tokens` field instead of incorrectly injecting `max_completion_tokens`
### OpenCode Model Variants
@@ -91,9 +184,25 @@ The Skills import flow has been reworked for correctness and cleanup.
## Bug Fixes
### WebDAV Password Clearing
- Fixed an issue where the WebDAV password was silently cleared when saving unrelated settings
### Tool Message Parsing
- Fixed incorrect parsing of tool-use messages in certain proxy response formats
### Dark Mode Styling
- Fixed dark mode rendering inconsistencies in UI components
### Copilot Request Fingerprint
- Fixed request fingerprint generation for Copilot proxy to match expected format
### Provider Form Double Submit
- Prevented duplicate submissions on rapid button clicks in provider add/edit forms (#1352)
- Prevented duplicate submissions on rapid button clicks in provider add/edit forms (#1352, thanks @Hexi1997)
### Ghostty Session Restore
@@ -101,7 +210,7 @@ The Skills import flow has been reworked for correctness and cleanup.
### Skill ZIP Import Extension
- Added `.skill` file extension support in ZIP import dialog (#1240, #1455)
- Added `.skill` file extension support in ZIP import dialog (#1240, #1455, thanks @yovinchen)
### Skill ZIP Install Target App
@@ -109,11 +218,11 @@ The Skills import flow has been reworked for correctness and cleanup.
### OpenClaw Active Card Highlight
- Fixed active OpenClaw provider card not being highlighted (#1419)
- Fixed active OpenClaw provider card not being highlighted (#1419, thanks @funnytime75)
### Responsive Layout with TOC
- Improved responsive design when TOC title exists (#1491)
- Improved responsive design when TOC title exists (#1491, thanks @West-Pavilion)
### Import Skills Dialog White Screen
@@ -125,6 +234,32 @@ The Skills import flow has been reworked for correctness and cleanup.
---
## Documentation
### Pricing Model ID Normalization
- Added documentation section explaining model ID normalization rules (prefix stripping, suffix trimming, `@``-` replacement) in EN/ZH/JA user manuals (#1591, thanks @makoMakoGo)
### macOS Signed Build Messaging
- Removed all `xattr` workaround instructions and "unidentified developer" warnings from README, README_ZH, installation guides (EN/ZH/JA), and FAQ pages (EN/ZH/JA); replaced with "signed and notarized by Apple" messaging
---
## ⚠️ Risk Notice
**GitHub Copilot Reverse Proxy Disclaimer**
The Copilot reverse proxy feature introduced in this release accesses GitHub Copilot services through reverse-engineered, unofficial APIs. Please be aware of the following risks before enabling this feature:
1. **Terms of Service**: This feature may violate [GitHub's Acceptable Use Policies](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies) and [Terms for Additional Products and Features](https://docs.github.com/en/site-policy/github-terms/github-terms-for-additional-products-and-features), which prohibit excessive automated bulk activity, unauthorized service reproduction, and placing undue burden on servers through automated means.
2. **Account Risk**: There are documented cases of GitHub issuing warning emails to users of similar tools, citing "scripted interactions or otherwise deliberately unusual or strenuous" usage patterns. Continued use after a warning may result in temporary or permanent suspension of Copilot access.
3. **No Guarantee**: GitHub may update its detection mechanisms at any time, and usage patterns that work today may be flagged in the future.
Users enable this feature **at their own risk**. CC Switch is not responsible for any account restrictions, warnings, or service suspensions resulting from the use of this feature.
---
## Download & Installation
Visit [Releases](https://github.com/farion1231/cc-switch/releases/latest) to download the appropriate version.
@@ -148,10 +283,11 @@ Visit [Releases](https://github.com/farion1231/cc-switch/releases/latest) to dow
| File | Description |
| ---------------------------------- | -------------------------------------------------------------------- |
| `CC-Switch-v3.12.3-macOS.zip` | **Recommended** - Extract and drag to Applications, Universal Binary |
| `CC-Switch-v3.12.3-macOS.tar.gz` | For Homebrew installation and auto-update |
| `CC-Switch-v3.12.3-macOS.dmg` | **Recommended** - DMG installer, drag to Applications, Universal Binary |
| `CC-Switch-v3.12.3-macOS.zip` | ZIP archive, extract and drag to Applications, Universal Binary |
| `CC-Switch-v3.12.3-macOS.tar.gz` | For Homebrew installation and auto-update |
> **Note**: Since the author doesn't have an Apple Developer account, you may see an "unidentified developer" warning on first launch. Please close it, then go to "System Settings" -> "Privacy & Security" -> click "Open Anyway", and it will open normally afterwards.
> macOS builds are code-signed and notarized by Apple for a seamless install experience.
### Homebrew (macOS)
@@ -174,4 +310,4 @@ brew upgrade --cask cc-switch
| Fedora / RHEL / CentOS / Rocky Linux | `.rpm` | `sudo rpm -i CC-Switch-*.rpm` or `sudo dnf install ./CC-Switch-*.rpm` |
| openSUSE | `.rpm` | `sudo zypper install ./CC-Switch-*.rpm` |
| Arch Linux / Manjaro | `.AppImage` | Add execute permission and run directly, or use AUR |
| Other distributions / Unsure | `.AppImage` | `chmod +x CC-Switch-*.AppImage && ./CC-Switch-*.AppImage` |
| Other distributions / Unsure | `.AppImage` | `chmod +x CC-Switch-*.AppImage && ./CC-Switch-*.AppImage` |
+156 -20
View File
@@ -1,6 +1,6 @@
# CC Switch v3.12.3
> Tool Search ドメイン制限バイパス、Skill バックアップ/リストアライフサイクル、プロキシ Gzip 圧縮と o シリーズモデル互換性
> GitHub Copilot リバースプロキシ、macOS コード署名と公証、Reasoning Effort マッピング、Tool Search 環境変数トグル、Skill バックアップ/リストア、OpenCode SQLite バックエンド
**[中文版 →](v3.12.3-zh.md) | [English →](v3.12.3-en.md)**
@@ -8,19 +8,24 @@
## 概要
CC Switch v3.12.3 は、バイナリパッチによる Tool Search ドメインホワイトリスト制限のバイパス機能を追加し、完全な Skill バックアップ/リストアライフサイクルを導入し、OpenAI o シリーズモデルのプロキシ互換性と gzip 圧縮を改善し、Skills インポート、プロバイダーフォーム、ターミナルセッション復元の堅牢性を修正しました。Skill はアンインストール前に自動バックアップされ、リストアと削除の管理機能が追加されました。インポートフローはファイルシステムベースの暗黙的な推論から明示的なアプリ選択に変更されました
CC Switch v3.12.3 は、GitHub Copilot リバースプロキシと Copilot Auth Center を追加し、Copilot トークンを使用した Claude/OpenAI API へのアクセスを実現しました。macOS ビルドに Apple コード署名と公証を導入し、「開発元を確認できません」の警告を解消しました。Reasoning Effort マッピングにより、Claude の thinking budget を OpenAI 互換の reasoning_effort パラメータに自動変換します。Tool Search は従来のバイナリパッチ方式から Claude 2.1.76+ ネイティブの `ENABLE_TOOL_SEARCH` 環境変数トグルに移行し、共通設定エディタから切り替え可能になりました。OpenCode バックエンドを JSON から SQLite に移行し、Skill バックアップ/リストアライフサイクル、プロキシ gzip 圧縮、o シリーズモデル互換性の改善も含まれます
**リリース日**: 2026-03-16
**リリース日**: 2026-03-24
**更新規模**: 17 commits | 61 files changed | +3,335 / -194 lines
**更新規模**: 36 commits | 107 files changed | +9,124 / -802 lines
---
## ハイライト
- **Tool Search ドメインバイパス**: 等長バイナリパッチで Claude CLI Tool Search のドメインホワイトリストチェックを削除する新設定。起動時に自動バックアップと再適用
- **GitHub Copilot リバースプロキシ**: Copilot トークンを使用して Claude/OpenAI API にアクセスするリバースプロキシを追加。Copilot Auth Center でトークンの取得と管理が可能([⚠️ リスクに関する注意事項](#-リスクに関する注意事項)
- **macOS コード署名と公証**: macOS ビルドが Apple のコード署名と公証に対応し、初回起動時の警告なしでインストール可能に。DMG インストーラーを新たに提供
- **Reasoning Effort マッピング**: プロキシ層での自動マッピング — 明示的な `output_config.effort` を優先し、`budget_tokens` 閾値(<4000→low, 400016000→medium, ≥16000→high)にフォールバック。o シリーズおよび GPT-5+ モデルに対応
- **Tool Search 環境変数トグル**: バイナリパッチ方式を廃止し、Claude 2.1.76+ ネイティブの `ENABLE_TOOL_SEARCH` 環境変数による切り替えに移行。共通設定エディタから設定可能
- **Skill バックアップ/リストアライフサイクル**: アンインストール前に Skill ファイルを自動バックアップ。バックアップリスト、リストア、削除の管理機能を追加
- **OpenCode SQLite バックエンド**: OpenCode に SQLite セッションストレージを追加(既存の JSON バックエンドと併存)。ID 競合時は SQLite を優先するデュアルバックエンドスキャン
- **Codex 1M コンテキストウィンドウトグル**: 設定エディタでワンクリックで `model_context_window = 1000000` を設定可能。`model_auto_compact_token_limit` も自動設定
- **自動アップグレード無効化トグル**: Claude 共通設定エディタに `DISABLE_AUTOUPDATER` 環境変数のチェックボックスを追加し、Claude Code の自動アップグレードを防止
- **プロキシ Gzip 圧縮**: 非ストリーミングプロキシリクエストが gzip 圧縮を自動ネゴシエーションし、帯域幅消費を削減
- **o シリーズモデル互換性**: Chat Completions プロキシが o1/o3/o4-mini モデルに `max_completion_tokens` を正しく使用。Responses API は正しい `max_output_tokens` フィールドを維持
- **Skills インポートの刷新**: ファイルシステムベースの暗黙的なアプリ推論を明示的な `ImportSkillSelection` に置き換え、複数アプリの誤った有効化を防止
@@ -30,14 +35,36 @@ CC Switch v3.12.3 は、バイナリパッチによる Tool Search ドメイン
## 新機能
### Tool Search ドメイン制限バイパス
### GitHub Copilot リバースプロキシ
Claude CLI Tool Search のドメインホワイトリスト制限をバイパスする設定を追加しました。
GitHub Copilot トークンを使用して Claude API および OpenAI API にアクセスするリバースプロキシ機能を追加しました。
- PATH からアクティブな `claude` コマンドを解決し、等長バイトパッチを適用してドメインホワイトリストチェックを削除
- バックアップは `~/.cc-switch/toolsearch-backups/`(パスの SHA-256)に保存され、Claude Code のバージョンアップグレード後も有効
- 設定が有効な場合、アプリ起動時にパッチを自動的に再適用
- フロントエンドがパッチ結果を確認し、失敗時に設定を自動ロールバック
- Copilot のアクセストークンを利用し、Claude Code や Codex などのクライアントからプロキシ経由で API リクエストを転送
- Copilot 固有のリクエストフィンガープリントとヘッダー処理に対応
- プロバイダープリセットに Copilot 用テンプレートを追加
### Copilot Auth Center
Copilot トークンの取得と管理を行う認証センターを追加しました。
- GitHub デバイスフローによるトークン取得をサポート
- トークンの有効期限管理と自動リフレッシュ
- フロントエンドから直接トークンステータスの確認と再認証が可能
### Reasoning Effort マッピング
OpenAI o シリーズおよび GPT-5+ モデル向けのプロキシ層自動マッピング機能を追加しました。
- 二段階の解決ロジック:明示的な `output_config.effort` を優先し、thinking `budget_tokens` 閾値(<4000→low, 400016000→medium, ≥16000→high)にフォールバック
- Chat Completions と Responses API の両パスをカバー、17 個のユニットテスト付き
### Tool Search 環境変数トグル
Claude CLI Tool Search の有効化/無効化を環境変数で制御する設定を追加しました。
- Claude 2.1.76+ で導入されたネイティブの `ENABLE_TOOL_SEARCH` 環境変数を使用
- 共通設定(Common Config)エディタから直接トグル可能
- 従来のバイナリパッチ方式は不要になり、CLI アップデート時の再適用も不要
### Skill アンインストール時の自動バックアップ
@@ -56,10 +83,76 @@ Claude CLI Tool Search のドメインホワイトリスト制限をバイパス
- 削除は確認ダイアログの後にバックアップディレクトリを削除
- ConfirmDialog にネストされたダイアログスタッキングをサポートする設定可能な zIndex プロパティを追加
### OpenCode SQLite バックエンド
OpenCode に SQLite セッションストレージサポートを追加しました(既存の JSON バックエンドと併存)。
- デュアルバックエンドスキャン、ID 競合時は SQLite を優先
- アトミックなセッション削除とパス検証
- JSON バックエンドは後方互換性のため引き続き機能
### Codex 1M コンテキストウィンドウトグル
設定エディタに Codex 1M コンテキストウィンドウのワンクリックトグルを追加しました。
- チェックボックスで `config.toml``model_context_window = 1000000` を設定
- 有効化時に `model_auto_compact_token_limit = 900000` を自動設定
- 無効化時は両フィールドをクリーンに削除
### 自動アップグレード無効化トグル
Claude 共通設定エディタに自動アップグレードを無効化するチェックボックスを追加しました。
- 有効化時に `DISABLE_AUTOUPDATER=1` 環境変数を設定し、Claude Code の自動アップグレードを防止
- Teammates モード、Tool Search、高強度思考トグルと同じ行に表示
### macOS コード署名と公証
macOS ビルドに Apple のコード署名と公証を導入しました。
- Apple Developer ID による署名と Apple 公証サービスによる公証を実施
- 初回起動時の「開発元を確認できません」警告が不要に
- DMG インストーラーを新たに提供し、ドラッグ&ドロップでのインストールに対応
- CI/CD パイプラインに署名・公証ステップを統合
---
## 変更
### Skills キャッシュ戦略の最適化
Skills のキャッシュ戦略を最適化し、パフォーマンスと信頼性を向上しました。
- キャッシュの有効期限管理とインバリデーション戦略を改善
- 不要なキャッシュ再構築を削減し、起動時間を短縮
### Claude 4.6 コンテキストウィンドウ更新
Claude 4.6 モデルのコンテキストウィンドウサイズを更新しました。
- Claude 4.6 の最新コンテキストウィンドウサイズをプリセットに反映
### MiniMax M2.7 アップグレード
MiniMax モデルプリセットを M2.7 にアップグレードしました。
- MiniMax プロバイダープリセットのモデル ID とパラメータを M2.7 に更新
### Xiaomi MiMo アップグレード
Xiaomi MiMo モデルプリセットをアップグレードしました。
- MiMo プロバイダープリセットのモデル ID とパラメータを最新版に更新
### AddProviderDialog の簡素化
- 冗長な OAuth タブを削除し、ダイアログを 3 タブから 2 タブ(アプリ固有 + ユニバーサル)に簡素化
### プロバイダーフォームの高度なオプション折りたたみ
- Claude プロバイダーフォームのモデルマッピング、API フォーマットなどの高度なフィールドが未入力時にデフォルトで折りたたまれるように変更
- プリセットが値を入力すると自動展開。手動クリア時は自動折りたたみしない
### プロキシ Gzip 圧縮
非ストリーミングプロキシリクエストが gzip 圧縮をサポートし、帯域幅消費を削減しました。
@@ -71,7 +164,7 @@ Claude CLI Tool Search のドメインホワイトリスト制限をバイパス
プロキシ転送が OpenAI o シリーズモデルのトークンパラメータを正しく処理するようになりました。
- Chat Completions パスが o1/o3/o4-mini モデルに `max_tokens` の代わりに `max_completion_tokens` を使用 (#1451)
- Chat Completions パスが o1/o3/o4-mini モデルに `max_tokens` の代わりに `max_completion_tokens` を使用 (#1451@Hemilt0n に感謝)
- Responses API パスが正しい `max_output_tokens` フィールドを維持し、`max_completion_tokens` の誤った注入を防止
### OpenCode モデルバリアント
@@ -91,9 +184,25 @@ Skills インポートフローが正確性とクリーンアップのために
## バグ修正
### WebDAV パスワードの消失
- 無関係な設定保存時に WebDAV パスワードがサイレントにクリアされる問題を修正
### ツールメッセージのパース
- プロキシのツールメッセージパース処理の不具合を修正し、特定のツール呼び出しパターンでのエラーを解消
### ダークモードの表示
- ダークモードでの一部 UI コンポーネントの表示不具合を修正
### Copilot リクエストフィンガープリント
- Copilot リバースプロキシのリクエストフィンガープリント生成の不具合を修正し、認証エラーを解消
### プロバイダーフォームの二重送信
- プロバイダー追加/編集フォームでの高速連続クリックによる重複送信を防止 (#1352)
- プロバイダー追加/編集フォームでの高速連続クリックによる重複送信を防止 (#1352@Hexi1997 に感謝)
### Ghostty ターミナルセッション復元
@@ -101,7 +210,7 @@ Skills インポートフローが正確性とクリーンアップのために
### Skill ZIP インポート拡張子
- ZIP インポートダイアログが `.skill` ファイル拡張子をサポートするように修正 (#1240, #1455)
- ZIP インポートダイアログが `.skill` ファイル拡張子をサポートするように修正 (#1240, #1455@yovinchen に感謝)
### Skill ZIP インストール対象アプリ
@@ -109,11 +218,11 @@ Skills インポートフローが正確性とクリーンアップのために
### OpenClaw アクティブカードのハイライト
- OpenClaw の現在アクティブなプロバイダーカードがハイライト表示されない問題を修正 (#1419)
- OpenClaw の現在アクティブなプロバイダーカードがハイライト表示されない問題を修正 (#1419@funnytime75 に感謝)
### TOC 付きレスポンシブレイアウト
- TOC タイトルが存在する場合のレスポンシブデザインを改善 (#1491)
- TOC タイトルが存在する場合のレスポンシブデザインを改善 (#1491@West-Pavilion に感謝)
### Skills インポートダイアログの白い画面
@@ -125,6 +234,32 @@ Skills インポートフローが正確性とクリーンアップのために
---
## ドキュメント
### 料金モデル ID の正規化
- 中英日三言語のユーザーマニュアルにモデル ID 正規化ルール(プレフィックス除去、サフィックストリミング、`@``-` 置換)の説明セクションを追加 (#1591@makoMakoGo に感謝)
### macOS 署名済みメッセージの更新
- README、README_ZH、インストールガイド(EN/ZH/JA)、FAQ ページ(EN/ZH/JA)からすべての `xattr` 回避策と「開発元を確認できません」警告を削除し、「Apple のコード署名と公証済み」メッセージに置換
---
## ⚠️ リスクに関する注意事項
**GitHub Copilot リバースプロキシに関する免責事項**
本リリースで追加された Copilot リバースプロキシ機能は、リバースエンジニアリングによる非公式 API を通じて GitHub Copilot サービスにアクセスします。この機能を有効にする前に、以下のリスクをご確認ください:
1. **利用規約違反の可能性**:この機能は [GitHub 利用規約](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies)および[追加製品の利用条件](https://docs.github.com/en/site-policy/github-terms/github-terms-for-additional-products-and-features)に違反する可能性があります。これらの規約では、過度な自動一括操作、サービスの無断複製、自動化手段によるサーバーへの過度な負荷が禁止されています。
2. **アカウントリスク**:類似ツールの利用者が GitHub から「スクリプト化されたインタラクション、または意図的に異常もしくは過度な使用」を指摘する警告メールを受け取った事例が報告されています。警告後も使用を継続した場合、Copilot へのアクセスが一時的または永久的に停止される可能性があります。
3. **将来の利用保証なし**:GitHub は検出メカニズムをいつでも更新する可能性があり、現在利用可能な使用パターンが将来的にフラグ付けされる可能性があります。
この機能を有効にすることで、ユーザーは**すべてのリスクを自己責任で負う**ものとします。CC Switch は、この機能の使用に起因するアカウント制限、警告、またはサービス停止について一切の責任を負いません。
---
## ダウンロードとインストール
[Releases](https://github.com/farion1231/cc-switch/releases/latest) から適切なバージョンをダウンロードしてください。
@@ -148,10 +283,11 @@ Skills インポートフローが正確性とクリーンアップのために
| ファイル | 説明 |
| ---------------------------------- | ----------------------------------------------------------------- |
| `CC-Switch-v3.12.3-macOS.zip` | **推奨** - 解凍して Applications にドラッグ、Universal Binary |
| `CC-Switch-v3.12.3-macOS.tar.gz` | Homebrew インストールと自動更新用 |
| `CC-Switch-v3.12.3-macOS.dmg` | **推奨** - DMG インストーラー、ドラッグ&ドロップでインストール |
| `CC-Switch-v3.12.3-macOS.zip` | 解凍して Applications にドラッグ、Universal Binary |
| `CC-Switch-v3.12.3-macOS.tar.gz` | Homebrew インストールと自動更新用 |
> **注意**: 作者が Apple Developer アカウントを持っていないため、初回起動時に「開発元を確認できません」という警告が表示される場合があります。一度閉じてから、「システム設定」→「プライバシーとセキュリティ」→「このまま開く」をクリックすると、その後は正常に開けます。
> macOS 版は Apple のコード署名と公証済みで、そのままインストールしてご利用いただけます。
### Homebrew (macOS)
+151 -32
View File
@@ -1,6 +1,6 @@
# CC Switch v3.12.3
> Tool Search 域名限制绕过、Skill 备份/恢复生命周期、代理 Gzip 压缩与 o 系列模型兼容性
> GitHub Copilot 反向代理、macOS 代码签名与公证、Reasoning Effort 映射、Tool Search 环境变量开关、Skill 备份/恢复生命周期
**[English →](v3.12.3-en.md) | [日本語版 →](v3.12.3-ja.md)**
@@ -8,36 +8,84 @@
## 概览
CC Switch v3.12.3 新增了通过二进制补丁绕过 Tool Search 域名白名单限制的功能,引入了完整的 Skill 备份/恢复生命周期,改进了代理对 OpenAI o 系列模型的兼容性和 gzip 压缩支持,并修复了 Skills 导入、供应商表单和终端会话恢复等方面的问题。Skill 卸载前会自动备份并支持恢复和删除管理,导入流程从基于文件系统的隐式推断改为显式应用选择
CC Switch v3.12.3 新增了 **GitHub Copilot 反向代理** 支持和 **Copilot Auth Center** 认证管理,引入了 **Reasoning Effort 映射** 实现跨供应商推理强度控制,通过 Claude 2.1.76+ 原生 `ENABLE_TOOL_SEARCH` 环境变量实现了 **Tool Search 开关**,新增了 **OpenCode SQLite 后端** 支持,并完成了 **macOS 代码签名与 Apple 公证**。同时引入了完整的 Skill 备份/恢复生命周期,改进了代理对 OpenAI o 系列模型的兼容性和 gzip 压缩支持,优化了 Skills 缓存策略,更新了 Claude 4.6 上下文窗口、MiniMax M2.7 和小米 MiMo 模型预设,并修复了 WebDAV 密码、工具消息解析、暗色模式和 Copilot 请求指纹等方面的问题
**发布日期**2026-03-16
**发布日期**2026-03-24
**更新规模**17 commits | 61 files changed | +3,335 / -194 lines
**更新规模**36 commits | 107 files changed | +9,124 / -802 lines
---
## 重点内容
- **Tool Search 域名绕过**:新增设置项,通过等长二进制补丁移除 Claude CLI Tool Search 域名白名单检查,启动时自动备份和重新应用
- **GitHub Copilot 反向代理**:新增 Copilot 反向代理支持,通过 Copilot Auth Center 管理 GitHub Token 认证,实现 Copilot 模型在 Claude Code 中的无缝使用([⚠️ 风险提示](#-风险提示)
- **macOS 代码签名与公证**macOS 版本已通过 Apple 代码签名和公证,新增 DMG 安装格式,无需再手动绕过"未知开发者"警告
- **Reasoning Effort 映射**:代理层自动映射 — 显式 `output_config.effort` 优先,回退到 `budget_tokens` 阈值(<4000→low, 400016000→medium, ≥16000→high),支持 o 系列和 GPT-5+ 模型
- **Tool Search 环境变量开关**:利用 Claude 2.1.76+ 原生 `ENABLE_TOOL_SEARCH` 环境变量,在通用配置编辑器中一键启用 Tool Search
- **Skill 备份/恢复生命周期**:卸载前自动备份 Skill 文件;新增备份列表、恢复和删除管理
- **代理 Gzip 压缩**:非流式代理请求现在自动协商 gzip 压缩,减少带宽消耗
- **o 系列模型兼容性**Chat Completions 代理正确使用 `max_completion_tokens` 处理 o1/o3/o4-mini 模型;Responses API 保持使用正确的 `max_output_tokens` 字段
- **OpenCode SQLite 后端**:为 OpenCode 新增 SQLite 会话存储(与现有 JSON 后端并存),ID 冲突时 SQLite 优先的双后端扫描
- **Codex 1M 上下文窗口开关**:配置编辑器中一键设置 `model_context_window = 1000000`,自动填充 `model_auto_compact_token_limit`
- **禁用自动升级开关**:通用配置编辑器中新增 `DISABLE_AUTOUPDATER` 环境变量复选框,防止 Claude Code 自动升级
- **代理 Gzip 压缩**:非流式代理请求自动协商 gzip 压缩,减少带宽消耗
- **o 系列模型兼容性**Chat Completions 代理正确使用 `max_completion_tokens` 处理 o1/o3/o4-mini 模型
- **Skills 导入重构**:将基于文件系统的隐式应用推断替换为显式的 `ImportSkillSelection`,防止多应用错误激活
- **Ghostty 终端支持**:修复在 Ghostty 终端中恢复 Claude 会话的问题
---
## 新功能
### Tool Search 域名限制绕过
### GitHub Copilot 反向代理
新增设置项,可绕过 Claude CLI Tool Search 的域名白名单限制
新增完整的 GitHub Copilot 集成,作为 Claude Code 供应商使用
- 从 PATH 中解析当前活跃的 `claude` 命令,应用等长字节补丁移除域名白名单检查
- 备份存储在 `~/.cc-switch/toolsearch-backups/`(以路径的 SHA-256 为文件名),Claude Code 升级后备份仍然有效
- 设置启用时,应用启动自动重新应用补丁
- 前端检查补丁结果,失败时自动回滚设置
- 通过 OAuth Device Code 流程进行 GitHub 认证
- 支持多账号管理和自动 Token 刷新
- Anthropic ↔ OpenAI 格式自动转换
- 实时获取可用模型列表和用量统计 (#930,感谢 @Mason-mengze)
### Copilot Auth Center
在设置中新增认证中心面板,全局管理 GitHub 账号。
- 支持按供应商绑定账号(通过 `meta.authBinding`
- 统一的 Token 管理和刷新机制
### Tool Search 开关
利用 Claude 2.1.76+ 原生 `ENABLE_TOOL_SEARCH` 环境变量控制 Tool Search 功能。
- 在供应商通用配置编辑器中以复选框形式暴露
- 替代了之前的二进制补丁方案,更简洁可靠 (#930,感谢 @Mason-mengze)
### Reasoning Effort 映射
新增代理层自动推理强度映射,支持 OpenAI o 系列和 GPT-5+ 模型。
- 两级解析:显式 `output_config.effort` 优先,回退到 `budget_tokens` 阈值(<4000→low, 400016000→medium, ≥16000→high
- 覆盖 Chat Completions 和 Responses API 两条路径,含 17 个单元测试
### OpenCode SQLite 后端
为 OpenCode 新增 SQLite 会话存储支持(与现有 JSON 后端并存)。
- 双后端扫描,ID 冲突时 SQLite 优先
- 原子会话删除和路径校验
- JSON 后端保持向后兼容
### Codex 1M 上下文窗口开关
在配置编辑器中新增 Codex 1M 上下文窗口一键开关。
- 复选框设置 `config.toml` 中的 `model_context_window = 1000000`
- 启用时自动填充 `model_auto_compact_token_limit = 900000`
- 关闭时干净移除两个字段
### 禁用自动升级开关
在 Claude 通用配置编辑器中新增禁用自动升级的复选框。
- 勾选后设置 `DISABLE_AUTOUPDATER=1` 环境变量,阻止 Claude Code 自动升级
- 与 Teammates 模式、Tool Search、高强度思考等开关同一排显示
### Skill 卸载自动备份
@@ -54,25 +102,33 @@ CC Switch v3.12.3 新增了通过二进制补丁绕过 Tool Search 域名白名
- 列出所有可用的 skill 备份及元数据
- 恢复操作将文件拷回 SSOT,保存数据库记录,并同步到当前应用,失败时自动回滚
- 删除操作在确认对话框后移除备份目录
- ConfirmDialog 新增可配置的 zIndex 属性,支持嵌套对话框堆叠
### macOS 代码签名与 Apple 公证
CI 流程新增完整的 macOS 代码签名和 Apple 公证支持。
- 导入 Apple Developer ID 证书,签名 Universal Binary
- 提交 Apple 公证并将票据装订到 `.app``.dmg`
- 硬性验证步骤(`codesign --verify` + `spctl -a` + `stapler validate`)把关发布
---
## 变更
### 代理 Gzip 压缩
### Skills 缓存策略优化
非流式代理请求现在支持 gzip 压缩,减少带宽消耗。
-`invalidateQueries` 替换为直接 `setQueryData` 更新,用于 skill 安装/卸载/导入操作
- 新增 `staleTime: Infinity``keepPreviousData`,消除加载闪烁 (#1573,感谢 @TangZhiZzz)
### 代理 Gzip 压缩
- 非流式请求允许 reqwest 自动协商 gzip 并透明解压响应
- 流式请求保守地保持 `Accept-Encoding: identity`,避免中断的 SSE 流解压出错
### o1/o3 模型兼容性
代理转发现在正确处理 OpenAI o 系列模型的 token 参数。
- Chat Completions 路径对 o1/o3/o4-mini 模型使用 `max_completion_tokens` 替代 `max_tokens` (#1451)
- Responses API 路径保持使用正确的 `max_output_tokens` 字段,不再错误注入 `max_completion_tokens`
- Chat Completions 路径对 o1/o3/o4-mini 模型使用 `max_completion_tokens` 替代 `max_tokens` (#1451,感谢 @Hemilt0n)
- Responses API 路径保持使用正确的 `max_output_tokens` 字段
### OpenCode 模型变体
@@ -80,20 +136,55 @@ CC Switch v3.12.3 新增了通过二进制补丁绕过 Tool Search 域名白名
### Skills 导入流程
Skills 导入流程经过重构,提升正确性和清理能力。
- 将基于文件系统的隐式应用推断替换为显式的 `ImportSkillSelection`,防止同一 skill 目录存在于多个应用路径下时错误激活多个应用
-`sync_to_app` 增加协调逻辑,移除已禁用/孤立的符号链接
- MCP `sync_all_enabled` 现在会从 live 配置中移除已禁用的服务器
- 数据库迁移保留旧版应用映射快照,避免有损重建
### Claude 4.6 上下文窗口
- Claude Opus 4.6 和 Sonnet 4.6 上下文窗口从 200K 更新至 1M(GA 发布)
### MiniMax 模型升级
- MiniMax 预设从 M2.5 升级至 M2.7,更新三语合作伙伴描述
### 小米 MiMo 模型升级
- MiMo 预设从 mimo-v2-flash 升级至 mimo-v2-pro
### 添加供应商对话框简化
- 移除冗余的 OAuth 标签页,对话框从 3 个标签页减少到 2 个(应用专属 + 通用)
### 供应商表单高级选项折叠
- Claude 供应商表单中的模型映射、API 格式等高级字段在未填写时默认折叠
- 预设填充值后自动展开,手动清空不会自动折叠
---
## Bug 修复
### WebDAV 密码被静默清除
- 修复 ProviderList 或 UsageScriptModal 保存设置时 WebDAV 密码被静默清除的问题
- 前端 payload 中剥离 `webdavSync`,后端 `merge_settings_for_save()` 增加回填逻辑保护现有密码
### 工具消息解析
- 修复 Claudetool_result content blocks)、Codexfunction_call/function_call_output payloads)和 Geminiarray content + toolCalls extraction)的 tool_use/tool_result 消息分类 (#1401,感谢 @BlueOcean223)
### 暗色模式选择器
- 将 Tailwind `darkMode``["selector", "class"]` 改为 `["selector", ".dark"]`,确保暗色模式正确激活 (#1596,感谢 @qinxiandiqi)
### Copilot 请求指纹
- 统一所有 Copilot API 调用点的请求指纹头,防止 User-Agent 泄漏和 Stream Check 不匹配
### 供应商表单防重复提交
- 修复快速连续点击按钮时供应商添加/编辑表单重复提交的问题 (#1352)
- 修复快速连续点击按钮时供应商添加/编辑表单重复提交的问题 (#1352,感谢 @Hexi1997)
### Ghostty 终端会话恢复
@@ -101,7 +192,7 @@ Skills 导入流程经过重构,提升正确性和清理能力。
### Skill ZIP 导入扩展名
- ZIP 导入对话框现在支持 `.skill` 文件扩展名 (#1240, #1455)
- ZIP 导入对话框现在支持 `.skill` 文件扩展名 (#1240, #1455,感谢 @yovinchen)
### Skill ZIP 安装目标应用
@@ -109,11 +200,11 @@ Skills 导入流程经过重构,提升正确性和清理能力。
### OpenClaw 活跃供应商高亮
- 修复 OpenClaw 当前激活的供应商卡片未高亮显示的问题 (#1419)
- 修复 OpenClaw 当前激活的供应商卡片未高亮显示的问题 (#1419,感谢 @funnytime75)
### 响应式布局与 TOC
- 改善存在 TOC 标题时的响应式布局 (#1491)
- 改善存在 TOC 标题时的响应式布局 (#1491,感谢 @West-Pavilion)
### Skills 导入对话框白屏
@@ -125,6 +216,33 @@ Skills 导入流程经过重构,提升正确性和清理能力。
---
## 文档
### 定价模型 ID 归一化
- 在中英日三语用户手册中新增模型 ID 归一化规则说明(前缀剥离、后缀修剪、`@``-` 替换)(#1591,感谢 @makoMakoGo)
### macOS 签名与公证说明
- 移除 README、安装指南和 FAQ 中所有 `xattr` 变通方案和"未知开发者"警告
- 替换为"已通过 Apple 代码签名和公证"的说明
---
## ⚠️ 风险提示
**GitHub Copilot 反向代理免责声明**
本版本新增的 Copilot 反向代理功能通过逆向工程的非官方 API 访问 GitHub Copilot 服务。启用此功能前,请注意以下风险:
1. **违反服务条款**:此功能可能违反 [GitHub 可接受使用政策](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies)和[附加产品条款](https://docs.github.com/en/site-policy/github-terms/github-terms-for-additional-products-and-features),其中禁止过度自动化批量活动、未经授权的服务复制以及通过自动化手段对服务器施加不当负担。
2. **账号风险**:已有类似工具的用户收到 GitHub 官方警告邮件,指出其存在"脚本化交互或其他刻意的异常或高强度使用"行为。收到警告后继续使用可能导致 Copilot 访问权限被暂停甚至永久封禁。
3. **无法保证长期可用**:GitHub 可能随时更新其检测机制,当前可用的使用方式未来可能被标记。
用户启用此功能即表示**自行承担所有风险**。CC Switch 不对因使用此功能而导致的任何账号限制、警告或服务暂停承担责任。
---
## 下载与安装
访问 [Releases](https://github.com/farion1231/cc-switch/releases/latest) 下载对应版本。
@@ -148,10 +266,11 @@ Skills 导入流程经过重构,提升正确性和清理能力。
| 文件 | 说明 |
| ---------------------------------- | --------------------------------------------------------- |
| `CC-Switch-v3.12.3-macOS.zip` | **推荐** - 解压后拖入 Applications 即可Universal Binary |
| `CC-Switch-v3.12.3-macOS.tar.gz` | 用于 Homebrew 安装和自动更新 |
| `CC-Switch-v3.12.3-macOS.dmg` | **推荐** - DMG 安装包,拖入 Applications 即可 |
| `CC-Switch-v3.12.3-macOS.zip` | 解压后拖入 ApplicationsUniversal Binary |
| `CC-Switch-v3.12.3-macOS.tar.gz` | 用于 Homebrew 安装和自动更新 |
> **注意**:由于作者没有苹果开发者账号,首次打开可能出现"未知开发者"警告,请先关闭,然后前往"系统设置" → "隐私与安全性" → 点击"仍要打开",之后便可以正常打开
> macOS 版本已通过 Apple 代码签名和公证,可直接安装使用。
### HomebrewmacOS
@@ -131,21 +131,9 @@ brew upgrade --cask cc-switch
2. Extract to get `CC Switch.app`
3. Drag it to the Applications folder
### First Launch Warning
### Signed and Notarized
Since the developer does not have an Apple Developer account, a "developer cannot be verified" warning may appear on first launch:
**Recommended solution**:
Open Terminal and run the following command:
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
**Alternative solution (via System Settings)**:
1. Close the warning dialog
2. Open "System Settings" > "Privacy & Security"
3. Find the CC Switch prompt and click "Open Anyway"
4. Reopen the app to use it normally
CC Switch for macOS is signed and notarized by Apple. You can install and open it directly — no extra steps needed.
## Linux
+2 -16
View File
@@ -2,23 +2,9 @@
## Installation Issues
### macOS Shows "Unidentified Developer"
### macOS Installation
**Problem**: First launch shows "Cannot open because it is from an unidentified developer"
**Solution 1**: Via System Settings
1. Close the warning dialog
2. Open "System Settings" > "Privacy & Security"
3. Find the CC Switch prompt
4. Click "Open Anyway"
5. Reopen the app
**Solution 2**: Via Terminal command (recommended)
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
The app can be opened normally after running this command.
CC Switch for macOS is code-signed and notarized by Apple. You can download and install it directly without any additional steps. If you encounter issues, try downloading the latest version from the [Releases page](https://github.com/farion1231/cc-switch/releases).
### Windows: App Doesn't Launch After Installation
@@ -131,21 +131,9 @@ brew upgrade --cask cc-switch
2. 展開して `CC Switch.app` を取得
3. 「アプリケーション」フォルダにドラッグ
### 初回起動時の警告
### 署名・公証済み
開発者が Apple 開発者アカウントを持っていないため、初回起動時に「不明な開発者」の警告が表示される場合があります
**推奨される解決方法**
ターミナルで以下のコマンドを実行してください:
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
**別の解決方法(システム設定から)**:
1. 警告ダイアログを閉じる
2. 「システム設定」→「プライバシーとセキュリティ」を開く
3. CC Switch に関する表示を見つけ、「このまま開く」をクリック
4. 再度アプリを開くと正常に使用可能
CC Switch の macOS 版は Apple のコード署名と公証を受けています。追加の操作なしで直接インストールして開くことができます
## Linux
+2 -16
View File
@@ -2,23 +2,9 @@
## インストールに関する問題
### macOS で「不明な開発者」と表示される
### macOS のインストール
**問題**:初回起動時に「開けません。身元不明の開発者のものです」と表示される
**解決方法 1**:システム設定から
1. 警告ダイアログを閉じる
2. 「システム設定」→「プライバシーとセキュリティ」を開く
3. CC Switch に関する表示を見つける
4. 「このまま開く」をクリック
5. 再度アプリを開く
**解決方法 2**:ターミナルコマンドから(推奨)
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
実行後、正常にアプリを開けるようになります。
CC Switch の macOS 版は Apple のコード署名と公証を受けています。追加の操作なしで直接ダウンロードしてインストールできます。問題が発生した場合は、[Releases ページ](https://github.com/farion1231/cc-switch/releases) から最新版をダウンロードしてください。
### Windows でインストール後に起動できない
@@ -145,21 +145,9 @@ brew upgrade --cask cc-switch
2. 解压得到 `CC Switch.app`
3. 拖动到「应用程序」文件夹
### 首次打开提示
### 已签名并公证
由于开发者没有 Apple 开发者账号,首次打开可能出现「未知开发者」警告:
**推荐解决方法**
打开终端执行以下命令:
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
**备选解决方法(通过系统设置)**:
1. 关闭警告弹窗
2. 打开「系统设置」→「隐私与安全性」
3. 找到 CC Switch 相关提示,点击「仍要打开」
4. 再次打开应用即可正常使用
CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接安装打开,无需额外操作。
## Linux
+2 -16
View File
@@ -2,23 +2,9 @@
## 安装问题
### macOS 提示「未知开发者」
### macOS 安装
**问题**:首次打开时提示「无法打开,因为它来自身份不明的开发者」
**解决方法一**:通过系统设置
1. 关闭警告弹窗
2. 打开「系统设置」→「隐私与安全性」
3. 找到 CC Switch 相关提示
4. 点击「仍要打开」
5. 再次打开应用
**解决方法二**:通过终端命令(推荐)
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
执行后即可正常打开应用。
CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接下载安装,无需额外操作。如遇问题,请尝试从 [Releases 页面](https://github.com/farion1231/cc-switch/releases) 下载最新版本。
### Windows 安装后无法启动
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

+9
View File
@@ -883,6 +883,7 @@ mod tests {
dir: TempDir,
original_home: Option<String>,
original_userprofile: Option<String>,
original_test_home: Option<String>,
}
impl TempHome {
@@ -890,14 +891,17 @@ mod tests {
let dir = TempDir::new().expect("failed to create temp home");
let original_home = env::var("HOME").ok();
let original_userprofile = env::var("USERPROFILE").ok();
let original_test_home = env::var("CC_SWITCH_TEST_HOME").ok();
env::set_var("HOME", dir.path());
env::set_var("USERPROFILE", dir.path());
env::set_var("CC_SWITCH_TEST_HOME", dir.path());
Self {
dir,
original_home,
original_userprofile,
original_test_home,
}
}
}
@@ -913,6 +917,11 @@ mod tests {
Some(value) => env::set_var("USERPROFILE", value),
None => env::remove_var("USERPROFILE"),
}
match &self.original_test_home {
Some(value) => env::set_var("CC_SWITCH_TEST_HOME", value),
None => env::remove_var("CC_SWITCH_TEST_HOME"),
}
}
}
+74 -2
View File
@@ -6,8 +6,20 @@ fn merge_settings_for_save(
mut incoming: crate::settings::AppSettings,
existing: &crate::settings::AppSettings,
) -> crate::settings::AppSettings {
if incoming.webdav_sync.is_none() {
incoming.webdav_sync = existing.webdav_sync.clone();
match (&mut incoming.webdav_sync, &existing.webdav_sync) {
// incoming 没有 webdav → 保留现有
(None, _) => {
incoming.webdav_sync = existing.webdav_sync.clone();
}
// incoming 有 webdav 但密码为空,且现有有密码 → 填回现有密码
// get_settings_for_frontend 总是清空密码,所以通过 save_settings
// 传入的空密码意味着"保持现有"而非"用户主动清空"
(Some(incoming_sync), Some(existing_sync))
if incoming_sync.password.is_empty() && !existing_sync.password.is_empty() =>
{
incoming_sync.password = existing_sync.password.clone();
}
_ => {}
}
incoming
}
@@ -116,6 +128,66 @@ mod tests {
Some("https://dav.new.example.com")
);
}
/// Regression test: frontend always receives empty password from
/// get_settings_for_frontend(). If a component accidentally spreads
/// the full settings object into save_settings, the empty password
/// must NOT overwrite the existing one.
#[test]
fn save_settings_should_preserve_password_when_incoming_has_empty_password() {
let mut existing = AppSettings::default();
existing.webdav_sync = Some(WebDavSyncSettings {
base_url: "https://dav.example.com".to_string(),
username: "alice".to_string(),
password: "secret".to_string(),
..WebDavSyncSettings::default()
});
// Simulate frontend sending settings with cleared password
let mut incoming = AppSettings::default();
incoming.webdav_sync = Some(WebDavSyncSettings {
base_url: "https://dav.example.com".to_string(),
username: "alice".to_string(),
password: "".to_string(),
..WebDavSyncSettings::default()
});
let merged = merge_settings_for_save(incoming, &existing);
assert_eq!(
merged.webdav_sync.as_ref().map(|v| v.password.as_str()),
Some("secret"),
"empty password from frontend must not overwrite existing password"
);
}
/// When both incoming and existing have no password, merge should
/// work without panicking and keep the empty state.
#[test]
fn save_settings_should_handle_both_empty_passwords() {
let mut existing = AppSettings::default();
existing.webdav_sync = Some(WebDavSyncSettings {
base_url: "https://dav.example.com".to_string(),
username: "alice".to_string(),
password: "".to_string(),
..WebDavSyncSettings::default()
});
let mut incoming = AppSettings::default();
incoming.webdav_sync = Some(WebDavSyncSettings {
base_url: "https://dav.example.com".to_string(),
username: "alice".to_string(),
password: "".to_string(),
..WebDavSyncSettings::default()
});
let merged = merge_settings_for_save(incoming, &existing);
assert_eq!(
merged.webdav_sync.as_ref().map(|v| v.password.as_str()),
Some("")
);
}
}
/// 获取开机自启状态
+8
View File
@@ -296,6 +296,14 @@ fn schema_migration_v4_adds_pricing_model_columns() {
let conn = Connection::open_in_memory().expect("open memory db");
conn.execute_batch(
r#"
CREATE TABLE providers (
id TEXT NOT NULL,
app_type TEXT NOT NULL,
name TEXT NOT NULL,
settings_config TEXT NOT NULL DEFAULT '{}',
meta TEXT NOT NULL DEFAULT '{}',
PRIMARY KEY (id, app_type)
);
CREATE TABLE proxy_config (app_type TEXT PRIMARY KEY);
CREATE TABLE proxy_request_logs (request_id TEXT PRIMARY KEY, model TEXT NOT NULL);
CREATE TABLE mcp_servers (
+1
View File
@@ -22,6 +22,7 @@ pub mod response_handler;
pub mod response_processor;
pub(crate) mod server;
pub mod session;
pub(crate) mod sse;
pub mod thinking_budget_rectifier;
pub mod thinking_optimizer;
pub mod thinking_rectifier;
+8 -3
View File
@@ -2,6 +2,7 @@
//!
//! 实现 OpenAI SSE → Anthropic SSE 格式转换
use crate::proxy::sse::strip_sse_field;
use bytes::Bytes;
use futures::stream::{Stream, StreamExt};
use serde::{Deserialize, Serialize};
@@ -118,7 +119,7 @@ pub fn create_anthropic_sse_stream(
}
for l in line.lines() {
if let Some(data) = l.strip_prefix("data: ") {
if let Some(data) = strip_sse_field(l, "data") {
if data.trim() == "[DONE]" {
log::debug!("[Claude/OpenRouter] <<< OpenAI SSE: [DONE]");
let event = json!({"type": "message_stop"});
@@ -609,7 +610,9 @@ mod tests {
let events: Vec<Value> = merged
.split("\n\n")
.filter_map(|block| {
let data = block.lines().find_map(|line| line.strip_prefix("data: "))?;
let data = block
.lines()
.find_map(|line| strip_sse_field(line, "data"))?;
serde_json::from_str::<Value>(data).ok()
})
.collect();
@@ -694,7 +697,9 @@ mod tests {
let events: Vec<Value> = merged
.split("\n\n")
.filter_map(|block| {
let data = block.lines().find_map(|line| line.strip_prefix("data: "))?;
let data = block
.lines()
.find_map(|line| strip_sse_field(line, "data"))?;
serde_json::from_str::<Value>(data).ok()
})
.collect();
@@ -9,6 +9,7 @@
//! 与 Chat Completions 的 delta chunk 模型完全不同,需要独立的状态机处理。
use super::transform_responses::{build_anthropic_usage_from_responses, map_responses_stop_reason};
use crate::proxy::sse::strip_sse_field;
use bytes::Bytes;
use futures::stream::{Stream, StreamExt};
use serde_json::{json, Value};
@@ -133,9 +134,9 @@ pub fn create_anthropic_sse_stream_from_responses(
let mut data_parts: Vec<String> = Vec::new();
for line in block.lines() {
if let Some(evt) = line.strip_prefix("event: ") {
if let Some(evt) = strip_sse_field(line, "event") {
event_type = Some(evt.trim().to_string());
} else if let Some(d) = line.strip_prefix("data: ") {
} else if let Some(d) = strip_sse_field(line, "data") {
data_parts.push(d.to_string());
}
}
@@ -810,7 +811,9 @@ mod tests {
let events: Vec<Value> = merged
.split("\n\n")
.filter_map(|block| {
let data = block.lines().find_map(|line| line.strip_prefix("data: "))?;
let data = block
.lines()
.find_map(|line| strip_sse_field(line, "data"))?;
serde_json::from_str::<Value>(data).ok()
})
.collect();
+22 -1
View File
@@ -5,6 +5,7 @@
use super::session::ProxySession;
use super::usage::parser::TokenUsage;
use super::ProxyError;
use crate::proxy::sse::strip_sse_field;
use bytes::Bytes;
use futures::stream::{Stream, StreamExt};
use serde_json::Value;
@@ -90,7 +91,7 @@ impl StreamHandler {
buffer = buffer[pos + 2..].to_string();
for line in event_text.lines() {
if let Some(data) = line.strip_prefix("data: ") {
if let Some(data) = strip_sse_field(line, "data") {
if data.trim() != "[DONE]" {
if let Ok(json) = serde_json::from_str::<Value>(data) {
let mut guard = events.lock().await;
@@ -211,4 +212,24 @@ mod tests {
let handler = StreamHandler::new(30);
assert_eq!(handler.idle_timeout, Duration::from_secs(30));
}
#[test]
fn test_strip_sse_field_accepts_optional_space() {
assert_eq!(
super::strip_sse_field("data: {\"ok\":true}", "data"),
Some("{\"ok\":true}")
);
assert_eq!(
super::strip_sse_field("data:{\"ok\":true}", "data"),
Some("{\"ok\":true}")
);
assert_eq!(
super::strip_sse_field("event: message_start", "event"),
Some("message_start")
);
assert_eq!(
super::strip_sse_field("event:message_start", "event"),
Some("message_start")
);
}
}
+23 -1
View File
@@ -6,6 +6,7 @@ use super::{
handler_config::UsageParserConfig,
handler_context::{RequestContext, StreamingTimeoutConfig},
server::ProxyState,
sse::strip_sse_field,
usage::parser::TokenUsage,
ProxyError,
};
@@ -527,7 +528,7 @@ pub fn create_logged_passthrough_stream(
if !event_text.trim().is_empty() {
// 提取 data 部分并尝试解析为 JSON
for line in event_text.lines() {
if let Some(data) = line.strip_prefix("data: ") {
if let Some(data) = strip_sse_field(line, "data") {
if data.trim() != "[DONE]" {
if let Ok(json_value) = serde_json::from_str::<Value>(data) {
if let Some(c) = &collector {
@@ -591,6 +592,27 @@ mod tests {
use std::sync::Arc;
use tokio::sync::RwLock;
#[test]
fn test_strip_sse_field_accepts_optional_space() {
assert_eq!(
super::strip_sse_field("data: {\"ok\":true}", "data"),
Some("{\"ok\":true}")
);
assert_eq!(
super::strip_sse_field("data:{\"ok\":true}", "data"),
Some("{\"ok\":true}")
);
assert_eq!(
super::strip_sse_field("event: message_start", "event"),
Some("message_start")
);
assert_eq!(
super::strip_sse_field("event:message_start", "event"),
Some("message_start")
);
assert_eq!(super::strip_sse_field("id:1", "data"), None);
}
fn build_state(db: Arc<Database>) -> ProxyState {
ProxyState {
db: db.clone(),
+31
View File
@@ -0,0 +1,31 @@
#[inline]
pub(crate) fn strip_sse_field<'a>(line: &'a str, field: &str) -> Option<&'a str> {
line.strip_prefix(&format!("{field}: "))
.or_else(|| line.strip_prefix(&format!("{field}:")))
}
#[cfg(test)]
mod tests {
use super::strip_sse_field;
#[test]
fn strip_sse_field_accepts_optional_space() {
assert_eq!(
strip_sse_field("data: {\"ok\":true}", "data"),
Some("{\"ok\":true}")
);
assert_eq!(
strip_sse_field("data:{\"ok\":true}", "data"),
Some("{\"ok\":true}")
);
assert_eq!(
strip_sse_field("event: message_start", "event"),
Some("message_start")
);
assert_eq!(
strip_sse_field("event:message_start", "event"),
Some("message_start")
);
assert_eq!(strip_sse_field("id:1", "data"), None);
}
}
+2 -1
View File
@@ -267,7 +267,8 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
setShowUsageConfirm(false);
try {
if (settingsData) {
await settingsApi.save({ ...settingsData, usageConfirmed: true });
const { webdavSync: _, ...rest } = settingsData;
await settingsApi.save({ ...rest, usageConfirmed: true });
await queryClient.invalidateQueries({ queryKey: ["settings"] });
}
} catch (error) {
+2 -1
View File
@@ -204,7 +204,8 @@ export function ProviderList({
setShowStreamCheckConfirm(false);
try {
if (settings) {
await settingsApi.save({ ...settings, streamCheckConfirmed: true });
const { webdavSync: _, ...rest } = settings;
await settingsApi.save({ ...rest, streamCheckConfirmed: true });
await queryClient.invalidateQueries({ queryKey: ["settings"] });
}
} catch (error) {
@@ -82,6 +82,9 @@ export function CommonConfigEditor({
config?.env?.ENABLE_TOOL_SEARCH === "true" ||
config?.env?.ENABLE_TOOL_SEARCH === "1",
effortHigh: config?.effortLevel === "high",
disableAutoUpgrade:
config?.env?.DISABLE_AUTOUPDATER === "1" ||
config?.env?.DISABLE_AUTOUPDATER === 1,
};
} catch {
return {
@@ -89,6 +92,7 @@ export function CommonConfigEditor({
teammates: false,
enableToolSearch: false,
effortHigh: false,
disableAutoUpgrade: false,
};
}
}, [localValue]);
@@ -131,6 +135,15 @@ export function CommonConfigEditor({
delete config.effortLevel;
}
break;
case "disableAutoUpgrade":
if (!config.env) config.env = {};
if (checked) {
config.env.DISABLE_AUTOUPDATER = "1";
} else {
delete config.env.DISABLE_AUTOUPDATER;
if (Object.keys(config.env).length === 0) delete config.env;
}
break;
}
handleLocalChange(JSON.stringify(config, null, 2));
@@ -220,6 +233,17 @@ export function CommonConfigEditor({
/>
<span>{t("claudeConfig.effortHigh")}</span>
</label>
<label className="inline-flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">
<input
type="checkbox"
checked={toggleStates.disableAutoUpgrade}
onChange={(e) =>
handleToggle("disableAutoUpgrade", e.target.checked)
}
className="w-4 h-4 text-blue-500 bg-white dark:bg-gray-800 border-border-default rounded focus:ring-blue-500 dark:focus:ring-blue-400 focus:ring-2"
/>
<span>{t("claudeConfig.disableAutoUpgrade")}</span>
</label>
</div>
<JsonEditor
value={localValue}
@@ -208,12 +208,15 @@ export function SessionManagerPage({ appId }: { appId: string }) {
return (
<TooltipProvider>
<div className="mx-auto px-4 sm:px-6 flex flex-col flex-1 min-h-0">
<div
className="mx-auto px-4 sm:px-6 flex flex-col h-full min-h-0"
onWheel={(e) => e.stopPropagation()}
>
<div className="flex-1 overflow-hidden flex flex-col gap-4">
{/* 主内容区域 - 左右分栏 */}
<div className="flex-1 overflow-hidden grid gap-4 md:grid-cols-[320px_1fr]">
{/* 左侧会话列表 */}
<Card className="flex flex-col overflow-hidden">
<Card className="flex flex-col flex-1 min-h-0 overflow-hidden">
<CardHeader className="py-2 px-3 border-b">
{isSearchOpen ? (
<div className="relative flex-1">
@@ -387,7 +390,7 @@ export function SessionManagerPage({ appId }: { appId: string }) {
</div>
)}
</CardHeader>
<CardContent className="flex-1 overflow-hidden p-0">
<CardContent className="flex-1 min-h-0 p-0">
<ScrollArea className="h-full">
<div className="p-2">
{isLoading ? (
@@ -605,7 +608,7 @@ export function SessionManagerPage({ appId }: { appId: string }) {
</CardHeader>
{/* 消息列表区域 */}
<CardContent className="flex-1 overflow-hidden p-0">
<CardContent className="flex-1 min-h-0 p-0">
<div className="flex h-full min-w-0">
{/* 消息列表 */}
<ScrollArea className="flex-1 min-w-0">
+10 -1
View File
@@ -110,7 +110,16 @@ const UnifiedSkillsPanel = React.forwardRef<
message: t("skills.uninstallConfirm", { name: skill.name }),
onConfirm: async () => {
try {
const result = await uninstallMutation.mutateAsync(skill.id);
// 构建 skillKey 用于更新 discoverable 缓存
const installName =
skill.directory.split(/[/\\]/).pop()?.toLowerCase() ||
skill.directory.toLowerCase();
const skillKey = `${installName}:${skill.repoOwner?.toLowerCase() || ""}:${skill.repoName?.toLowerCase() || ""}`;
const result = await uninstallMutation.mutateAsync({
id: skill.id,
skillKey,
});
setConfirmDialog(null);
toast.success(t("skills.uninstallSuccess", { name: skill.name }), {
description: result.backupPath
+27
View File
@@ -514,6 +514,33 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
},
},
},
{
name: "StepFun Step Plan",
websiteUrl: "https://platform.stepfun.com/docs/zh/step-plan/overview",
apiKeyUrl: "https://platform.stepfun.com/interface-key",
settingsConfig: {
npm: "@ai-sdk/openai-compatible",
name: "StepFun Step Plan",
options: {
baseURL: "https://api.stepfun.com/step_plan/v1",
apiKey: "",
setCacheKey: true,
},
models: {
"step-3.5-flash": { name: "Step 3.5 Flash" },
},
},
category: "cn_official",
icon: "stepfun",
iconColor: "#005AFF",
templateValues: {
apiKey: {
label: "API Key",
placeholder: "step-...",
editorValue: "",
},
},
},
{
name: "ModelScope",
websiteUrl: "https://modelscope.cn",
+91 -14
View File
@@ -1,4 +1,4 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient, keepPreviousData } from "@tanstack/react-query";
import {
skillsApi,
type SkillBackupEntry,
@@ -10,11 +10,15 @@ import type { AppId } from "@/lib/api/types";
/**
* Skills
* 使 staleTime: Infinity placeholderData: keepPreviousData
* 使
*/
export function useInstalledSkills() {
return useQuery({
queryKey: ["skills", "installed"],
queryFn: () => skillsApi.getInstalled(),
staleTime: Infinity,
placeholderData: keepPreviousData,
});
}
@@ -38,17 +42,21 @@ export function useDeleteSkillBackup() {
/**
* Skills
* 使 staleTime: Infinity placeholderData: keepPreviousData
* 使
*/
export function useDiscoverableSkills() {
return useQuery({
queryKey: ["skills", "discoverable"],
queryFn: () => skillsApi.discoverAvailable(),
staleTime: Infinity, // 无限缓存,直到仓库变化时 invalidate
staleTime: Infinity,
placeholderData: keepPreviousData,
});
}
/**
* Skill
* /
*/
export function useInstallSkill() {
const queryClient = useQueryClient();
@@ -60,23 +68,76 @@ export function useInstallSkill() {
skill: DiscoverableSkill;
currentApp: AppId;
}) => skillsApi.installUnified(skill, currentApp),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
onSuccess: (installedSkill, _vars, _ctx) => {
const { skill } = _vars;
// 直接更新 installed 缓存
queryClient.setQueryData<InstalledSkill[]>(
["skills", "installed"],
(oldData) => {
if (!oldData) return [installedSkill];
return [...oldData, installedSkill];
},
);
// 更新 discoverable 缓存中对应技能的 installed 状态
const installName =
skill.directory.split(/[/\\]/).pop()?.toLowerCase() ||
skill.directory.toLowerCase();
const skillKey = `${installName}:${skill.repoOwner.toLowerCase()}:${skill.repoName.toLowerCase()}`;
queryClient.setQueryData<DiscoverableSkill[]>(
["skills", "discoverable"],
(oldData) => {
if (!oldData) return oldData;
return oldData.map((s) => {
if (s.key === skillKey) {
return { ...s, installed: true };
}
return s;
});
},
);
},
});
}
/**
* Skill
* /
*/
export function useUninstallSkill() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => skillsApi.uninstallUnified(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
mutationFn: ({
id,
skillKey,
}: {
id: string;
skillKey: string;
}) => skillsApi.uninstallUnified(id).then((result) => ({ ...result, skillKey })),
onSuccess: ({ skillKey }, _vars) => {
// 直接更新 installed 缓存,移除该 skill
queryClient.setQueryData<InstalledSkill[]>(
["skills", "installed"],
(oldData) => {
if (!oldData) return oldData;
return oldData.filter((s) => s.id !== _vars.id);
},
);
// 更新 discoverable 缓存中对应技能的 installed 状态
queryClient.setQueryData<DiscoverableSkill[]>(
["skills", "discoverable"],
(oldData) => {
if (!oldData) return oldData;
return oldData.map((s) => {
if (s.key === skillKey) {
return { ...s, installed: false };
}
return s;
});
},
);
},
});
}
@@ -132,14 +193,23 @@ export function useScanUnmanagedSkills() {
/**
* Skills
* /
*/
export function useImportSkillsFromApps() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (imports: ImportSkillSelection[]) =>
skillsApi.importFromApps(imports),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
onSuccess: (importedSkills) => {
// 直接更新 installed 缓存
queryClient.setQueryData<InstalledSkill[]>(
["skills", "installed"],
(oldData) => {
if (!oldData) return importedSkills;
return [...oldData, ...importedSkills];
},
);
// 刷新 unmanaged 列表(已被导入的应该移除)
queryClient.invalidateQueries({ queryKey: ["skills", "unmanaged"] });
},
});
@@ -186,6 +256,7 @@ export function useRemoveSkillRepo() {
/**
* ZIP Skills
* /
*/
export function useInstallSkillsFromZip() {
const queryClient = useQueryClient();
@@ -197,9 +268,15 @@ export function useInstallSkillsFromZip() {
filePath: string;
currentApp: AppId;
}) => skillsApi.installFromZip(filePath, currentApp),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
queryClient.invalidateQueries({ queryKey: ["skills", "unmanaged"] });
onSuccess: (installedSkills) => {
// 直接更新 installed 缓存
queryClient.setQueryData<InstalledSkill[]>(
["skills", "installed"],
(oldData) => {
if (!oldData) return installedSkills;
return [...oldData, ...installedSkills];
},
);
},
});
}
+2 -1
View File
@@ -65,7 +65,8 @@
"hideAttribution": "Hide AI Attribution",
"enableTeammates": "Teammates Mode",
"enableToolSearch": "Enable Tool Search",
"effortHigh": "High Effort Thinking"
"effortHigh": "High Effort Thinking",
"disableAutoUpgrade": "Disable Auto-Upgrade"
},
"header": {
"viewOnGithub": "View on GitHub",
+2 -1
View File
@@ -65,7 +65,8 @@
"hideAttribution": "AI署名を非表示",
"enableTeammates": "Teammates モード",
"enableToolSearch": "Tool Search を有効化",
"effortHigh": "高強度思考"
"effortHigh": "高強度思考",
"disableAutoUpgrade": "自動アップグレードを無効化"
},
"header": {
"viewOnGithub": "GitHub で見る",
+2 -1
View File
@@ -65,7 +65,8 @@
"hideAttribution": "隐藏 AI 署名",
"enableTeammates": "Teammates 模式",
"enableToolSearch": "启用 Tool Search",
"effortHigh": "高强度思考"
"effortHigh": "高强度思考",
"disableAutoUpgrade": "禁用自动升级"
},
"header": {
"viewOnGithub": "在 GitHub 上查看",
@@ -11,6 +11,8 @@ const toggleSkillAppMock = vi.fn();
const uninstallSkillMock = vi.fn();
const importSkillsMock = vi.fn();
const installFromZipMock = vi.fn();
const deleteSkillBackupMock = vi.fn();
const restoreSkillBackupMock = vi.fn();
vi.mock("sonner", () => ({
toast: {
@@ -25,9 +27,22 @@ vi.mock("@/hooks/useSkills", () => ({
data: [],
isLoading: false,
}),
useSkillBackups: () => ({
data: [],
refetch: vi.fn(),
isFetching: false,
}),
useDeleteSkillBackup: () => ({
mutateAsync: deleteSkillBackupMock,
isPending: false,
}),
useToggleSkillApp: () => ({
mutateAsync: toggleSkillAppMock,
}),
useRestoreSkillBackup: () => ({
mutateAsync: restoreSkillBackupMock,
isPending: false,
}),
useUninstallSkill: () => ({
mutateAsync: uninstallSkillMock,
}),
@@ -68,6 +83,8 @@ describe("UnifiedSkillsPanel", () => {
uninstallSkillMock.mockReset();
importSkillsMock.mockReset();
installFromZipMock.mockReset();
deleteSkillBackupMock.mockReset();
restoreSkillBackupMock.mockReset();
});
it("opens the import dialog without crashing when app toggles render", async () => {