Commit Graph

888 Commits

  • 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.
  • feat(skills): 优化技能安装/卸载的缓存更新策略 (#1573)
    - 修改安装、卸载、导入、ZIP安装等操作的缓存更新逻辑,从invalidateQueries改为直接setQueryData
    - 为已安装和可发现技能查询添加keepPreviousData和staleTime: Infinity配置
    - 修复会话管理页面布局滚动问题,添加min-h-0防止内容溢出
  • chore: update Claude 4.6 context window to 1M (GA)
    Claude Opus 4.6 and Sonnet 4.6 1M context window is now GA and no
    longer requires a beta header. Update contextWindow from 200k to 1M
    for all OpenClaw/OpenCode presets (27 entries in OpenClaw, 1 in
    OpenCode Bedrock). Also add claude-sonnet-4-6 model pricing seed.
  • refactor(ui): remove duplicate OAuth tab from AddProviderDialog
    - Remove AuthCenterPanel import and OAuth TabsContent
    - Narrow activeTab type from three values to "app-specific" | "universal"
    - Simplify footer by removing oauth branch, reducing to two-way conditional
    - Change TabsList from grid-cols-3 to grid-cols-2
    - OAuth authentication remains available in settings page and CopilotAuthSection
  • 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>
  • fix(ui): replace hardcoded height calc with flex-1 to eliminate bottom blank area
    All content panels used h-[calc(100vh-8rem)] which didn't match the
    actual available space (CONTENT_TOP_OFFSET is 92px on Mac, 64px on
    Win/Linux, not 128px), causing a visible gap at the bottom.
    
    Replace with flex-1 min-h-0 to let panels fill the remaining space
    after <main> and any sibling elements (e.g. OpenClaw health banner).
  • 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.
  • feat(skills): auto-backup skill files before uninstall
    Create a local backup under ~/.cc-switch/skill-backups/ before removing
    skill directories. The backup includes all skill files and a meta.json
    with original skill metadata. Old backups are pruned to keep at most 20.
    The backup path is returned to the frontend and shown in the success
    toast. Bump version to 3.12.3.
  • fix(skills): add missing TooltipProvider in ImportSkillsDialog to prevent white screen
    The AppToggleGroup component added in 7097a0d7 uses Radix UI Tooltip
    which requires a TooltipProvider context. Without it, opening the
    import dialog crashes with a runtime error and renders a blank page.
  • fix: place OpenCode model variants at top level instead of inside options (#1317)
    Split the expanded model panel into two editing areas:
    - "Model Properties" for top-level fields (variants, cost, etc.)
    - "SDK Options" for model.options fields (provider routing, etc.)
    
    Also guard against renaming extra fields to reserved keys (name, limit,
    options) or duplicate names, and fix ModelOptionKeyInput blur desync when
    a rename is rejected by the parent handler.
  • fix(provider-form): prevent duplicate submissions on rapid button clicks (#1352)
    Make ProviderForm.handleSubmit async and await onSubmit so react-hook-form's
    isSubmitting state is tracked. Disable submit buttons in AddProviderDialog and
    EditProviderDialog while submission is in flight via onSubmittingChange callback.
  • feat: add Tool Search domain restriction bypass with active-installation patching
    Resolve the active `claude` command from PATH and apply an equal-length
    byte patch to remove the domain whitelist check. Backups are stored in
    ~/.cc-switch/toolsearch-backups/ (SHA-256 of path) so they survive
    Claude Code version upgrades. The patch auto-reapplies on app startup
    when the setting is enabled.
    
    Frontend checks PatchResult.success and rolls back the setting on failure.
  • fix: replace implicit app inference with explicit selection for Skills import and sync
    Skills import previously inferred app enablement from filesystem presence,
    causing incorrect multi-app activation when the same skill directory existed
    under multiple app paths. Now the frontend submits explicit app selections
    via ImportSkillSelection, and schema migration preserves a snapshot of
    legacy app mappings to avoid lossy reconstruction.
    
    Also adds reconciliation to sync_to_app (removes disabled/orphaned symlinks)
    and MCP sync_all_enabled (removes disabled servers from live config).
  • feat: improve empty state guidance for first-run experience
    Show detailed import instructions and conditionally display common
    config snippet hint for Claude/Codex/Gemini (not OpenCode/OpenClaw).
  • fix: make Codex TOML base_url editing section-aware
    Rewrite setCodexBaseUrl/extractCodexBaseUrl to understand TOML section
    boundaries, ensuring base_url is written into the correct
    [model_providers.<name>] section instead of being appended to file end.
    
    - Add section-aware TOML helpers in providerConfigUtils.ts
    - Extract shared update_codex_toml_field/remove_codex_toml_base_url_if
      in codex_config.rs, deduplicate proxy.rs TOML editing logic
    - Replace scattered inline base_url regexes with extractCodexBaseUrl()
    - Add comprehensive tests for both Rust and TypeScript implementations
  • feat: add authHeader field to OpenClawProviderConfig and reuse type in form state
    Add optional `authHeader` boolean to support vendor-specific auth headers
    (e.g. Longcat). Refactor `resetOpenclawState` to use the shared
    `OpenClawProviderConfig` type instead of an inline duplicate definition.
  • feat: add SiliconFlow as sponsor and update affiliate links
    Replace SDS logos with SiliconFlow logos, add SiliconFlow sponsor entries
    to all three README files (en/zh/ja), fix incorrect alt attribute, and
    update all SiliconFlow provider preset apiKeyUrl to affiliate link.
  • fix: prevent common config modal infinite reopen loop and add draft editing
    The auto-open useEffect in CodexConfigEditor and GeminiConfigEditor
    created an inescapable loop: commonConfigError triggered modal open,
    closing the modal didn't clear the error, so the effect immediately
    reopened it — locking the entire UI.
    
    - Remove auto-open useEffect from both Codex and Gemini config editors
    - Convert common config modals to draft editing (edit locally, validate
      before save) instead of persisting on every keystroke
    - Add TOML/JSON pre-validation via parseCommonConfigSnippet before any
      merge operation to prevent invalid content from being persisted
    - Expose clearCommonConfigError so editors can clear stale errors on
      modal close
  • feat: add Micu API as sponsor and update affiliate links
    Add Micu (米醋API) sponsor entry to all three README files (EN/ZH/JA)
    with logo and description. Update apiKeyUrl for Micu provider presets
    across claude, codex, opencode, and openclaw to use affiliate
    registration link.
  • feat: rename UCloud provider to Compshare/优云智算 with i18n support
    Add optional `nameKey` field to all preset interfaces for localized
    display names. The preset selector and form now resolve `nameKey` via
    i18next, falling back to `name` when unset.
    
    - zh: 优云智算 / en+ja: Compshare
    - Update model ID prefix ucloud/ → compshare/ in OpenClaw presets
    - Update partner promotion copy and README sponsor descriptions
  • fix: sync session search index with query data to refresh list after deletion
    Replace useRef+useEffect async index rebuild with useMemo so the
    FlexSearch index and the sessions array always reference the same data.
    This ensures filtered search results update immediately when a session
    is deleted via TanStack Query setQueryData.
  • fix: align OpenClaw tool permission profiles with upstream schema (#1355)
    * fix: align OpenClaw tool permission profiles with upstream schema
    
    * fix: remove dead i18n keys and save-blocking validation
    
    - Remove unused `profiles.*` nested i18n keys (dead code, ToolsPanel uses flat `profileMinimal` etc.)
    - Remove `invalidProfile` i18n key no longer referenced
    - Remove handleSave validation that blocked saving allow/deny when legacy profile exists
    - Keep the profile destructuring cleanup from the original PR
    
    ---------
    
    Co-authored-by: Your Name <your.email@example.com>
    Co-authored-by: Jason <farion1231@gmail.com>
  • feat(openClaw form): add input type selection for models Advanced Options / 为模型高级选项添加输入类型选择 (#1368)
    * feat(openClaw form): add input type selection for models Advanced Options / 为模型高级选项添加输入类型选择
    
    * fix(i18n): add missing openclaw.inputTypes key to all locales
    
    The new inputTypes field in OpenClawFormFields used defaultValue fallback,
    causing English and Japanese users to see Chinese text.
    
    ---------
    
    Co-authored-by: xu.liu2 <xu.liu2@brgroup.com>
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix: add missing authHeader: true to Longcat provider preset (#1377)
    Fixes issue where Longcat models were failing with 404 error due to missing
    authHeader configuration. According to Longcat API documentation, this
    setting is required for proper authentication.
    
    Closes: #1376
  • feat: add StepFun provider presets and step-3.5-flash (#1369)
    * feat: add StepFun provider presets
    
    * docs: regroup StepFun pricing entry
    
    * docs: tweak StepFun zh label
    
    * style: apply prettier fixes
    
    * Revert "style: apply prettier fixes"
    
    This reverts commit cff7bf2e65.
  • fix: toolbar compact mode not triggering on Windows due to left-side overflow (#1375)
    The toolbar container used `justify-end` which caused content to overflow
    to the left side. Browser `scrollWidth` does not account for left-side
    overflow, so `useAutoCompact` could never detect it and compact mode
    never activated — resulting in clipped tab labels (e.g. "aude" instead
    of "Claude") at minimum window width.
    
    Fix: remove `justify-end` from the toolbar container and use `ml-auto`
    on the child element instead. This keeps the right-aligned appearance
    but makes overflow go rightward, where `scrollWidth` correctly detects
    it and triggers compact mode.
    
    Co-authored-by: zuolan <zuolan1102@qq.com>
    Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>