Commit Graph

1368 Commits

  • docs: fix outdated README content and sync across languages
    - Fix JA README macOS FAQ still claiming app is unsigned (now code-signed & notarized)
    - Update SessionManager description from "Claude Code only" to "all supported apps" (EN/ZH/JA)
    - Replace non-existent Flatpak download entry with self-build reference (EN/ZH/JA)
    - Add OpenCode and OpenClaw filesystem paths to Flatpak minimal permissions
    - Bump user manual version to v3.12.3
  • fix(test): resolve HOME env race condition in parallel tests
    - Use get_home_dir() instead of dirs::home_dir() in get_opencode_dir()
      and get_openclaw_dir() to respect CC_SWITCH_TEST_HOME override
    - Add CC_SWITCH_TEST_HOME to all TempHome implementations
    - Add #[serial] to all with_test_home tests to share serialization
      with other env-mutating tests
    - Remove --test-threads=1 workaround from CI
  • 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.
  • docs: add CONTRIBUTING.md, SECURITY.md, and CODE_OF_CONDUCT.md
    Add three community health files with bilingual (EN/ZH) support:
    - CODE_OF_CONDUCT.md: Contributor Covenant v2.1 with official Chinese translation
    - SECURITY.md: security policy pointing to GitHub Security Advisories
    - CONTRIBUTING.md: contribution guide with dev setup, code style, PR guidelines,
      i18n rules, and AI-assisted contribution policy
  • ci: add issue and PR templates with bilingual support
    Add GitHub issue templates (bug report, feature request, documentation
    issue, question) and PR template. All templates use bilingual format
    (English + Chinese) to support the international community.
  • ci: add dependabot.yml config (#1829)
    * ci: add dependabot.yml config
    
    * chore: replace `rust` label with `backend` and fix missing newline
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix(claude): sync takeover live config during provider changes (#1828)
    Keep Claude's live settings aligned with the latest provider state while proxy takeover is active, without breaking takeover fields or restore behavior.
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • 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>
  • docs: add ChefShop AI sponsor to all README versions
    Add ChefShop AI as a new sponsor with logo and descriptions in Chinese, English, and Japanese README files.
  • fix(proxy): serialize per-app provider switches to prevent state corruption
    Concurrent failover switches for the same app could cause is_current,
    local settings, and Live backup to point at different providers.
    
    - Add SwitchLockManager with per-app mutexes (different apps still parallel)
    - Unify scattered switch logic into ProxyService::hot_switch_provider
    - Fix TOCTOU in set_current_provider via mutate_settings
    - Add logical_target_changed to skip redundant UI refreshes
    - Add tests for serialization and restore-waits-for-switch scenarios
  • 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
  • fix(proxy): parse SSE fields with optional spaces in streaming handlers (#1664)
    * fix(proxy): handle SSE fields with or without spaces
    
    * refactor(proxy): deduplicate SSE field parsing
  • 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>
  • docs(release): add Copilot reverse proxy risk notice to v3.12.3 release notes
    Add risk disclaimer section in all three languages (EN/ZH/JA) warning
    users about potential GitHub ToS violations, account suspension risks,
    and no long-term availability guarantee for the Copilot reverse proxy.
  • chore(release): consolidate v3.12.3 release notes, changelog and test fixes
    Merge previously unreleased v3.12.4 content into v3.12.3:
    - CHANGELOG: combine [Unreleased] into [3.12.3], clear [Unreleased]
    - Release notes (zh/en/ja): add Copilot proxy, macOS signing,
      Reasoning Effort, OpenCode SQLite, Codex 1M toggle, Disable
      Auto-Upgrade toggle, and contributor thanks
    - Fix test mocks for skill backup/restore hooks
    - Fix schema migration test missing providers table
    - Fix TempHome to save/restore CC_SWITCH_TEST_HOME env var
  • 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.
  • feat(dmg): use create-dmg for styled macOS DMG installer
    Tauri's built-in DMG styling relies on AppleScript/Finder access which
    silently fails on CI (tauri-apps/tauri#1731). Switch to create-dmg tool
    which works on GitHub Actions macOS runners.
    
    - Replace Tauri DMG with create-dmg: background image, icon positions,
      app-drop-link, codesign, hide-extension
    - Regenerate background image at 2x Retina resolution (1320x800)
    - Revert tauri.conf.json dmg config (ineffective on CI)
    - Reorder steps: Prepare → Notarize DMG → Verify
    - Notarize and Verify now use release-assets/ path for DMG
  • feat(dmg): customize macOS DMG installer appearance
    - Add DMG background image with drag-to-install arrow guide
    - Configure window size (660x400), app and Applications icon positions
    - Center icons horizontally with visual arrow between them
  • fix(ci): add separate DMG notarization step and build retry for macOS
    Tauri only notarizes the .app bundle, not the DMG container. This caused
    stapler staple to fail with "Record not found" for the DMG.
    
    - Add "Notarize macOS DMG" step using xcrun notarytool with retry logic
    - Add retry logic (3 attempts) to macOS build step for transient network failures
    - Add hdiutil verify before DMG notarization submission
  • feat(ci): add macOS code signing and Apple notarization to release workflow
    - Import Developer ID Application certificate into temporary keychain
    - Inject APPLE_SIGNING_IDENTITY/APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID
      into Tauri build step for automatic signing and notarization
    - Staple notarization tickets to both .app and .dmg (hard-fail)
    - Add verification step: codesign --verify + spctl -a + stapler validate
      for both .app and .dmg, gating the release on success
    - Collect .dmg alongside .tar.gz and .zip in release assets
    - Clean up temporary keychain with original default restored
    - Update release notes to recommend .dmg and note Apple notarization
    - Remove all xattr workarounds and "unidentified developer" warnings
      from README, README_ZH, installation guides, and FAQ (EN/ZH/JA)
  • 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防止内容溢出
  • fix: parse tool_use/tool_result messages and add OpenCode SQLite backend (#1401)
    * fix: parse tool_use/tool_result messages and add OpenCode SQLite backend
    
      - Claude: reclassify user messages containing tool_result as "tool" role
      - Codex: handle function_call and function_call_output payload types
      - Gemini: support array content and toolCalls extraction, filter info/error types
      - OpenCode: add SQLite session scan, load and delete alongside legacy JSON
      - utils: extend parse_timestamp_to_ms for integer timestamps, extract tool_use/tool_result in shared extract_text
    
    * fix: address remaining issues from tool_use/tool_result parsing commit
      - Claude: fix role misclassification for mixed user+tool_result messages (any → all)
      - OpenCode: extract duplicate part text logic into extract_part_text()
      - OpenCode: add path validation for SQLite delete to prevent foreign DB access
      - OpenCode: wrap SQLite deletion in transaction for atomicity
      - openclaw_config: remove redundant as_deref() on Option<&str>
  • feat(proxy): resolve reasoning_effort from explicit effort with budget fallback
    Replace map_thinking_to_reasoning_effort() with resolve_reasoning_effort()
    that uses a two-tier priority system:
    
    1. Explicit output_config.effort: low/medium/high map 1:1, max → xhigh
    2. Fallback: thinking.type + budget_tokens thresholds (<4k → low,
       4k-16k → medium, ≥16k → high, adaptive → high)
    
    Both Chat Completions and Responses API paths share the same helper,
    ensuring consistent mapping across all OpenAI-compatible endpoints.