Commit Graph

922 Commits

  • i18n(zh): unify Skills terminology in settings labels
    Use "Skills" consistently in skillStorage title/description and
    skillSync title to match the upstream Agent Skills wording and the
    existing English label style used elsewhere on the settings page.
  • refactor: tighten OAuth Auth Center copy, layout, and icon
    - Trim Auth Center section descriptions to focus on user intent
    - Remove duplicate outer heading on the auth settings tab
    - Swap Sparkles glyph for CodexIcon on the ChatGPT card
    - Generalize codexOauth.authStatus to a neutral "Auth status"
    - Register settings.authCenter.* keys across zh/en/ja locales
  • feat: display subscription quota for Codex OAuth provider cards
    Codex OAuth (ChatGPT Plus/Pro) providers previously fell through to the
    default UsageFooter branch and showed no quota at all, while Copilot and
    official Codex providers already had a wham/usage-backed quota footer.
    
    This wires up the same five-hour / seven-day tier badges for codex_oauth
    provider cards by reusing the existing query_codex_quota function and
    SubscriptionQuotaFooter rendering, parameterized to keep both the CLI
    credential path ("codex") and the cc-switch managed OAuth path
    ("codex_oauth") working from a single source of truth.
    
    - Parameterize services::subscription::query_codex_quota with tool_label
      and expired_message; promote SubscriptionQuota constructors to
      pub(crate). The CLI path keeps its existing "codex" label and the
      "re-login with Codex CLI" message; the new path passes "codex_oauth"
      and a cc-switch-specific re-login hint.
    - Add a new get_codex_oauth_quota Tauri command in commands/codex_oauth.rs
      that resolves the ChatGPT account (explicit binding > default account
      > not_found), pulls a valid access_token from CodexOAuthManager
      (auto-refresh handled), and delegates to query_codex_quota.
    - Extract SubscriptionQuotaFooter's render body into a pure
      SubscriptionQuotaView component (props: quota / loading / refetch /
      appIdForExpiredHint / inline). The existing SubscriptionQuotaFooter
      becomes a thin wrapper with identical props and behavior, so
      CopilotQuotaFooter and the official Claude/Codex/Gemini paths are
      untouched. This avoids duplicating ~280 lines of five-state rendering.
    - Add CodexOauthQuotaFooter, a 38-line wrapper that calls the new
      useCodexOauthQuota hook and forwards to SubscriptionQuotaView.
    - ProviderCard inserts an isCodexOauth branch between isCopilot and
      isOfficial, keyed off PROVIDER_TYPES.CODEX_OAUTH (newly added to
      config/constants.ts to centralize the previously scattered string).
    - Frontend hook caches per (codex_oauth, accountId) so multiple cards
      bound to the same ChatGPT account share one fetch via react-query
      dedup; cards bound to different accounts get independent fetches.
    - No new i18n keys: existing subscription.fiveHour / sevenDay / expired /
      refresh / queryFailed / expiredHint are reused.
  • feat: add Codex OAuth (ChatGPT Plus/Pro) reverse proxy support
    Adds a new managed OAuth provider that lets Claude Code route requests
    through a user's ChatGPT Plus/Pro subscription via the chatgpt.com
    backend-api/codex endpoint.
    
    - CodexOAuthManager: OpenAI Device Code flow with multi-account support,
      JWT-based account identification, and automatic access_token refresh.
    - Reuses the generic managed-auth command surface (auth_start_login,
      auth_poll_for_account, etc.) via provider dispatch in commands/auth.rs.
    - ClaudeAdapter detects codex_oauth providers, forces the base URL to
      the ChatGPT backend, pins api_format to openai_responses, and emits
      Authorization + originator headers; the forwarder injects the dynamic
      access_token and ChatGPT-Account-Id per request.
    - transform_responses gains an is_codex_oauth path that aligns the body
      with OpenAI's codex-rs ResponsesApiRequest contract: sets store:false,
      appends reasoning.encrypted_content to include, strips max_output_tokens
      / temperature / top_p, injects default instructions/tools/parallel_tool_calls,
      and forces stream:true. Covered by 9 new unit tests plus regression
      guards for the non-Codex path.
    - Stream check reuses the same transform flag so detection matches the
      production request shape.
    - Frontend adds CodexOAuthSection + useCodexOauth hook, integrates it
      into ClaudeFormFields / ProviderForm / AuthCenterPanel, ships a new
      "Codex (ChatGPT Plus/Pro)" preset, and adds zh/en/ja i18n strings.
  • fix: resolve rustfmt formatting and clippy warnings
    - Apply cargo fmt across schema.rs, session_usage*.rs, skill.rs, usage_stats.rs
    - Fix clippy::for_kv_map: use messages.values() instead of (_, msg) pattern
    - Suppress clippy::only_used_in_recursion for intentional recursive base path
    - Fix prettier formatting in UsageScriptModal.tsx
  • feat: display Copilot premium interactions quota on provider card
    Copilot usage query API was implemented but never surfaced on the main
    provider list. Add CopilotQuotaFooter component that auto-detects
    github_copilot providers and displays premium interaction utilization
    inline, reusing the existing TierBadge UI from SubscriptionQuotaFooter.
  • feat: add per-app usage filtering (Claude/Codex/Gemini)
    Add dashboard-level app type filter to usage statistics, replacing the
    DataSourceBar with a more useful segmented control. All components
    (summary cards, trend chart, provider stats, model stats, request logs)
    now respond to the selected app filter.
    
    Backend: add optional app_type parameter to get_usage_summary,
    get_daily_trends, get_provider_stats, and get_model_stats queries.
    Frontend: new AppTypeFilter type, updated query keys with appType
    dimension for proper cache separation, and RequestLogTable local
    filter auto-locks when dashboard filter is active.
  • feat: add Gemini CLI session log usage tracking
    Parse ~/.gemini/tmp/*/chats/session-*.json for precise per-message
    token data (input/output/cached/thoughts). Integrates with existing
    background sync and manual sync button alongside Claude and Codex.
  • feat: replace Codex estimated usage with precise JSONL session log parsing
    Replace the 70/30 input/output token estimation from state_5.sqlite
    with precise parsing of Codex CLI JSONL session logs (~/.codex/sessions/).
    
    - Parse event_msg (token_count), turn_context, and session_meta events
    - Compute exact input/output/cached token deltas from cumulative totals
    - Reuse session_log_sync table for incremental file scanning
    - Pre-filter lines with string contains() before JSON deserialization
    - Add codex_session data source to DataSourceBar with i18n (zh/en/ja)
  • feat: add session log usage tracking without proxy
    Parse Claude Code JSONL session files (~/.claude/projects/) and Codex
    SQLite database (~/.codex/state_5.sqlite) to track API usage without
    requiring proxy interception. This enables usage statistics for users
    who don't use the proxy feature.
    
    Key changes:
    - Add session_usage.rs: incremental JSONL parser with message.id dedup
    - Add session_usage_codex.rs: import thread-level token data from Codex
    - Add data_source column to proxy_request_logs (proxy/session_log/codex_db)
    - Add session_log_sync table for tracking parse offsets
    - Background sync every 60s + manual sync via DataSourceBar UI
    - Schema migration v7→v8
    - i18n support for zh/en/ja
  • fix: hide empty description and fix broken skill link for skills.sh results
    - Hide "暂无描述" text when skill has no description (skills.sh API
      doesn't return descriptions), show empty spacer instead
    - Change skills.sh result link from guessed subdirectory path to repo
      root URL, since skillId doesn't reflect the actual nested path
  • 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.
  • feat: add skill storage location toggle between CC Switch and ~/.agents/skills
    Allow users to choose between storing skills in CC Switch's managed
    directory (~/.cc-switch/skills/) or the Agent Skills open standard
    directory (~/.agents/skills/). Includes migration logic that safely
    moves files before updating settings, with confirmation dialog for
    non-empty installations.
  • fix: animate "Update All" button sliding in from the left of "Check Updates"
    Use max-width + opacity CSS transition so the button smoothly expands
    into view instead of popping in abruptly.
  • feat: add "Update All" button for batch skill updates
    Show an "Update All (N)" button next to "Check Updates" when updates
    are available. Sequentially updates each skill and reports results.
  • 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
  • fix: set default auto-query interval to 5min and fix number input clearing
    - Change default autoQueryInterval from 0 (disabled) to 5 minutes for
      new usage scripts (Token Plan, Balance, and general templates)
    - Fix controlled number inputs (timeout & interval) that couldn't be
      cleared: defer validation to onBlur so users can delete and retype
  • fix: align usage display across provider cards by always rendering action buttons
    Test and ConfigureUsage buttons are now always rendered instead of
    conditionally, with a disabled style for providers that don't support
    them (e.g. official subscriptions). This ensures consistent button
    container width so the usage display aligns uniformly across all cards.
  • feat: add official balance query for DeepSeek, StepFun, SiliconFlow, OpenRouter, Novita AI
    Add a new "Official" (官方) template type in the usage query panel that
    queries account balance via each provider's native API endpoint.
    Follows the same zero-script pattern as Token Plan — Rust handles the
    HTTP call, frontend auto-detects the provider from base URL.
    
    Supported providers and endpoints:
    - DeepSeek: GET /user/balance
    - StepFun: GET /v1/accounts
    - SiliconFlow: GET /v1/user/info (cn + com)
    - OpenRouter: GET /api/v1/credits
    - Novita AI: GET /v3/user/balance
  • fix: add padding to toolbar container to prevent add button shadow clipping (#1951)
    The orange add button's circular shadow appeared square because the
    parent overflow-x-hidden container was clipping it at the edges.
  • fix: correct MiniMax quota calculation and improve Token Plan display
    - Fix MiniMax usage_count being treated as remaining (was inverted)
    - Add MiniMax weekly quota tier extraction
    - Remove Zhipu TIME_LIMIT (tools usage), keep only TOKENS_LIMIT
    - Improve Kimi parsing with extract_reset_time and parse_f64 helpers
    - Reuse TierBadge for Token Plan inline rendering
    - Clean up unused i18n keys and debug println
  • feat: add Token Plan quota query for Kimi, Zhipu GLM, and MiniMax
    Add a new "Token Plan" template type in the usage query panel that
    natively queries quota/usage from Chinese coding plan providers
    (Kimi For Coding, Zhipu GLM, MiniMax) without requiring custom scripts.
    
    - Rust backend: new coding_plan service with provider-specific API
      queries (Kimi /v1/usages, Zhipu /api/monitor/usage/quota/limit,
      MiniMax /coding_plan/remains) normalized into UsageResult
    - Frontend: Token Plan template in UsageScriptModal with auto-detection
      of provider based on ANTHROPIC_BASE_URL pattern matching
    - Follows the same pattern as GitHub Copilot template (dedicated API
      path in queryProviderUsage, no JS script needed)
  • 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.
  • feat: restore Copilot preset and auth center tab in settings
    Re-enable GitHub Copilot provider preset and the OAuth auth center tab
    that were temporarily hidden due to abnormal consumption rates. The
    Copilot optimizer introduced in the previous commit addresses the
    underlying issue.
  • fix: remove hover push animation on provider cards
    Keep usage display fixed in place and show action buttons with
    a simple opacity fade instead of sliding in and pushing content.
  • fix: hide usage config and health check buttons for official providers
    Official providers use built-in subscription quota display instead of
    custom usage scripts, and stream check is not applicable. Hide both
    action buttons when isOfficialProvider is true.
  • feat: add official subscription quota display for Gemini
    - Read Gemini OAuth credentials from macOS Keychain (gemini-cli-oauth)
      or legacy file (~/.gemini/oauth_creds.json)
    - Auto-refresh expired access tokens using refresh_token (Google OAuth
      tokens expire in ~1h, unlike Claude/Codex)
    - Two-step API: loadCodeAssist for project ID, then retrieveUserQuota
      for per-model quota buckets
    - Classify models into Pro/Flash/Flash Lite categories, show min
      remaining fraction as utilization percentage
    - Extend isOfficialProvider() for Gemini (no API key + no base URL)
    - Parameterize expiredHint i18n key with tool name for all three apps
  • feat: add official subscription quota display for Codex
    Read Codex OAuth credentials from ~/.codex/auth.json (with macOS
    Keychain fallback) and query chatgpt.com/backend-api/wham/usage to
    show rate limit utilization on official Codex provider cards. Reuses
    the same tier naming (five_hour, seven_day) for frontend i18n compat.
  • feat: display official subscription quota on Claude provider cards
    Read Claude OAuth credentials from macOS Keychain (with file fallback)
    and query the Anthropic usage API to show quota utilization inline on
    official provider cards. Includes compact countdown timer for reset
    windows and hides the rarely-used seven_day_sonnet tier in inline mode.
  • feat: differentiate fetch models error messages by failure type
    Distinguish between missing API key, missing endpoint, auth failure,
    unsupported provider (404/405), and timeout errors instead of showing
    a generic failure toast for all cases.
  • feat: add auto-fetch models from provider's /v1/models endpoint
    Add ability to fetch available models from third-party aggregation
    providers (SiliconFlow, OpenRouter, etc.) via OpenAI-compatible
    GET /v1/models endpoint. Users can click "Fetch Models" button in
    the provider form, then select models from a dropdown on each
    model input field.
    
    - Backend: new model_fetch service + Tauri command (Rust)
    - Frontend: ModelInputWithFetch shared component
    - Integrated into all 5 app forms (Claude/Codex/Gemini/OpenCode/OpenClaw)
    - i18n support for zh/en/ja
  • fix(copilot): 修复 GitHub Copilot 认证和代理问题 (#1854)
    * fix(copilot): 修复 GitHub Copilot 400 认证错误
    
    问题:使用 GitHub Copilot provider 时报错 400 bad request
    
    根因:与 copilot-api 项目对比发现多处差异
    
    修复内容:
    - 更新版本号 0.26.7 到 0.38.2
    - 更新 API 版本 2025-04-01 到 2025-10-01
    - 添加缺失的关键 headers
    - 修正 openai-intent 值
    - 添加动态 API endpoint 支持
    - 同步更新 stream_check.rs headers
    
    Closes #1777
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * fix: flush stream after write_all in hyper_client proxy
    
    Add explicit flush() calls after write_all() for TLS stream, plain TCP
    stream, and CONNECT tunnel requests to ensure buffered data is sent
    immediately, preventing connection hangs in Copilot auth header flow.
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    * 修复登录时的剪切板在mac与linux端可能没复制验证码
    
    * fix: flush stream after write_all in hyper_client proxy
    
    Add explicit flush() calls after write_all() for TLS stream, plain TCP
    stream, and CONNECT tunnel requests to ensure buffered data is sent
    immediately, preventing connection hangs in Copilot auth header flow.
    
    * 修复登录时的剪切板在mac与linux端可能没复制验证码
    
    * 1、修复不同类型的个人商业等不同类型的copilot账号问题
    2、将验证码复制改为异步操作
    
    * fix: address PR review comments for Copilot auth                                                      │
    │                                                                                                                      │
    │ - Fix clipboard blocking by using spawn_blocking for arboard ops                                                     │
    │ - Implement dynamic endpoint routing for enterprise Copilot users                                                    │
    │ - Add api_endpoints cache cleanup in remove_account() and clear_auth()                                               │
    │ - Change API endpoint log level from info to debug                                                                   │
    │ - Fix clear_auth() to continue cleanup even if file deletion fails                                                   │
    │ - Add 9 unit tests for Copilot detection and api_endpoints cachin
    
    * style: fix cargo fmt formatting
    
    * Fix Copilot dynamic endpoint handling
    
    * fix: restore clear_auth() memory-first cleanup order and fix cache leaks
    
    - Restore clear_auth() to clean memory state before deleting the storage
      file. The previous order (file deletion first) caused a regression where
      users could get stuck in a "cannot log out" state if file removal failed.
    
    - Add missing copilot_models.clear() in clear_auth() — this cache was
      cleaned in remove_account() but never in the full clear path.
    
    - Add endpoint_locks cleanup in both remove_account() and clear_auth()
      to prevent minor in-process memory leaks.
    
    - Update test to assert the correct behavior: memory should be cleaned
      even when file deletion fails.
    
    ---------
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    Co-authored-by: 周梦泽 <mengze.zhou@dafeng-tech.com>
    Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
    Co-authored-by: Jason <farion1231@gmail.com>
  • feat: hide GitHub Copilot provider preset and auth tab
    Users reported that Copilot support causes excessively fast token
    consumption. Temporarily hide the feature by adding a `hidden` field
    to ProviderPreset interface and commenting out the auth center tab
    in settings. Existing Copilot providers in DB still work via proxy.
  • 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>
  • fix(omo): adapt to oh-my-openagent rename with backward compatibility (#1746)
    * fix(omo): adapt to oh-my-openagent rename with backward compatibility
    
    Closes https://github.com/farion1231/cc-switch/issues/1733
    
    * fix(omo): prioritize oh-my-openagent config over legacy oh-my-opencode
  • fix(provider): show persistent config highlight for additive-mode providers (#1747)
    * fix(provider): show persistent config highlight for additive-mode providers
    
    Closes https://github.com/farion1231/cc-switch/issues/1692
    
    * fix(provider): limit persistent config highlight to opencode only
  • feat(terminal): add directory picker before launching Claude terminal (#1752)
    * Add directory picker before launching Claude terminal
    
    * fix(terminal): preserve cwd path and strip Windows verbatim prefix
    
    - Stop trimming non-empty paths so directories with leading/trailing
      spaces on Unix are handled correctly
    - Strip \\?\ extended-length prefix from canonicalized paths on Windows
      to prevent batch script cd failures
    
    * fix(terminal): restore UNC paths when stripping Windows verbatim prefix
    
    Handle \\?\UNC\server\share form separately from regular \\?\ prefix,
    converting it back to \\server\share so network/WSL directory paths
    remain valid in batch cd commands.
    
    * fix(terminal): use pushd for UNC paths in Windows batch launcher
    
    `cmd.exe` cannot set a UNC path (e.g. `\\wsl$\...`) as the current
    directory via `cd /d`; it errors with "CMD does not support UNC paths
    as current directories". Switch to `pushd` which temporarily maps the
    UNC share to a drive letter.
    
    Rename `build_windows_cd_command` → `build_windows_cwd_command` to
    reflect the broader semantics. Extract `build_windows_cwd_command_str`
    and `is_windows_unc_path` helpers for testability, and add unit tests
    covering drive paths, UNC paths, and batch metacharacter escaping.
    
    Also fix minor style issues: sort mod declarations alphabetically,
    add missing EOF newline in lightweight.rs, add explicit type annotation
    in streaming_responses test, and reformat tray menu builder chain.
  • feat: add bulk delete for session manager (#1693)
    * feat: add bulk delete for session manager
    
    * fix: address batch delete review issues
    
    * fix: keep session list in sync after batch delete
  • fix: preserve WebDAV password display and validate MKCOL 405 (#1685)
    * fix: preserve WebDAV password display and validate MKCOL 405
    
    * fix: scope WebDAV password preservation to post-save refresh
  • 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
  • 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>
  • 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
  • feat(opencode): add StepFun Step Plan provider preset (#1668)
    Add StepFun Step Plan (阶跃星辰编程计划) as an OpenCode provider preset.
    
    StepFun Step Plan is a subscription-based coding AI service that uses a
    dedicated API endpoint separate from the standard StepFun provider.
    
    - Base URL: https://api.stepfun.com/step_plan/v1
    - Model: step-3.5-flash (196B MoE, optimized for agent and coding tasks)
    - Category: cn_official
    - Supports tool_call, reasoning, temperature control
    
    Ref: https://platform.stepfun.com/docs/zh/step-plan/overview
    Made-with: Cursor
    
    Co-authored-by: sky-wang-salvation <sky-wang-salvation@users.noreply.github.com>
  • feat(claude): add "Disable Auto-Upgrade" checkbox to provider config editor
    Add a toggle for DISABLE_AUTOUPDATER env var in CommonConfigEditor,
    following the same pattern as the existing Teammates mode toggle.
  • fix: prevent WebDAV password from being silently cleared by unrelated saves
    Two components (ProviderList, UsageScriptModal) directly spread the full
    settings object from the query into settingsApi.save(), which includes
    webdavSync with an empty password (cleared by get_settings_for_frontend
    for security). The backend merge_settings_for_save only preserved
    existing WebDAV config when the incoming field was None, not when it was
    present with an empty password.
    
    Frontend fix: strip webdavSync before saving in both components, matching
    the pattern already used by useSettings hook.
    
    Backend defense-in-depth: merge_settings_for_save now backfills the
    existing password when the incoming one is empty, preventing future
    regressions from similar oversights.