mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 21:30:17 +08:00
61d7ac01fb9d0a3541f426c41dde7331049230a5
141 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d1f6c74be8 |
fix(usage): treat usage_script credentials as explicit overrides (#4654)
* fix(usage): treat usage_script credentials as explicit overrides * fix: treat usage script credentials as explicit overrides |
||
|
|
f1328d89fc |
feat(skills): flag unmanaged skills with a green dot on the Import button
- Add an optional `enabled` flag to useScanUnmanagedSkills; the Skills panel scans once on mount (enabled:true), with 30s staleTime + keepPreviousData to dedupe disk IO across navigations. - App subscribes to the shared query (enabled:false) and renders a green dot + tooltip on the top-bar Import button when unmanaged skills are available. |
||
|
|
a5903d8600 |
feat(health-check): replace real-LLM probe with HTTP reachability check
The provider panel health check sent a real streaming model request, which many third-party providers block (401/403/WAF), causing false negatives while only stable official endpoints passed. Replace it with a lightweight reachability probe: GET the provider base_url and treat any HTTP response (200/4xx/5xx) as reachable; only DNS/connect/TLS/timeout count as failure. Latency is the probe's TTFB. Backend (services/stream_check.rs): rewrite ~2200 -> ~350 lines, dropping real-request building, format conversion, auth and API-path resolution while keeping per-app base_url extraction. Defaults: 8s timeout, 1 retry, 1500ms degraded threshold. Failover invariant: the reachability check must never reset the circuit breaker (reachable != usable; a 403 host is reachable but broken for real traffic). Remove the resetCircuitBreaker call from useStreamCheck; failover failure detection stays driven solely by real proxy traffic (forwarder/circuit_breaker untouched). useResetCircuitBreaker is kept dormant for a future manual-recovery entry. Open the check to all providers: drop the official/copilot/codex-oauth/third-party gating and the 'sends a real request' confirm dialog. For official providers whose base_url is intentionally empty, fall back to the endpoint the client actually uses (Claude -> api.anthropic.com, Codex -> chatgpt.com/backend-api/codex, Gemini -> generativelanguage). Non-official providers with a missing base_url still error to avoid a false green light. Claude Desktop Official is native 1P mode (talks to claude.ai, cc-switch not in the request path, no reliable endpoint) so its button stays hidden. Slim StreamCheckConfig and per-provider testConfig to timeout/threshold/retries (drop test model + prompt); sync zh/en/ja/zh-TW. Retain the now-unused anthropic_to_openai/anthropic_to_gemini transform utilities and their test suites. |
||
|
|
948d762792 |
feat(codex): add unified session history toggle for official providers
Codex buckets resume history by the model_provider id recorded in each session: official runs (no key, built-in "openai") and cc-switch third-party runs (shared "custom") are mutually invisible in the resume picker. Add an opt-in setting that runs official providers under the shared "custom" id so future official sessions land in the same history bucket as third-party ones. Forward-only by design: existing sessions are not migrated. When enabled, official live config.toml gets model_provider = "custom" plus a [model_providers.custom] entry that mirrors the built-in openai provider (requires_openai_auth routes auth to the ChatGPT login in auth.json, name "OpenAI" keeps is_openai() feature gates, explicit supports_websockets/wire_api restore built-in defaults). auth.json is untouched. Key invariants: - Injection lives only in the live config: switch-away backfill strips the exact injected shape, so stored provider configs stay clean and turning the toggle off fully reverts on the next write. - Toggle changes apply immediately via a takeover-aware reapply: when the proxy owns the live config (backup/placeholder present), only the live backup is updated, mirroring the provider-switch path. - The takeover backup path runs the same injection so a takeover release restores a config that still carries the unified routing. - Injection refuses to activate a foreign [model_providers.custom] table (e.g. stale entry with a third-party base_url) to avoid routing ChatGPT OAuth traffic to an unknown backend. The toggle lives under Settings → Codex App Enhancements; the description warns that resuming old sessions across providers may fail because encrypted_content reasoning only decrypts on the backend that created it (upstream treats cross-provider resume as unsupported). |
||
|
|
473f21971d |
feat(usage): add official subscription quota template with unified tier rendering
Changes: - Add official_subscription template type for Claude/Codex/Gemini - Replace implicit 'category=official auto-query' with explicit opt-in template - Default disabled; users enable via usage script modal with configurable interval - Unify tier→label mapping across subscription and script paths via labeled_tier_parts() - Fix tray rendering: week aliases (seven_day/opus/sonnet) now use highest utilization - Add depth guard: official_subscription checks enabled flag in query_provider_usage_inner - Add cache invalidation symmetry: invalidate_subscription() for disabled providers - i18n: add templateOfficialSubscription + hint in zh/en/ja/zh-TW Backend (Rust): - provider.rs: add TEMPLATE_TYPE_OFFICIAL_SUBSCRIPTION branch, flatten SubscriptionQuota→UsageData - tray.rs: extract labeled_tier_parts() shared by both summary functions, use max_by for multi-alias groups - usage_cache.rs: add invalidate_subscription() method - Test coverage: add week-alias highest-utilization tests for both paths Frontend (TypeScript): - UsageScriptModal: add official_subscription to templates, auto-detect for official providers - ProviderCard: gate useUsageQuery with !isOfficialSubscriptionUsage, pass autoQueryInterval to footer - SubscriptionQuotaFooter: accept autoQueryInterval prop, default 0 (disabled) - constants.ts: add TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION Fixes tier rendering regression where: - Claude/Codex: seven_day was missed (only weekly_limit matched) → lost 7-day window in tray - Gemini: gemini_pro/flash/flash_lite fell through to fallback → leaked machine names - Multi-window (opus+sonnet): find() took first, not worst → underestimated utilization and emoji color All tests pass (cargo test + cargo clippy clean). |
||
|
|
2a24da517f |
feat: 新增 S3 兼容云存储同步 (#1351)
* Add S3 Cloud Sync design document Design for adding AWS S3 as a new Cloud Sync backend alongside WebDAV. Hybrid approach: extract shared sync protocol, add independent S3 transport. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add S3 cloud sync implementation design (reqwest + Sig V4) Updated design based on 2026-03-06 draft: switches from rust-s3 crate to hand-rolled AWS Sig V4 on existing reqwest for broader S3-compatible service support (AWS, MinIO, R2, Alibaba OSS, Tencent COS, Huawei OBS). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add S3 cloud sync implementation plan (11 tasks, TDD) Detailed step-by-step plan covering: sync_protocol extraction, S3 Sig V4 transport, settings, sync/auto-sync modules, Tauri commands, frontend presets/dynamic form, and i18n. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * deps: add hmac crate for S3 Sig V4 signing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract sync_protocol.rs from webdav_sync.rs for shared use Move transport-agnostic sync protocol logic (constants, types, snapshot building, manifest validation, artifact verification, snapshot application, utilities) into a new shared sync_protocol module so both WebDAV and the upcoming S3 transport can reuse it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use transport-neutral error keys in sync_protocol Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add S3 transport layer with AWS Sig V4 signing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add S3SyncSettings to AppSettings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add S3 sync module with upload/download/fetch Implements the S3 sync protocol layer (s3_sync.rs) that combines the shared sync_protocol with the S3 transport. Mirrors the WebDAV sync module structure with independent sync mutex, connection check, upload, download, fetch_remote_info, and sync status persistence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add S3 auto sync worker with debounce Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add S3 sync Tauri commands and auto sync worker startup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add S3 sync TypeScript types and API layer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add S3 sync i18n translations (en/zh/ja) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add S3 sync presets and dynamic form to sync settings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: preserve HTTP scheme for S3 custom endpoints (MinIO support) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add live S3 integration tests (env-var driven, --ignored) Run with: S3_TEST_AK=... S3_TEST_SK=... S3_TEST_BUCKET=... cargo test --lib services::s3::integration_tests -- --ignored Verifies test_connection, put_object, get_object, head_object, and 404 handling against a real S3 bucket using the project's own Sig V4 signing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove internal design docs before PR * fix: wire S3 auto-sync to DB hook & sync UI state on async load - P1: Add s3_auto_sync::notify_db_changed call in SQLite update_hook so S3 auto-sync worker receives DB change signals (was only wired for WebDAV, leaving S3 worker idle) - P2: Add useEffect to update syncType selector when s3Config loads asynchronously, preventing stale "webdav" default for S3 users * fix: satisfy clippy for s3 sync * fix: address s3 sync review feedback --------- Co-authored-by: Keith (via OpenClaw) <keithyt06@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Jason <farion1231@gmail.com> |
||
|
|
0960fd7179 |
fix: Claude Desktop 官方供应商添加报错 #3402 (#3405)
* fix: Claude Desktop 官方供应商添加时缺少 ANTHROPIC_BASE_URL 报错 根因:前端 mutation 为 claude-desktop 生成随机 UUID 作为 provider id, 后端 is_official_provider 通过 id 匹配跳过校验,随机 UUID 不匹配导致 走入普通 direct 模式校验并要求 ANTHROPIC_BASE_URL。 修复: - 前端:claude-desktop + category=official 时使用固定 id "claude-desktop-official" - 后端:validate_provider / validate_direct_provider / validate_proxy_provider / apply_provider_to_paths 增加 category=="official" 兜底检查 Fixes #3402 * fix: restrict Claude Desktop official provider detection * fix: add Claude Desktop official provider via seed --------- Co-authored-by: 金恩光 <enguang.jin@gmail.com> Co-authored-by: Jason <farion1231@gmail.com> |
||
|
|
41433cfab2 | Add Codex restart hint after provider switch | ||
|
|
3f59ab3746 |
Default Codex auth preservation to off (opt-in)
Flip preserve_codex_official_auth_on_switch from true to false so third-party Codex switches overwrite auth.json by default, matching the expectation that switching providers also swaps credentials. Users who rely on keeping the ChatGPT login in auth.json while on a third-party provider (for official plugins / remote login) can enable it in Settings -> Codex Authentication. The toggle field ships for the first time here (it is not in v3.16.0), so no existing settings.json holds an explicit value -- every user lands on the new default and no migration is required. Also set the flag explicitly in the preservation unit test instead of relying on the global default, keeping it valid now that the default is false. |
||
|
|
2683af57cb | Add Codex auth preservation setting | ||
|
|
bc1467db8d |
feat(usage): real-time stats refresh + fix codex sync panic on non-ASCII model names (#3027)
The usage dashboard previously only refreshed on app restart for users who don't route through the cc-switch proxy. Two issues were involved: 1. The session-sync background task panicked when a Codex model name contained non-ASCII characters (e.g. `【官】glm-5.1`), because `normalize_codex_model` sliced `&name[name.len() - 11..]` without verifying char boundaries. Once the task panicked, no session logs were imported until the app was restarted (where startup-time `rollup_and_prune` happened to flush pending data). 2. Even with sync working, the dashboard only polled every 30s and skipped polling when the window was unfocused, so freshly-imported data was invisible until the next poll or window refocus. Fixes ----- * `normalize_codex_model`: guard the 11-byte ISO-date suffix slice with `is_char_boundary` + `is_ascii` checks. ASCII-only suffix means the date-stripping logic is correct, and non-ASCII names (which can never be valid date suffixes anyway) now bypass the slice safely. * New `usage_events` module that emits `usage-log-recorded` to the frontend whenever `proxy_request_logs` actually gains a new row. Sources covered: proxy `log_request`, Claude/Codex/Gemini session sync, and startup `rollup_and_prune`. Notifications use a global `OnceLock<AppHandle>` so call sites that don't already hold an `AppHandle` (e.g. `UsageLogger`) can notify without signature churn. * 200ms debounce in `notify_log_recorded` collapses bursts (a single Codex sync importing 3000+ entries triggers ~2 emits, not 3000) so the frontend's `invalidateQueries` is never spammed. * Frontend `useUsageEventBridge` listens for the event and invalidates `usageKeys.all`. Hook is mounted only on `UsageDashboard`, so the listener is unsubscribed automatically when the user navigates away. Verification ------------ * `cargo check` passes (existing 25 dead-code warnings in `commands/misc.rs` are pre-existing and unrelated). * `tsc --noEmit` passes. * Manually verified end-to-end: a Codex sync run that imported 3145 entries produced 2 debounced emits, both logged as `emit usage-log-recorded 成功`, and the dashboard updated within ~200ms. Behaviour notes --------------- * `INSERT OR IGNORE` paths (Claude/Codex session sync) only notify when the row is actually inserted, so dedup-skipped writes don't trigger empty refreshes. * Gemini's `INSERT … ON CONFLICT … DO UPDATE` path reuses the existing `conn.changes() > 0` check and only notifies when token counts truly changed. * `rollup_and_prune` notifies once per pruning cycle (at most once per app start) so the dashboard reflects the new aggregate state. Co-authored-by: in30mn1a <in30mn1a@users.noreply.github.com> |
||
|
|
5fd3ec0d6a |
feat(i18n): add Traditional Chinese localization (#3093)
* Add Traditional Chinese localization * fix: address zh-TW formatting and token units - Format `zh-TW.json` with Prettier. - Use Traditional Chinese `萬` and `億` units for zh-TW token summaries. - Add usage formatting coverage for Traditional Chinese locale aliases. --------- Co-authored-by: Jason <farion1231@gmail.com> |
||
|
|
8cdaf90d8d |
refactor: replace JSON deep copy with deepClone helper and extract useTauriEvent hook (#3140)
* refactor: replace JSON.parse(JSON.stringify()) with structuredClone and extract useTauriEvent hook Replace all `JSON.parse(JSON.stringify())` deep copy patterns with native `structuredClone()` across production source (9 occurrences), tests (11 occurrences), and a hand-rolled `deepClone` utility in providerConfigUtils.ts. Add "ES2022" to tsconfig lib for type support. Extract a `useTauriEvent` hook to eliminate the repeated Tauri event listener boilerplate (`useEffect` + `active/disposed` flag + async `listen`) that was duplicated across App.tsx (3 listeners) and useUsageCacheBridge.ts. The hook handles async registration, race-condition guards, and cleanup automatically. * fix: add compatible deepClone helper - Add a shared deepClone helper with a structuredClone runtime guard and fallback. - Route clone call sites through the helper. - Preserve universal-provider-synced listener ordering and drop the dead-directory diff. * fix: harden Tauri event handling - Guard WebDAV sync status events against missing payloads. - Preserve settings query invalidation ordering before showing auto-sync errors. - Simplify useTauriEvent subscriptions to avoid dependency-driven re-listens. --------- Co-authored-by: zcb <zhangchongbiao@qiyuanlab.com> Co-authored-by: Jason <farion1231@gmail.com> |
||
|
|
1c82b8a3fa |
Add Chat Completions routing for Codex providers
- Add a Codex API format selector and routing badge for Chat Completions providers. - Convert Codex Responses requests to upstream Chat Completions when routing is required. - Convert Chat Completions JSON and SSE responses back to Responses format. - Keep generated Codex wire_api values on Responses for Codex compatibility. - Add i18n labels, provider metadata handling, and focused conversion tests. |
||
|
|
8786f44c5a |
Fix race condition in useEffect hooks and type assertion bug (#2827)
- Add active flag pattern to 3 useEffect hooks in App.tsx to prevent event listener leaks when component unmounts before async setup completes - Add guard check in useSettings.ts to prevent undefined from being stored in localStorage when payload.language is missing Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
83f4e1d0ad |
refactor(claude-desktop): trim duplication in proxy and switch flows
- services/proxy.rs: collapse 10 repeated `OpenCode | OpenClaw | Hermes | ClaudeDesktop` match arms into `_` fallthroughs. - claude_desktop_config.rs: extract a `with_rollback` closure shared by apply_provider_to_paths and restore_official_at_paths. - useProviderActions.ts: replace the triple-nested ternary picking the switch-success toast message with a flat let/if/else block. Net -36 lines. No behavior change; cargo test and pnpm typecheck pass. |
||
|
|
8b3ad9caf9 |
feat(claude-desktop): add 3P provider switching with proxy gateway
Adds a new ClaudeDesktop AppType that writes Claude Desktop's third-party
inference profile under configLibrary/, sharing _meta.json with other
launchers (Ollama-compatible) so cc-switch can coexist with them.
Two switch modes:
- direct: provider already exposes claude-* / anthropic/claude-* model
ids on Anthropic Messages, Claude Desktop connects to it directly.
- proxy: cc-switch's local proxy acts as the inference gateway,
presenting only claude-* route names to Claude Desktop and mapping
them to real upstream models. Required after Anthropic restricted
Claude Desktop to claude-family ids.
Backend:
- New module claude_desktop_config with snapshot/rollback, official seed
bypass, /claude-desktop/v1/{models,messages} routes, and a single
source of truth for default proxy routes.
- Gateway token persisted in SQLite, validated on every proxied request.
- get_claude_desktop_status surfaces drift signals (stale models,
missing routes, proxy stopped, base URL mismatch, missing token).
Frontend:
- Slim ClaudeDesktopProviderForm independent from ProviderForm,
controlled by a top-level appId guard.
- ProviderList banner consumes the status query (5s polling) and
renders actionable diagnostics.
- ClaudeDesktopRouteToggle in the header to start/stop the local
gateway without touching takeover state.
- Three-locale i18n synchronised.
|
||
|
|
db66348ff8 |
feat(providers): add Baidu Qianfan Coding Plan for Claude Code (#2322)
* feat(providers): add baidu qianfan coding plan presets * refactor(providers): align qianfan presets with existing format * chore(providers): narrow qianfan coding plan scope |
||
|
|
4737158439 |
feat(tray): show coding-plan usage for Kimi / Zhipu / MiniMax
|
||
|
|
cdcc423122 |
refactor(hermes): drop config health check scanner
The Hermes config.yaml schema has stabilized and users have migrated to the current provider fields, so the value of scanning for model.provider dangling references, custom_providers shape errors, v12 migration residue etc. no longer justifies the maintenance surface — and the scan produces false positives when users keep some providers under Hermes' v12+ providers: dict (Hermes' runtime merges both shapes, but CC Switch's scanner only looked at the list form). Removes the whole HermesHealthWarning type, scan_hermes_config_health command, HermesHealthBanner React component, useHermesHealth hook, warnings field on HermesWriteOutcome, and the three helper functions (yaml_as_non_empty_str, collect_mapping_string_keys, hermes_warning) that only served the scanner. Drops the matching i18n keys in zh/en/ja and the fixInWebUI button label that only the banner used. |
||
|
|
dc04165f18 |
feat(tray): show cached provider usage in the system tray menu (#2184)
* feat: add Rust-side write-through usage cache Introduce an in-memory UsageCache on AppState that the existing usage query commands populate on success. The cache is read-only to the rest of the app today; the next commit consumes it from the tray menu. - New services::usage_cache module with split maps: subscription keyed by AppType, script keyed by (AppType, provider_id). - AppType gains Eq + Hash so it can be used as a HashMap key. - commands::subscription::get_subscription_quota now takes State<AppState> and writes through on success (signature change is invisible to the frontend — Tauri injects State automatically). - commands::provider::queryProviderUsage body extracted into an inner async fn; the public command wraps it with write-through, covering Copilot, coding-plan, balance, and generic script paths uniformly. Cache is in-memory only; auto-query interval and the upcoming tray refresh action rebuild it after restarts. * feat(tray): surface cached usage in the system tray menu Read UsageCache populated by the previous commit and render it in three places, scoped to whatever TRAY_SECTIONS covers (Claude/Codex/Gemini): 1. Inline suffix on each provider submenu item "AnyProvider · 🟢 5h 18% / 7d 23%" 2. Disabled summary row per visible app under "Show Main" "Claude · Anthropic Official · 🟢 5h 18% / 7d 23%" 3. "Refresh all usage" menu item that triggers get_subscription_quota + queryProviderUsage for every applicable provider, then rebuilds the tray menu via the existing refresh_tray_menu path. Color encoding uses emoji (🟢 <70% / 🟠 70-89% / 🔴 ≥90%) since Tauri 2 tray labels are plain text. Missing cache entry leaves the label unchanged — tray never issues network requests when opened. Three new i18n-ready strings live in TrayTexts (en/zh/ja), following the existing pattern for tray text. Closes #2178. * feat(usage): bridge tray UsageCache writes to frontend React Query Why: tray hover triggers backend-only refresh that wrote to UsageCache but never notified the frontend, leaving main UI stale while tray showed fresh numbers. Emit a payload-carrying event after each cache write so React Query can setQueryData directly, keeping both views in sync without duplicate fetches. * fix(tray): skip hidden apps on hover refresh and drop stale disabled-script cache Address P2 findings from automated review on #2184: 1. refresh_all_usage_in_tray now filters TRAY_SECTIONS by settings.visible_apps before scheduling subscription/script queries, matching create_tray_menu and preventing wasted external API calls (and rate-limit/auth-error log noise) for apps the user has hidden. 2. format_usage_suffix only trusts the script cache when provider.meta.usage_script is still enabled; when a script is disabled/removed the cached suffix is now invalidated so the tray label no longer shows stale data indefinitely. * refactor: consolidate codex provider helpers and fix test semantics - Add Provider::is_codex_oauth() and Provider::codex_fast_mode_enabled() to eliminate duplicated meta extraction in claude.rs and stream_check.rs - Fix non-codex-oauth tests to pass codex_fast_mode=false (was true, harmless but semantically misleading) - Remove redundant is_dir() guard after resolve_skill_source_dir already guarantees the returned path is a directory * style: apply cargo fmt * fix(tray): reflect failed refreshes in cache and support Gemini flash-lite Follow-up to the tray usage-display feature addressing review feedback: - Write snapshots for both Ok(success:false) and Err paths in queryProviderUsage / get_subscription_quota so stale success data no longer persists across failed refreshes; the original Err is still returned to the frontend onError handler. - Include gemini_flash_lite tier in the tray summary with label "l". Matches the frontend SubscriptionQuotaFooter and keeps the worst emoji correct when lite is the highest utilization. - Add TIER_GEMINI_PRO / _FLASH / _FLASH_LITE constants in services/subscription.rs and reuse them in classify_gemini_model and sort_order. - Extract Provider::has_usage_script_enabled() to remove the duplicated meta.usage_script chain at two call sites. - Use db.get_provider_by_id in refresh_all_usage_in_tray instead of materialising the full provider map, and parallelise subscription and script futures via futures::future::join. - Narrow refresh_all_usage_in_tray to each section's effective current provider (script if enabled, else subscription when the provider is official). Hover refreshes now issue at most TRAY_SECTIONS.len() outbound requests. - Add 10 unit tests in tray::tests covering Claude/Codex h/w dispatch, Gemini p/f/l dispatch (including lite-only and lite-worst cases), and success/failure guards. --------- Co-authored-by: Jason <farion1231@gmail.com> |
||
|
|
29e87487a1 |
fix(skills): prevent duplicate imports when import button is double-clicked (#2211)
Closes #2139 Two related defects let the installed-skills count balloon when users tap the import confirm button multiple times — either deliberately or because the button is still clickable while a slow import is in flight: - The confirm button only disabled itself while `selected.size === 0`, so it stayed clickable during a pending mutation. Each extra click triggered another `importFromApps` mutation. - `useImportSkillsFromApps` appended the server response to the installed cache without deduping, so re-firing the mutation stacked the same skills into the list again. Disable the confirm (and cancel) buttons while the mutation is pending — matching the `isRestoring` / `isDeleting` pattern already used by `RestoreSkillsDialog` — and merge success payloads by `InstalledSkill.id` so repeated results overwrite rather than accumulate. The merge is extracted as a pure `mergeImportedSkills` reducer to make the behaviour unit-testable and to short-circuit on an empty payload, returning the existing reference so React Query does not notify subscribers about a no-op cache update. |
||
|
|
8e9452b7ec |
feat(hermes): launch dashboard from toolbar when Web UI is offline
When the Hermes Web UI probe fails, the toolbar entry now opens an info confirm dialog offering to run `hermes dashboard` in the user's preferred terminal. Accepting spawns it via a temp bash/batch script; `hermes dashboard` itself opens the browser once ready, so we do not poll. The Memory panel and Health banner keep the existing toast behavior. Also corrects the stale `hermes web` hint in the offline toast (the real command is `hermes dashboard`) and reorders Linux terminal detection to try `which` before stat'ing /usr/bin, /bin, /usr/local/bin. |
||
|
|
eb1cd17ab2 |
fix(header): stop auto-compact from latching after maximize
useAutoCompact cached normalWidthRef = el.scrollWidth on every non-compact resize, but per DOM spec scrollWidth === clientWidth when content fits. Maximizing the window (content no longer overflows) therefore wrote the container width into normalWidthRef, making it impossible to re-enter compact when the window was restored to its original size. Move the assignment inside the overflow branch so the cache is only written at the actual compact threshold, where scrollWidth reflects the real content width. |
||
|
|
185ac2be9b |
feat(hermes): memory enable switch + clearer migration warning copy
Replaces the greyed-out "Memory is disabled" banner with a real Switch at the top of each memory tab. Users can now toggle Hermes' memory/user blobs without leaving CC Switch; the underlying write goes through the merge-aware `set_memory_enabled`, so budgets and external-provider settings survive toggle operations. The new `useToggleHermesMemoryEnabled` mutation invalidates the limits query so the Switch state and the amber disabled-hint update in lockstep. Reworks the `schema_migrated_v12` health banner copy to match the simplified "CC Switch only manages custom_providers" posture — it now tells users to reconcile migrated dict entries via Hermes Web UI, instead of the earlier (and now inaccurate) "CC Switch reads both". |
||
|
|
b8a3534cb5 |
feat(settings): add Hermes config dir override with data-driven dispatch
Adds a dedicated Hermes row to the directory-override settings so users can point CC Switch at alternate Hermes config locations (e.g. a second profile directory for work/personal split). `get_config_dir` on the Rust side already supports hermes; this just wires up the frontend row. Wiring it through `useDirectorySettings` revealed a scaling problem: every supported app required five parallel ternary chains across `computeDefaultConfigDir`, `updateDirectory`, `browseDirectory`, `resetDirectory`, and `updateDirectoryState`. Replaces those with two lookup tables (`APP_DIRECTORY_META`, `DIRECTORY_KEY_TO_SETTINGS_FIELD`) so adding the next app is two entries, not fifteen edit sites. Drive-by cleanup from the same touch: * `resetAllDirectories` takes a `ResolvedAppDirectoryOverrides` object instead of five positional optional strings. * `setResolvedDirs` returns the same reference when the sanitized value is unchanged, so no-op edits don't cascade renders. Also lands all i18n updates for this series (`hermesConfigDir` and placeholder, Memory section's enable/disable/toggleFailed copy, and the reworded `schemaMigratedV12` warning) in zh/en/ja together. |
||
|
|
acc6d795e4 |
feat(hermes): replace Prompts entry with Memory panel
Hermes has no slash-prompt concept (templates live as Skills), so the
Prompts tab for the Hermes app was always empty. Swap the toolbar Book
button for a Brain button that opens a new Memory panel editing
~/.hermes/memories/{MEMORY,USER}.md — Hermes' first-class memory store
which its Web UI exposes only as on/off toggles, never as an editor.
The panel shows each file in its own tab with a character-budget bar
read from config.yaml's nested memory.* section (memory_char_limit /
user_char_limit, default 2200 / 1375). Edits are written atomically;
Hermes picks them up on the next session start per MemoryStore.
Also extract useDarkMode to src/hooks/useDarkMode.ts — the codebase
already repeats the same MutationObserver pattern in 12+ places; this
PR introduces the shared hook and uses it once, leaving the migration
of the other copies to a follow-up.
|
||
|
|
088b47b08a |
refactor(hermes): delegate deep config to Hermes Web UI
Slim the Hermes surface in CC Switch to match its core positioning — cross-client provider switching and shared MCP/prompts/skills — and delegate deep configuration (model, agent, env, skills, cron, logs) to the Hermes Web UI at http://127.0.0.1:9119. - Drop AgentPanel/EnvPanel/ModelPanel and their mutation commands, hooks, types, and i18n keys across zh/en/ja. - Add open_hermes_web_ui Tauri command that probes /api/status and launches the URL in the system browser. Hermes injects its own session token into the returned HTML, so CC Switch doesn't need to touch auth. - Surface the launcher from the Hermes toolbar and the health banner via a shared useOpenHermesWebUI() hook; the offline error code is defined once per side and referenced across the contract. - Keep read-only access to model.provider so ProviderList can still highlight the active supplier; apply_switch_defaults continues to write the top-level model section when switching providers. Net diff: +152 / -1253. |
||
|
|
828ec2ce07 |
fix(hermes): show active provider and wire add/enable/remove actions
Switching a Hermes provider previously only fired a toast because the frontend treated Hermes as non-additive (unlike backend AppType::is_additive_mode, which lists OpenCode | OpenClaw | Hermes) and relied on the unused is_current DB flag for highlighting. Align the UI model with the backend: - Include Hermes in ProviderActions' isAdditiveMode so the main button switches between "Add" and "Remove". - Drive the "current" highlight from model.provider (via useHermesModelConfig) instead of the DB is_current field; model.provider is Hermes's real SSOT for the active provider. - Reuse OpenClaw's set-as-default button slot to expose an "Enable" action on Hermes that calls switchProvider, so providers already in config can be activated without re-adding. switch_normal + apply_switch_defaults already atomically update custom_providers and model.provider, so no backend change is needed. - Invalidate liveProviderIds + modelConfig + health in parallel after add/update/delete/switch via a new invalidateHermesProviderCaches helper, replacing four copies of three sequential awaits. |
||
|
|
240969d8c7 |
feat: add Hermes UI components, presets, and config panels (Phase 8)
- Add 7 provider presets (OpenRouter, Anthropic, OpenAI, Google, DeepSeek, Together, Nous) - Create HermesFormFields + useHermesFormState for provider form integration - Create Model/Agent/Env config panels with save/load functionality - Create HermesHealthBanner for config warnings - Add hermes icon (violet winged H) to icon system - Integrate into App.tsx: 3 new view types (hermesModel/hermesAgent/hermesEnv), sidebar buttons (Brain/Bot/KeyRound), health banner, session support - Integrate into ProviderForm: presets, form state, key validation, rendering - Integrate into AddProviderDialog: universal tab exclusion, providerKey, base_url extraction - Add i18n keys for all Hermes UI (zh/en/ja) |
||
|
|
a0b585992a |
feat: add Hermes frontend types, API layer, and hooks (Phase 7)
- Add "hermes" to AppId union type and all exhaustive Record<AppId> - Add HermesModelConfig, HermesAgentConfig, HermesEnvConfig types - Add hermes field to VisibleApps, McpApps, ProxyTakeoverStatus - Create src/lib/api/hermes.ts with Tauri invoke wrappers - Create src/hooks/useHermes.ts with 5 query + 3 mutation hooks - Register hermes in APP_IDS, APP_ICON_MAP (violet color scheme) - Split MCP_SKILLS_APP_IDS into MCP_APP_IDS (includes hermes) and SKILLS_APP_IDS (excludes hermes, since Hermes has no Skills support) - Wire hermes additive-mode into App.tsx (remove/duplicate handlers), ProviderList.tsx (live provider ID query + In Config badge), mutations.ts (cache invalidation on switch/add/delete) - Add Hermes checkbox to McpFormModal - Add basic hermes i18n keys (en/zh/ja) |
||
|
|
701e7d9581 |
fix: surface backend error details in proxy toast messages
The takeover.failed i18n template lacked the {{detail}} placeholder
and three useProxyStatus onError callbacks omitted the detail variable,
so proxy start/stop/takeover failures all displayed a generic message
regardless of the underlying cause.
|
||
|
|
c5b15dd25e |
fix(claude-plugin): sync current provider config to settings.json (#1905)
* fix(claude-plugin): sync current provider config to settings.json on toggle enable
- Extract syncClaudePluginIfChanged to share logic between autoSaveSettings and saveSettings
- Fix P1: enableClaudePluginIntegration toggle in General tab now actually syncs ~/.claude/settings.json
- Fix P2: check syncCurrentProvidersLiveSafe() return value and show toast on failure
- Fix P3: sync providers on both enable and disable, not just enable
- Fix P4: avoid double syncCurrentProvidersLiveSafe when plugin toggle + dir change happen together
- Remove duplicate comment
- Add missing providersApi.getCurrent/getAll mocks in tests
* style: reformat after rebase onto main
Prettier flagged a line-break introduced by the openclaw directory
change (from main) after rebase.
* fix(claude-plugin): read prev enabled state from live cache to avoid stale closure
syncClaudePluginIfChanged compared enabled against data?.enableClaudePluginIntegration
captured in a useCallback closure. After invalidateQueries + refetch, the React
Query cache is up to date, but the consuming hook's closure does not see the new
value until React re-renders. Quick on->off toggles could therefore skip
applyClaudePluginConfig, leaving ~/.claude/config.json in the previously enabled
state even though settings.json was persisted as disabled.
Read the previous value synchronously from queryClient.getQueryData(["settings"])
before saveMutation.mutateAsync(), then pass it to the helper as prevEnabled.
getQueryData bypasses the closure and reflects the live cache at call time.
Test covers the race: closure data stays at false while the cache reports true;
the helper must still call applyClaudePluginConfig({ official: true }).
---------
Co-authored-by: Jason <farion1231@gmail.com>
|
||
|
|
34349a2743 |
Add OpenClaw config directory settings (#1518)
Co-authored-by: 张斌 <zhangbin25@xiaomi.com> |
||
|
|
507bf038a9 |
feat(stream-check): refresh default models and detect model-not-found errors (#2099)
* chore(stream-check): update default health check models to latest Replaces deprecated gpt-5.1-codex@low with gpt-5.4@low and switches the Gemini default from gemini-3-pro-preview to gemini-3-flash-preview to pick the lightest variant of the latest series for fast, low-cost health checks. https://claude.ai/code/session_01NGWLchcTP76rJHjiP5Ehte * feat(stream-check): detect model-not-found errors with dedicated toast Health check previously classified failures purely by HTTP status code, which meant deprecated/invalid models showed up as a generic "Not found (404)" error pointing users to check the Base URL — misleading when the URL is fine and only the test model is wrong (e.g. gpt-5.1-codex after it was retired). Backend: add detect_error_category() that inspects 4xx response bodies for model-not-found indicators (model_not_found, does not exist, invalid model, not_found_error, etc.) and returns a "modelNotFound" category. Thread the resolved test model through build_stream_check_result so the failed result carries it in model_used. Add StreamCheckResult .error_category field (serde-skipped when None). Frontend: useStreamCheck branches on errorCategory === "modelNotFound" before the HTTP-status fallback and renders a toast.error with the model name and a description pointing to Model Test Config. Add i18n keys (modelNotFound / modelNotFoundHint) for zh/en/ja. Tests: unit-test detect_error_category against real OpenAI/Anthropic error shapes, 5xx false-positive avoidance, and plain 401 auth errors. https://claude.ai/code/session_01NGWLchcTP76rJHjiP5Ehte * fix(stream-check): add missing error_category field in fallback The error_category field was added to StreamCheckResult in this branch but the fallback constructor in stream_check_all_providers was not updated, which broke cargo build. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
689ca08409 |
feat: classify stream check errors with color-coded toasts
Distinguish between "provider rejects probe" (yellow warning) and "genuinely broken" (red error) in health check results. Backend: add AppError::HttpStatus variant to carry structured HTTP status codes, populate http_status on error results, classify codes into short labels (e.g. "Auth rejected (401)"), and truncate overly long response bodies. Frontend: route 401/403/400/429/5xx to toast.warning with localized hints explaining the error may not indicate actual unusability; route 404/402/connection errors to toast.error. Add i18n keys for all three locales (zh/en/ja). Also deduplicate check_once by reusing build_stream_check_result. |
||
|
|
a514f27937 |
feat: block official provider switching during proxy takeover
Prevent users from switching to official providers (Anthropic/OpenAI/Google)
when proxy takeover is active, as using a proxy with official APIs may cause
account bans.
Defense-in-depth across 4 layers:
- Backend: ProviderService::switch(), hot_switch_provider(), switch_proxy_provider command
- Frontend: useProviderActions soft guard with error toast
- UI: ProviderActions button disabled with ShieldAlert icon
- Tray menu: official provider items disabled with ⛔ indicator
Also warns when enabling proxy takeover while current provider is official.
|
||
|
|
cfcf9452d0 |
添加应用级别窗口按钮,以改善linux wayland下系统窗口按钮失效的问题 (#1119)
* feat(window): add app-level window controls with settings toggle Add a persistent settings toggle to enable app-level minimize/maximize/close controls and hide system decorations when enabled, providing a Wayland-friendly fallback for broken native titlebar interactions. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(window): restrict app-level window controls to Linux only and fix startup flicker - Guard useAppWindowControls with isLinux() in App.tsx so it's always false on macOS/Windows even if persisted as true - Wrap set_decorations call in lib.rs with #[cfg(target_os = "linux")] - Only show the toggle in WindowSettings on Linux - Skip setDecorations effect while settingsData is still loading to prevent the Rust-side decoration state from being overridden by the undefined->false fallback, which caused a brief title bar flicker --------- Co-authored-by: wzk <wx13571681304@outlook.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Jason <farion1231@gmail.com> |
||
|
|
308314baac |
fix(session-manager): improve session search accuracy and Chinese support
- Pre-filter sessions by provider before indexing to prevent result truncation when FlexSearch limit cuts across providers - Switch tokenizer from "forward" to "full" for Chinese substring matching - Preserve FlexSearch relevance ranking when search query is present |
||
|
|
bafe9e820d |
fix(notifications): remove duplicate toast when switching to proxy providers
When switching to Copilot/ChatGPT/OpenAI-format providers with the proxy not running, two toasts appeared: a "proxy required" warning followed by a "switch success" toast. Unify the post-switch toast logic so that all provider types show a single success toast, and skip it entirely when a proxy-required warning was already shown. |
||
|
|
d51e774b20 |
feat: integrate skills.sh search for discovering skills from public registry
Add skills.sh API integration allowing users to search and install from a catalog of 91K+ agent skills directly within CC Switch. The search results are converted to DiscoverableSkill objects and reuse the existing install pipeline. Includes fallback directory search for repos where skills are nested in subdirectories, and filters out non-GitHub sources. |
||
|
|
e3179ad9e4 |
feat: add skill update detection via SHA-256 content hashing
- Add content_hash and updated_at fields to skills table (DB migration v6→v7) - Compute directory content hash on install/import/restore for version tracking - Add check_updates command: downloads repos, compares hashes, returns update list - Add update_skill command: backs up old files, re-downloads and replaces SSOT - Backfill content_hash for existing skills on first update check - Add "Check Updates" button and per-skill update badge/button in UnifiedSkillsPanel - Add i18n keys for zh/en/ja |
||
|
|
8d38f0ee4f |
fix: allow provider switch without proxy, show warning instead of blocking
Remove the hard block that prevented switching to providers requiring proxy (OpenAI format, Copilot, full URL mode) when the proxy is not running. Now the switch proceeds with a warning toast. Also deduplicate the proxy hint info toast so it doesn't appear alongside the warning. |
||
|
|
3553d05bf0 |
feat(provider): additive provider key lifecycle & fix openclaw serializer panic (#1724)
* feat(provider): support additive provider key lifecycle management
Add `addToLive` parameter to add_provider so callers can opt out of
writing to the live config (e.g. when duplicating an inactive provider).
Add `originalId` parameter to update_provider to support provider key
renames — the old key is removed from live config before the new one
is written.
Frontend: ProviderForm now exposes provider-key input for openclaw app
type, and EditProviderDialog forwards originalId on save. Deep-link
import passes addToLive=true to preserve existing behavior.
* test(provider): add integration tests for additive provider key flows
Cover openclaw provider duplication scenario to verify that a generated
provider key is assigned automatically. Add MSW handlers for
get_openclaw_live_provider_ids, get_openclaw_default_model,
scan_openclaw_config_health, and check_env_conflicts endpoints.
Update EditProviderDialog mock to pass originalId alongside provider.
* fix(openclaw): replace json-five serializer to prevent panic on empty collections
json-five 0.3.1 panics when pretty-printing nested empty maps/arrays.
Switch value_to_rt_value() to serde_json::to_string_pretty() which
produces valid JSON5 output without the panic. Add regression test for
removing the last provider (empty providers map).
* style: apply rustfmt formatting to proxy and provider modules
Reformat chained .header() calls in ClaudeAdapter and StreamCheckService
for consistent alignment. Reorder imports alphabetically in stream_check.
Fix trailing whitespace in transform.rs and merge import lines in
provider/mod.rs.
* style: fix clippy warnings in live.rs and tray.rs
* refactor(provider): simplify live_config_managed and deduplicate tolerant live config checks
- Change live_config_managed from Option<bool> to bool with #[serde(default)]
- Extract repeated tolerant live config query into check_live_config_exists helper
- Fix duplicate key generation to also check live-only provider IDs
- Fix updateProvider test to match new { provider, originalId } call signature
- Add streaming_responses test type annotation for compiler inference
* fix(provider): distinguish legacy providers from db-only when tolerating live config errors
Change `ProviderMeta.live_config_managed` from `bool` to `Option<bool>`
to introduce a three-state semantic:
- `Some(true)`: provider has been written to live config
- `Some(false)`: explicitly db-only, never written to live config
- `None`: legacy data or unknown state (pre-existing providers)
Previously, legacy providers defaulted to `live_config_managed = false`
via `#[serde(default)]`, which silently swallowed live config parse
errors. This could mask genuine configuration issues for providers that
had actually been synced to live config before the field was introduced.
Now, only providers with an explicit `Some(false)` marker tolerate parse
errors; legacy `None` providers surface errors as before, preserving
safety for already-managed configurations.
Also wrap the `ensureQueryData` call for live provider IDs during
duplication in a try/catch so that a malformed config file shows a
user-facing toast instead of silently failing.
Add tests for both the legacy error propagation path and the frontend
duplication failure scenario.
* refactor(provider): unify OMO variant updates with atomic file-then-db writes and rollback
Consolidate the duplicated omo/omo-slim update branches into a single
match on the variant. Write the OMO config file from the in-memory
provider state *before* persisting to the database, so a file-write or
plugin-sync failure leaves the database unchanged. If `add_plugin`
fails after the config file is already written, roll back to the
previous on-disk contents via snapshot/restore.
Also:
- `sync_all_providers_to_live` now skips db-only providers
(`live_config_managed == Some(false)`) instead of attempting to write
them to live config.
- `import_{opencode,openclaw}_providers_from_live` mark imported
providers as `live_config_managed: Some(true)` so they are correctly
recognized during subsequent syncs.
- Extract OmoService helpers: `profile_data_from_provider`,
`snapshot_config_file`, `restore_config_file`, `write_profile_config`,
and the new public `write_provider_config_to_file`.
- Add 9 new tests covering sync skip, legacy restore, import marking,
OMO persistence, file-write failure, and plugin-sync rollback.
* fix(provider): fix additive provider delete/switch regressions and redundancy
- fix(delete): replace stale live_config_managed flag check with
check_live_config_exists so providers written to live before the
flag-flip logic was introduced are still cleaned up on delete
- fix(switch): make write_live_with_common_config return Err instead of
silently returning Ok when config structure is invalid, preventing
live_config_managed from being incorrectly flipped to true
- fix(update): block provider key rename for OMO/OMO Slim categories to
prevent orphaned current-state markers breaking OMO file syncs
- fix(switch): flip live_config_managed to true after successful live
write for DB-only additive providers so sync_all_providers_to_live
includes them on future syncs; roll back live write if DB update fails
- refactor(delete): merge symmetric OMO/OMO-Slim blocks into single
match-on-variant path; hoist DB read to top of additive branch
- refactor(remove_from_live_config): merge OMO/OMO-Slim if/else-if
into single match-on-variant path
- refactor(switch_normal): merge two OMO/OMO-Slim if blocks into one
OpenCode guard with (enable, disable) variant pair
- fix(update): remove redundant duplicate return Ok(true) after OMO
current-state write
* fix(test): use preferred_filename after OMO field rename
The merge from main brought in #1746 which renamed
OmoVariant.filename → preferred_filename, but the test helper
omo_config_path() was not updated, breaking compilation of all
new provider tests.
---------
Co-authored-by: Jason <farion1231@gmail.com>
|
||
|
|
67e074c0a7 |
refactor(proxy): transparent header forwarding via hyper client (#1714)
* style(frontend): reformat provider forms, constants and hooks
Apply prettier formatting across 5 frontend files. No logic changes.
Changed files:
- AddProviderDialog.tsx: reformat generic type annotation and callback
- ClaudeFormFields.tsx: consolidate multi-line useState and Collapsible props
- CodexConfigSections.tsx: expand single-line React imports to multi-line,
collapse removeCodexTopLevelField() call
- constants.ts: merge TemplateType into single line
- useSkills.ts: expand single-line TanStack Query imports to multi-line,
reformat uninstallSkill mutationFn chain
* deps(proxy): add hyper ecosystem crates and manual decompression libs
reqwest internally normalizes all header names to lowercase and does not
preserve insertion order, causing proxied requests to differ from the
original client requests. To achieve transparent header forwarding with
original casing and order, introduce lower-level hyper HTTP client libs.
New dependencies:
- hyper-util 0.1: TokioExecutor + legacy Client with
preserve_header_case support for HTTP/1.1
- hyper-rustls 0.27: rustls-based TLS connector for hyper
- http 1 / http-body 1 / http-body-util 0.1: HTTP type crates for
hyper 1.x request/response construction
- flate2 1: manual gzip/deflate decompression (replaces reqwest auto)
- brotli 7: manual brotli decompression
Changed dependencies:
- serde_json: enable preserve_order feature to keep JSON field order
- reqwest: drop gzip feature to prevent reqwest from overriding the
client's original accept-encoding header
* refactor(proxy): use hyper client for header-case preserving forwarding
Previously the proxy used reqwest for all upstream requests. reqwest
normalizes header names to lowercase and reorders them internally,
making proxied requests distinguishable from direct CLI requests.
Some upstream providers are sensitive to these differences.
This commit replaces reqwest with a hyper-based HTTP client on the
default (non-proxy) path, achieving wire-level header fidelity:
Server layer (server.rs):
- Replace axum::serve with a manual hyper HTTP/1.1 accept loop
- Enable preserve_header_case(true) so incoming header casing is
captured in a HeaderCaseMap extension on each request
- Bridge hyper requests to axum Router via tower::Service
New hyper client module (hyper_client.rs):
- Lazy-initialized hyper-util Client with preserve_header_case
- ProxyResponse enum wrapping both hyper::Response and reqwest::Response
behind a unified interface (status, headers, bytes, bytes_stream)
- send_request() builds requests with ordered HeaderMap + case map
Request handlers (handlers.rs):
- Switch from (HeaderMap, Json<Value>) extractors to raw
axum::extract::Request to preserve Extensions (containing the
HeaderCaseMap from the accept loop)
- Pass extensions through the forwarding chain
Forwarder (forwarder.rs):
- Remove HEADER_BLACKLIST array; replace with ordered header iteration
that preserves original header sequence and casing
- Build ordered_headers by iterating client headers, skipping only
auth/host/content-length, and inserting auth headers at the original
authorization position to maintain order
- Handle anthropic-beta (ensure claude-code-20250219 tag) and
anthropic-version (passthrough or default) inline during iteration
- Remove should_force_identity_encoding() — accept-encoding is now
transparently forwarded to upstream
- Use hyper client by default; fall back to reqwest only when an
HTTP/SOCKS5 proxy tunnel is configured
Provider adapters (adapter.rs, claude.rs, codex.rs, gemini.rs):
- Replace add_auth_headers(RequestBuilder) -> RequestBuilder with
get_auth_headers(AuthInfo) -> Vec<(HeaderName, HeaderValue)>
- Adapters now return header pairs instead of mutating a reqwest builder
- Claude adapter: merge Anthropic/ClaudeAuth/Bearer into single branch;
move Copilot fingerprint headers into get_auth_headers
Response processing (response_processor.rs):
- Add manual decompression (gzip/deflate/brotli via flate2 + brotli)
for non-streaming responses, since reqwest auto-decompression is now
disabled to allow accept-encoding passthrough
- Add compressed-SSE warning log for streaming responses
- Accept ProxyResponse instead of reqwest::Response
HTTP client (http_client.rs):
- Disable reqwest auto-decompression (.no_gzip/.no_brotli/.no_deflate)
on both global and per-provider clients
Streaming adapters (streaming.rs, streaming_responses.rs):
- Generalize stream error type from reqwest::Error to generic E: Error
Misc:
- log_codes.rs: add SRV-005 (ACCEPT_ERR) and SRV-006 (CONN_ERR)
- stream_check.rs: reformat copilot header lines
- transform.rs: fix trailing whitespace alignment
* fix(lint): resolve 35 clippy warnings across Rust codebase
Fix all clippy warnings reported by `cargo clippy --lib`:
- codex_config.rs: fix doc_overindented_list_items (3 spaces -> 2)
- commands/copilot.rs: inline format args in 2 log::error! calls
- commands/provider.rs: inline format args in 3 map_err closures
- proxy/hyper_client.rs: inline format arg in log::debug! call
- proxy/providers/copilot_auth.rs: inline format args in 16 locations
(log macros, format! in headers, error constructors)
- proxy/thinking_optimizer.rs: inline format args in 2 log::info! calls
- services/skill.rs: inline format args in log::debug! call
- services/webdav_sync.rs: inline format args in 6 format! calls
(version compat messages, download limit messages)
- services/webdav_sync/archive.rs: inline format args in 2 format! calls
- session_manager/providers/opencode.rs: inline format args in
source_path format!
All fixes use the clippy::uninlined_format_args suggestion pattern:
format!("msg: {}", var) -> format!("msg: {var}")
* deps(proxy): add raw HTTP write and native TLS cert dependencies
Add crates required for the raw TCP/TLS write path that bypasses
hyper's header encoder to preserve original header name casing:
- httparse: parse raw TCP peek bytes to capture header casings
- tokio-rustls + rustls: direct TLS connections for raw write path
- webpki-roots: Mozilla CA bundle baseline
- rustls-native-certs: load system keychain CAs (trusts proxy MITM
certificates from Clash, mitmproxy, etc.)
* fix(proxy): address code review feedback on response handling
Fixes from PR #1714 code review:
- Extract `read_decoded_body()` and `strip_entity_headers_for_rebuilt_body()`
in response_processor to properly clean content-encoding/content-length
headers after decompression
- Reuse `read_decoded_body()` in handlers.rs for Claude transform path,
ensuring compressed responses are decoded before format conversion
- Make `build_proxy_url_from_config()` public so forwarder can pass proxy
URL to the hyper raw write path
- Add `has_system_proxy_env()` utility with test coverage
- Add 50ms backoff after accept() failures in server.rs to prevent
tight-loop CPU spin on transient socket errors
* feat(proxy): implement raw TCP/TLS write with HTTP CONNECT tunnel
Rewrite hyper_client with a two-tier strategy for header case preservation:
Primary path (raw write):
- Peek raw TCP bytes in server.rs to capture OriginalHeaderCases before
hyper lowercases them
- Build raw HTTP/1.1 request bytes with exact original header name casing
- Write directly to TLS stream, then use WriteFilter to let hyper parse
the response while discarding its duplicate request writes
- Support HTTP CONNECT tunneling through upstream proxies, so header case
is preserved even when a proxy (Clash, V2Ray) is configured
Fallback path (hyper-util Client):
- Used when OriginalHeaderCases is empty or raw write fails
- Configured with title_case_headers(true) for best-effort casing
TLS improvements:
- Load native system certificates alongside webpki roots so proxy MITM
CAs (installed in system keychain) are trusted through CONNECT tunnels
Key types added:
- OriginalHeaderCases: maps lowercase name → original wire-casing bytes
- WriteFilter<S>: AsyncRead+AsyncWrite wrapper that discards writes
- connect_via_proxy(): HTTP CONNECT tunnel establishment
- ExtensionDebugMarker: diagnostic marker for extension chain debugging
* refactor(proxy): route requests through hyper with proxy-aware forwarding
Rework forwarder request dispatch to always prefer the hyper raw write
path (header case preservation) over reqwest:
Request routing:
- HTTP/HTTPS proxy: hyper raw write through CONNECT tunnel (case preserved)
- SOCKS5 proxy: reqwest fallback (CONNECT not supported for SOCKS5)
- No proxy: hyper raw write direct connection
Header handling improvements:
- Replace host header in-place at original position instead of
skip-and-append, preserving client's header ordering
- Preserve client's original accept-encoding for transparent passthrough;
only force identity encoding when transform path needs decompression
- Add should_force_identity_encoding() to centralize the decision
- Remove hardcoded 'br, gzip, deflate' override that masked client values
Proxy URL resolution (priority order):
1. Provider-specific proxy config (if enabled)
2. Global proxy URL configured in CC Switch
3. Direct connection (no proxy)
* chore(proxy): remove dead code, redundant tests and debug scaffolding
- Inline should_force_identity_encoding() (was just `needs_transform`)
and delete its 5 test cases
- Remove ExtensionDebugMarker diagnostic type
- Remove unused has_system_proxy_env() and its test
- Remove strip_entity_headers test
- Simplify hyper path: remove redundant is_socks_proxy ternary
- Update hyper_client module doc to reflect CONNECT tunnel support
* fix(proxy): block direct-connect fallback and complete CONNECT tunnel support
* feat(hooks): improve proxy requirement warnings with specific reasons
- Remove redundant OpenAI format hint toast messages
- Add detailed reason detection for proxy requirements (OpenAI Chat, OpenAI Responses, full URL mode)
- Update i18n files with new reason-specific keys
* style(*): format code with prettier
- Remove extra whitespace in http_client.rs
- Fix formatting issues in useProviderActions.ts
* fix(proxy): post-merge fixes for forward return type and clippy warnings
- Restore forward() return type to (ProxyResponse, Option<String>)
to pass claude_api_format through to callers
- Inline format args in log::warn! macro (clippy::uninlined_format_args)
- Suppress too_many_arguments for check_claude_stream
* refactor(proxy): preserve original header wire order and add non-streaming body timeout
- Rewrite build_raw_request to emit headers in original
client-sent sequence instead of hash-map order
- Remove unused OriginalHeaderCases::get_all method
- Add body_timeout to read_decoded_body to prevent
requests hanging when upstream stalls after headers
|
||
|
|
8d5f72757e |
fix: 修复 Copilot 作为 Claude 时 OpenAI 模型的 Responses 分流 (#1735)
* fix: route copilot claude openai models to responses * fix(i18n): add copilotProxyHint translation key for all locales The copilotProxyHint message was using inline defaultValue with Chinese text, which would show Chinese to English and Japanese users. Added proper translation keys in zh/en/ja locale files and removed the hardcoded defaultValue fallback. --------- Co-authored-by: Jason <farion1231@gmail.com> |
||
|
|
8cae7b7b73 |
feat(proxy): add full URL mode and refactor endpoint rewriting (#1561)
* feat(proxy): add full URL mode and refactor endpoint rewriting - Add `isFullUrl` provider meta to treat base_url as complete API endpoint - Remove hardcoded `?beta=true` from Claude adapter, pass through from client - Refactor forwarder endpoint rewriting with proper query string handling - Block provider switching when proxy is required but not running - Add full URL toggle UI in endpoint field with i18n (zh/en/ja) * refactor(proxy): remove beta query handling * fix(proxy): strip beta query when rewriting Claude endpoints * feat(codex): complete full URL support * refactor(ui): refine full URL endpoint hint |
||
|
|
8aa6ec784b |
feat(skills): 优化技能安装/卸载的缓存更新策略 (#1573)
- 修改安装、卸载、导入、ZIP安装等操作的缓存更新逻辑,从invalidateQueries改为直接setQueryData - 为已安装和可发现技能查询添加keepPreviousData和staleTime: Infinity配置 - 修复会话管理页面布局滚动问题,添加min-h-0防止内容溢出 |
||
|
|
8ccfbd36d6 |
feat(copilot): add GitHub Copilot reverse proxy support (#930)
* refactor(toolsearch): replace binary patch with ENABLE_TOOL_SEARCH env var toggle
- Remove toolsearch_patch.rs binary patching mechanism (~590 lines)
- Delete `toolsearch_patch.rs` and `commands/toolsearch.rs`
- Remove auto-patch startup logic and command registration from lib.rs
- Remove `tool_search_bypass` field from settings.rs
- Remove frontend settings ToggleRow, useSettings hook sync logic, and API methods
- Clean up zh/en/ja i18n keys (notifications + settings)
- Add ENABLE_TOOL_SEARCH toggle to Claude provider form
- Add checkbox in CommonConfigEditor.tsx (alongside teammates toggle)
- When enabled, writes `"env": { "ENABLE_TOOL_SEARCH": "true" }`
- When disabled, removes the key; takes effect on provider switch
- Add zh/en/ja i18n key: `claudeConfig.enableToolSearch`
Claude Code 2.1.76+ natively supports this env var, eliminating the need for binary patching.
* feat(claude): add effortLevel high toggle to provider form
- Add "high-effort thinking" checkbox to Claude provider config form
- When checked, writes `"effortLevel": "high"`; when unchecked, removes the field
- Add zh/en/ja i18n translations
* refactor(claude): remove deprecated alwaysThinking toggle
- Claude Code now enables extended thinking by default; alwaysThinkingEnabled is a no-op
- Thinking control is now handled via effortLevel (added in prior commit)
- Remove state, switch case, and checkbox UI from CommonConfigEditor
- Clean up alwaysThinking i18n keys across zh/en/ja locales
* feat(opencode): add setCacheKey: true to all provider presets
- Add setCacheKey: true to options in all 33 regular presets
- Add setCacheKey: true to OPENCODE_DEFAULT_CONFIG for custom providers
- Exclude 2 OMO presets (Oh My OpenCode / Slim) which have their own config mechanism
Closes #1523
* fix(codex): resolve 1M context window toggle causing MCP editor flicker
- Add localValueRef to short-circuit duplicate CodeMirror updateListener callbacks,
breaking the React state → CodeMirror → stale onChange → React state feedback loop
- Use localValueRef.current in handleContextWindowToggle and handleCompactLimitChange
to avoid stale closure reads
- Change compact limit input from type="number" to type="text" with inputMode="numeric"
to remove unnecessary spinner buttons
* feat(codex): add 1M context window toggle utilities and i18n keys
- Add extractCodexTopLevelInt, setCodexTopLevelInt, removeCodexTopLevelField
TOML helpers in providerConfigUtils.ts
- Add i18n keys for contextWindow1M, autoCompactLimit in zh/en/ja locales
* feat(claude): collapse model mapping fields by default
- Wrap 5 model mapping inputs in a Collapsible, collapsed by default
- Auto-expand when any model value is present (including preset-filled)
- Show hint text when collapsed explaining most users need no config
- Add zh/en/ja i18n keys for toggle label and collapsed hint
- Use variant={null} to avoid ghost button hover style clash in dark mode
* feat(claude): merge advanced fields into single collapsible section
- Merge API format, auth field, and model mapping into a unified "Advanced Options" collapsible
- Extend smart-expand logic to detect non-default values across all advanced fields
- Preserve model mapping sub-header and hint with a separator line
- Update zh/en/ja i18n keys (advancedOptionsToggle, advancedOptionsHint, modelMappingLabel, modelMappingHint)
* feat(copilot): add GitHub Copilot reverse proxy support
Add GitHub Copilot as a Claude provider variant with OAuth device code
authentication and Anthropic ↔ OpenAI format transformation.
Backend:
- Add CopilotAuthManager for GitHub OAuth device code flow
- Implement Copilot token auto-refresh (60s before expiry)
- Persist GitHub token to ~/.cc-switch/copilot_auth.json
- Add ProviderType::GitHubCopilot and AuthStrategy::GitHubCopilot
- Modify forwarder to use /chat/completions for Copilot
- Add Copilot-specific headers (Editor-Version, Editor-Plugin-Version)
Frontend:
- Add CopilotAuthSection component for OAuth UI
- Add useCopilotAuth hook for OAuth state management
- Auto-copy user code to clipboard and open browser
- Use 8-second polling interval to avoid GitHub rate limits
- Skip API Key validation for Copilot providers
- Add GitHub Copilot preset with claude-sonnet-4 model
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* fix(copilot): remove is_expired() calls from tests
Remove references to deleted is_expired() method in test code.
Only is_expiring_soon() is needed for token refresh logic.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* feat(copilot): add real-time model listing from Copilot API
- Add fetch_models() to CopilotAuthManager calling GET /models endpoint
- Add copilot_get_models Tauri command
- Add copilotGetModels() frontend API wrapper
- Modify ClaudeFormFields to show model dropdown for Copilot providers
- Fetches available models on component mount when isCopilotPreset
- Groups models by vendor (Anthropic, OpenAI, Google, etc.)
- Input + dropdown button combo allows both manual entry and selection
- Non-Copilot providers keep original plain Input behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(copilot): add usage query integration
- Add Copilot usage API integration (fetch_usage method)
- Add copilot_get_usage Tauri command
- Add GitHub Copilot template in usage query modal
- Unify naming: copilot → github_copilot
- Add constants management (TEMPLATE_TYPES, PROVIDER_TYPES)
- Improve error handling with detailed error messages
- Add database migration (v5 → v6) for template type update
- Add i18n translations (zh, en, ja)
- Improve type safety with TemplateType
- Apply code formatting (cargo fmt, prettier)
* 修复github 登录和注销问题 ,模型选择问题
* feat(copilot): add multi-account support for GitHub Copilot
- Add multi-account storage structure with v1 to v2 migration
- Add per-account token caching and auto-refresh
- Add new Tauri commands for account management
- Integrate account selection in Proxy forwarder
- Add account selection UI in CopilotAuthSection
- Save githubAccountId to ProviderMeta
- Add i18n translations for multi-account features (zh/en/ja)
* 修复用量查询Reset字段出现多余字符
* refactor(auth-binding): introduce generic provider auth binding primitives
- add shared authBinding types in Rust and TypeScript while keeping githubAccountId as a compatibility field\n- resolve Copilot token, models, and usage through provider-bound account lookup instead of only the implicit default account\n- fix the Unix build regression in settings.rs by restoring std::io::Write for write_all()\n- remove the accidental .github ignore entry and drop leftover Copilot form debug logs\n- keep the first migration step non-breaking by writing both authBinding and the legacy githubAccountId field from the form
* refactor(auth-service): add managed auth command surface and explicit default account state
- introduce generic managed auth commands and frontend auth API wrappers for provider-scoped login, status, account listing, removal, logout, and default-account selection\n- store an explicit Copilot default_account_id instead of relying on HashMap iteration order, and use it consistently for fallback token/model/usage resolution\n- sort managed accounts deterministically and surface default-account state to the UI\n- refactor the Copilot form hook to wrap a generic useManagedAuth implementation while preserving the existing component contract\n- add default-account controls to the Copilot auth section and extend Copilot auth status serialization/tests for the new state
* feat(auth-center): add a dedicated settings entrypoint for managed OAuth accounts
- add an Auth Center tab to Settings so managed OAuth accounts are no longer hidden inside individual provider forms\n- introduce a first AuthCenterPanel that hosts GitHub Copilot account management as the initial managed auth provider\n- keep the provider form experience intact while establishing a global account-management surface for future providers such as OpenAI\n- validate that the new settings tab works cleanly with the generic managed auth hook and existing Copilot account controls
* feat(add-provider): expose managed OAuth sources alongside universal providers
- add an OAuth tab to the Add Provider flow so managed auth sources sit beside app-specific and universal providers\n- reuse the new Auth Center panel inside the dialog, keeping account management discoverable during provider creation\n- make the dialog footer adapt to the OAuth tab so account setup does not pretend to create a provider directly\n- align the add-provider UX with the new architecture where OAuth accounts are global assets and providers bind to them later
* fix(auth-reliability): harden managed auth persistence and refresh behavior
- replace direct Copilot auth store writes with private temp-file writes and atomic rename semantics, and document the local token storage limitation\n- add per-account refresh locks plus a double-check path so concurrent requests do not stampede GitHub token refresh\n- surface legacy migration failures through auth status, expose them in the UI, and add translated copy for the new account-state labels\n- stop writing the legacy githubAccountId field from the provider form while keeping compatibility reads in place\n- add logout error recovery and Copilot model-load toasts so auth failures are no longer silently swallowed
* refactor(copilot-detection): prefer provider type before URL fallbacks
- update forwarder endpoint rewriting to treat providerType as the primary GitHub Copilot signal\n- keep githubcopilot.com string matching only as a compatibility fallback for older provider records without providerType\n- reduce one more path where Copilot behavior depended purely on URL heuristics
* fix(copilot-auth): add cancel button to error state in CopilotAuthSection
- 错误状态下仅有"重试"按钮,用户无法退出(如不可恢复的 403 未订阅错误)
- 新增"取消"按钮,复用已有的 cancelAuth 逻辑重置为 idle 状态
* 修复打包后github账号头像显示异常
* 修复github copilot 来源的模型测试报错
* feat(copilot-preset): add default model presets for GitHub Copilot
- 补充 Copilot 预设的默认模型配置,用户选完预设即可直接使用
- ANTHROPIC_MODEL: claude-opus-4.6
- ANTHROPIC_DEFAULT_HAIKU_MODEL: claude-haiku-4.5
- ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4.6
- ANTHROPIC_DEFAULT_OPUS_MODEL: claude-opus-4.6
---------
Co-authored-by: Jason <farion1231@gmail.com>
Co-authored-by: 周梦泽 <mengze.zhou@dafeng-tech.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
|
||
|
|
333c9f277b |
feat(skills): add restore and delete for skill backups
Introduce list/restore/delete commands for skill backups created during uninstall. Restore copies files back to SSOT, saves the DB record, and syncs to the current app with rollback on failure. Delete removes the backup directory after a confirmation dialog. ConfirmDialog gains a configurable zIndex prop to support nested dialog stacking. |