Commit Graph

755 Commits

  • Add Claude Desktop official preset
    - Add Claude Desktop Official to the Claude Desktop preset list.
    - Treat selected official presets as official mode in the form.
    - Cover the official preset with a preset-order regression test.
  • refactor(presets): render presets in array order and prioritize partners
    Remove the category-based grouping logic from ProviderPresetSelector,
    letting the array position in each preset config file be the single
    source of truth for display order. Move partner presets (PatewayAI,
    火山Agentplan, BytePlus, DouBaoSeed) right after Shengsuanyun across
    all 6 config files so they appear earlier in the UI.
  • fix(failover): patch P1-P3 reliability gaps surfaced by team review
    - Forwarder buffers non-streaming bodies and primes streaming first
      chunk before signaling success, so body timeouts and SSE first-chunk
      failures route through the circuit breaker instead of being recorded
      as success on response-header arrival
    - Atomic enable-failover: switch to P1 before persisting the flag, and
      roll back auto-added queue entries when the switch is rejected
      (e.g. official providers)
    - Hot-reload circuit breaker config on per-app proxy config change
      instead of waiting for a proxy restart
    - FailoverToggle / FailoverQueueManager / AutoFailoverConfigPanel
      require proxy takeover for the active app; the backend command also
      rejects enabling when takeover is off
    - ProviderHealthBadge consumes the backend is_healthy flag instead of
      hardcoding the 5-failure threshold
    
    Cleanup:
    - impl From<&AppProxyConfig> for CircuitBreakerConfig and use it from
      the command layer
    - Collapse three identical TabsContent blocks into a single map
  • feat(providers): add routing support badges for Claude Code and Codex
    Add visual indicators for routing capabilities on provider cards:
    - Claude Code: "Needs Routing" badge for non-official providers with non-anthropic API formats
    - Claude Code: "No Routing Support" badge for official providers
    - Codex: "No Routing Support" badge for official providers
    
    The badges help users understand which providers support format conversion through routing.
  • fix(providers): disable model test for third-party Claude providers
    Most third-party Claude Code providers now reject requests from
    non-official clients, so the model test button would just produce
    noisy failures (or worse, trigger risk controls on the provider
    side). Treat third-party Claude providers the same way as official /
    Copilot / Codex OAuth: pass onTest=undefined so ProviderActions
    renders the test button in its existing disabled visual state.
  • feat(codex-oauth): fetch model list from ChatGPT backend on demand
    - Add `get_codex_oauth_models` Tauri command reusing the managed OAuth
      access token to hit `chatgpt.com/backend-api/codex/models`; HTTP and
      multi-shape JSON parsing live in `services::codex_oauth_models` so the
      command stays thin.
    - Unify the Claude form's "fetch models" button across normal / Copilot /
      Codex OAuth presets, drop the auto-load effect for Copilot in favor of
      explicit clicks, and guard against stale responses with a requestId ref.
    - Add Vitest coverage for both Copilot and Codex OAuth paths asserting no
      request on mount and the correct account id on click; add Rust unit
      tests for the four model-list payload shapes.
  • fix(usage): pricing routing, SSE lifecycle, and validation hardening
    * model pricing routing: extend prefix-match families (gpt-/o1-o5/
      gemini-/deepseek-/qwen-/glm-/kimi-/minimax-) with per-family dash
      thresholds so short base IDs like gpt-5 no longer mis-match
      gpt-5-mini; strip ISO and 8-digit date suffixes via UTF-8-safe
      byte matching so claude-haiku-4-5-20251001 falls back to
      claude-haiku-4-5 pricing
    * SSE collector: SseUsageFinishGuard (RAII) guarantees finish() on
      early return or panic; AtomicBool fast path lets push() skip the
      Mutex once first-event time is recorded
    * validation: shared validate_cost_multiplier / validate_pricing_source
      helpers across DAO and service layers; PRICING_SOURCE_RESPONSE /
      PRICING_SOURCE_REQUEST constants replace string literals; price
      fields in update_model_pricing now reject empty / non-decimal /
      negative input before INSERT
    * backfill: add backfill_missing_usage_costs_for_model so a single
      price edit only scans matching rows instead of the full log table;
      startup backfill remains full-scan
    * session_usage{,_codex,_gemini}: share find_model_pricing helper from
      usage_stats; metadata_modified_nanos centralizes mtime precision
    * frontend: NON_NEGATIVE_DECIMAL_REGEX + isNonNegativeDecimalString
      replace three copies of the same multiplier regex; isUnpricedUsage
      surfaces zero-cost rows that have usage tokens (cached per row to
      avoid double evaluation); invalidate usageKeys.all on pricing mutate
      so backfilled rows refresh
  • fix(proxy-ui): accept IPv6 listen addresses in ProxyPanel validation
    The backend already understands `::` -> `::1` and wraps IPv6 literals
    in brackets (services/proxy.rs), but the panel's save-time validator
    only accepted localhost, 0.0.0.0, and IPv4 dotted-quads. Users who
    wanted to listen on an IPv6 loopback had to bypass the UI and edit
    config directly.
    
    Add an isValidIpv6 helper that requires at least one ':' and round-trips
    through `new URL('http://[<addr>]/')` so the platform's built-in IPv6
    parser does the heavy lifting (covers compressed `::`, full 8-group
    form, zone IDs). Update the invalidAddress copy in zh / en / ja so the
    error message reflects the new accepted set.
  • feat(claude-code): role-based model mapping with display names and 1M flag
    - Replace the four flat env inputs with a Sonnet/Opus/Haiku role table.
      Each row exposes ANTHROPIC_DEFAULT_*_MODEL plus a new display name
      field ANTHROPIC_DEFAULT_*_MODEL_NAME, and Sonnet/Opus gain a
      "Declare 1M" checkbox that toggles the [1M] suffix.
    - Strip the [1M] context-capability marker before forwarding non-Copilot
      requests upstream. Copilot keeps its existing [1m]->-1m normalization.
    - Claude Desktop import now consumes ANTHROPIC_DEFAULT_*_MODEL_NAME as
      label_override, closing the Claude Code -> Claude Desktop displayName
      pipeline; add_route's merge logic is shared between hashmap branches.
    - Unify the [1M] marker as ONE_M_CONTEXT_MARKER across
      claude_desktop_config and proxy::model_mapper; rename the strip
      helper to strip_one_m_suffix_for_upstream.
    - Collapse useModelState's seven duplicated useState initializers and
      the useEffect parse block into a single parseModelsFromConfig call.
    - Add tests/hooks/useModelState.test.tsx and a Claude Desktop import
      test covering Kimi K2 -> label_override. i18n (en/ja/zh) updated.
  • refactor(claude-desktop): lock route IDs to sonnet/opus/haiku roles
    Adapt to Claude Desktop 1.6259.1+ fail-all validation which only
    accepts claude-(sonnet|opus|haiku)-* route IDs. Branded model names
    (DeepSeek, Kimi, GLM, etc.) now live in a new labelOverride field
    instead of being embedded in route IDs.
    
    - Backend auto-repairs legacy unsafe routes to the next free
      sonnet/opus/haiku slot instead of erroring
    - Frontend swaps the free-form route input for a role dropdown plus
      menu display name field
    - Add CLAUDE_DESKTOP_ROLE_ROUTE_IDS as the single source of truth
      for role-to-route mapping; presets and form both consume it
    - Drop the dead displayName alias on ClaudeDesktopModelRoute and the
      ineffective /v1/models display_name injection (UI ignores it)
    - Update i18n (en/ja/zh) and form focus test for the new fields
  • feat(usage): filter-driven Hero with cache-normalized totals
    - Normalize OpenAI/Gemini input_tokens semantics in SQL via the new
      fresh_input_sql helper (cache_read subtracted at query time, no data
      migration). Recovers correct cache hit rates for Codex/Gemini.
    - Add get_usage_summary_by_app endpoint for per-app split (single
      UNION ALL + GROUP BY, avoids N+1).
    - Replace UsageSummaryCards + AppBreakdownRail with a single
      filter-driven UsageHero card; clicking a filter button now truly
      changes the displayed numbers and the title accent color.
    - Tighten KNOWN_APP_TYPES to the 3 app_types whose token data is
      reliably collected (claude/codex/gemini); hide claude-desktop,
      hermes, opencode, openclaw filter buttons and i18n keys.
    - Flag cache_creation as N/A for OpenAI-style protocols (Codex,
      Gemini); show a "partial" tooltip when the All view mixes both
      protocol families.
  • feat(claude-desktop): rework Claude Code import flow
    - Derive route keys from the upstream model name (pass-through style)
      instead of fixed Claude aliases, and translate the legacy [1M] suffix
      into the supports1m field at the import boundary. Three Claude aliases
      mapped to the same upstream now collapse to a single route (e.g.
      MiniMax-M2 across SONNET/OPUS/HAIKU env produces one
      claude-MiniMax-M2 -> MiniMax-M2 row), with [1M] OR-aggregated.
    - Add an import-time safety net that rebuilds claude-desktop-official
      when missing, so users who deleted it can recover via the normal
      import button without losing customizations on other providers.
    - Hide API key and endpoint URL inputs in the official provider edit
      form to mirror Claude Code's behavior and prevent user confusion.
    - Reword the empty-state import button label for clarity.
  • refactor(claude-desktop): replace [1M] suffix with supports1m field
    inferenceModels entries now emit {name, supports1m: true} objects when
    1M is enabled (plain strings otherwise), instead of appending a " [1M]"
    suffix to model IDs. Route IDs and upstream model IDs are stored
    verbatim; the suffix is rejected on input rather than silently stripped,
    and proxy request mapping now requires an exact route_id match.
  • fix(ui): center Monitor badge icon in app switcher
    The Monitor glyph's visual weight skews upward (screen rect dominates
    while the stand is two thin lines), making it appear off-center inside
    the 11px Claude Desktop badge. Add a per-badge offsetY config and
    apply translateY(0.5px) to compensate.
  • - 修复 Claude Desktop 模型输入框失焦
    - 为动态模型行添加稳定 rowId,避免编辑模型 ID 时重挂载
    
    - 增加模型映射和直连模型列表焦点保持回归测试
  • - 支持 Claude Desktop 使用 Copilot/Codex OAuth 供应商
    - 放开本地路由托管 OAuth 供应商校验,允许动态 Token
    - 新增 Claude Desktop Copilot/Codex 预设与账号选择
    - 添加 OAuth proxy 回归测试
  • refactor(claude-desktop): drop displayName from model route schema
    Claude Desktop's new model menu reads model IDs directly and ignores the
    display_name field, so a separate displayName slot added UI noise without
    any product value. Collapse the routeId / model / displayName tuple down
    to routeId / model, and let the route ID carry the user-visible name
    through a non-editable claude- prefix rendered next to the input.
    
    Drop display_name from ClaudeDesktopModelRoute, ClaudeDesktopDefaultRoute,
    and ResolvedModelRoute on the Rust side plus the matching TS interfaces,
    stop emitting it in /v1/models responses, derive route IDs from upstream
    model IDs when picked via the model dropdown, and update zh/en/ja copy to
    describe the new two-field layout.
  • feat(ui): use "Claude Code" label in app visibility settings
    The app visibility section in Settings showed "Claude" for the first
    entry, identical to "Claude Desktop" at a glance. Add a dedicated
    i18n key apps.claudeCode and point the settings panel at it, while
    leaving apps.claude untouched so other panels (MCP, Skills, Usage,
    etc.) keep their shorter "Claude" label.
  • feat(ui): distinguish Claude Code vs Claude Desktop in app switcher
    The two Claude entries shared the same orange logo, making them hard to
    tell apart at a glance. Rename the first entry to "Claude Code" and
    overlay a Terminal badge on its logo; overlay a Monitor badge on the
    Claude Desktop logo. Changes are scoped to AppSwitcher only; other
    panels (MCP, Skills, Usage, etc.) continue to show "Claude".
  • chore(brand): surface ccswitch.io as the sole official website
    Add an "Only Official Website" header to the three READMEs, an
    About panel button, and a tray menu entry — all pointing to
    ccswitch.io. Consolidates brand and SEO signals on the canonical
    domain across docs, GUI, and system tray.
  • feat(claude-desktop): add 44 provider presets translated from Claude Code
    - New src/config/claudeDesktopProviderPresets.ts with the Claude Code
      preset list re-shaped into Desktop's three-segment route format
      (routeId / upstreamModel / displayName); excludes OAuth providers,
      AWS Bedrock (no SigV4 support) and KAT-Coder (placeholder URL).
    - Non-Claude upstream presets show upstream model id as displayName
      (e.g. deepseek-v4-pro) so the Desktop model list reflects what is
      actually being requested. OpenRouter/TheRouter/PIPELLM keep
      Sonnet/Opus/Haiku since their upstream really is Anthropic Claude.
    - Wire ProviderPresetSelector into ClaudeDesktopProviderForm so
      selecting a preset back-fills baseUrl, mode, routes and apiFormat.
    - Drop the hard-coded ANTHROPIC_AUTH_TOKEN write in handleSubmit so
      ANTHROPIC_API_KEY presets (LemonData / AiHubMix / Gemini Native)
      save under the correct env key, and clear the opposite key on switch.
    - Hide the universal-providers tab for claude-desktop because its
      meta-driven routing has no analogue in the universal flat-env shape.
    - Add apps."claude-desktop" i18n key (zh/en/ja) so the dialog tab
      label resolves instead of showing the literal key.
  • refactor(claude-desktop): show badge only for providers requiring routing
    Direct-mode providers no longer display a badge since routing is
    optional for them. Proxy-mode providers now show "需要路由" / "Requires
    routing" to clarify that local routing must be active.
  • refactor(claude-desktop): simplify model mapping UX
    - Remove "Import from Claude" button from main provider list (keep in empty state)
    - Remove "Desktop model" column from proxy mode mapping table; route names are now auto-generated
    - Rename "upstream model" label to "requested model" and equalize column widths
    - Rewrite model mapping hint and toggle description for end-user clarity
    - Update validation messages and remove dead i18n keys (routeModelLabel)
  • refactor(claude-desktop): align provider form UI with Claude Code
    - Rename field labels: "Gateway Base URL" → "API Endpoint", "Bearer Token" → "API Key"
    - Change layout from 2-column grid to vertical sections matching Claude Code
    - Reuse shared EndpointField component with format-aware amber hint box
    - Replace native <datalist> with vendor-grouped ModelDropdown (OpenCode pattern)
    - Move Fetch/Add buttons to section headers with compact sm styling
    - Extract ModelDropdown to shared module, deduplicate from OpenCodeFormFields
    - Extract renderActionButtons helper to eliminate proxy/direct button duplication
    - Remove dead i18n keys (gatewayBaseUrl, bearerToken) from all 3 locales
  • fix(claude-desktop): remove proxy-stopped status alert
    The route toggle in the top-right corner already communicates proxy
    state; the extra warning banner was redundant and inconsistent with
    other managed apps.
  • feat(claude-desktop): add 3P provider switching with proxy gateway
    Adds a new ClaudeDesktop AppType that writes Claude Desktop's third-party
    inference profile under configLibrary/, sharing _meta.json with other
    launchers (Ollama-compatible) so cc-switch can coexist with them.
    
    Two switch modes:
    - direct: provider already exposes claude-* / anthropic/claude-* model
      ids on Anthropic Messages, Claude Desktop connects to it directly.
    - proxy: cc-switch's local proxy acts as the inference gateway,
      presenting only claude-* route names to Claude Desktop and mapping
      them to real upstream models. Required after Anthropic restricted
      Claude Desktop to claude-family ids.
    
    Backend:
    - New module claude_desktop_config with snapshot/rollback, official seed
      bypass, /claude-desktop/v1/{models,messages} routes, and a single
      source of truth for default proxy routes.
    - Gateway token persisted in SQLite, validated on every proxied request.
    - get_claude_desktop_status surfaces drift signals (stale models,
      missing routes, proxy stopped, base URL mismatch, missing token).
    
    Frontend:
    - Slim ClaudeDesktopProviderForm independent from ProviderForm,
      controlled by a top-level appId guard.
    - ProviderList banner consumes the status query (5s polling) and
      renders actionable diagnostics.
    - ClaudeDesktopRouteToggle in the header to start/stop the local
      gateway without touching takeover state.
    - Three-locale i18n synchronised.
  • refactor(theme): drop unused MouseEvent param from setTheme
    Now that the view transition animation is gone, setTheme no longer
    needs click coordinates. Reduce the API surface to (theme: Theme) =>
    void and simplify the call sites in mode-toggle and ThemeSettings.
  • refactor(theme): remove circular reveal animation for theme switching
    The View Transitions API used here crashes WebKitGTK with SIGSEGV on
    Linux. Rather than gating document.startViewTransition per platform
    (see PR #2502), drop the animation entirely — it's a low-value visual
    flourish on a low-frequency action that doesn't justify a permanent
    platform branch.
    
    Removes the ::view-transition-* CSS block and the coordinate plumbing
    in setTheme. The optional event parameter is kept on the API surface
    to keep call sites compiling; they'll be cleaned up in a follow-up
    commit.
  • chore(usage): drop Hermes Agent tracking integration
    Hermes aggregates all in-process API calls into a single sessions row
    with the `model` field locked to the initial model, so the usage
    dashboard cannot cleanly surface per-call billing context. Two rounds
    of UI workarounds (raw mapping, then `<model> @ <host>` display) did
    not resolve the user-facing confusion, so the whole tracking
    integration is dropped for now.
    
    Removes session_usage_hermes service (and its 17 tests), sync wiring
    in commands/usage.rs and lib.rs, _hermes_session/hermes_session
    entries in usage_stats SQL (provider_name_coalesce CASE and
    effective_usage_log_filter IN clause), frontend Tab/banner/dropdown/
    icon entries, and four i18n keys per locale.
    
    Hermes app integration outside usage tracking (proxy routing,
    session manager, config) is preserved. Pre-existing hermes rows in
    proxy_request_logs are left as orphans — filtered out by the
    updated SQL and never surfaced in the UI.
  • feat: support launch warp and execute session (#2466)
    * feat: support launch warp and execute session
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * other wires
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * for launch with provider
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * fixup indirection
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * clippy
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * address comments
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    ---------
    
    Signed-off-by: tison <wander4096@gmail.com>
  • feat(usage): add Hermes Agent tracking + fix zero-cost bug + perf
    Hermes:
    - Parse ~/.hermes/state.db sessions (incl. profiles/*/state.db) into
      proxy_request_logs with data_source='hermes_session', WAL-aware
      incremental sync, Hermes-reported cost preferred over model_pricing
      fallback
    
    Zero-cost bug (dashboard showed \$0 totals):
    - GPT-5.5 family default pricing (~83% of affected rows used GPT-5.5)
    - find_model_pricing_row: ASCII-lowercase normalization so
      "OpenAI/GPT-5.5@HIGH" matches seeded "gpt-5.5"
    - Startup cost backfill in async task: scan rows where total_cost <= 0
      but tokens > 0, recompute via model_pricing in a single transaction
    
    Performance:
    - Add (app_type, created_at DESC) covering index for dashboard range
      queries
    - Add expression index on COALESCE(data_source, 'proxy') so dedup EXISTS
      subqueries use index lookup instead of full scan; drop superseded
      idx_request_logs_dedup_lookup
    
    Refactor:
    - row_to_request_log_detail helper (3-way de-dup; fixes cost_multiplier
      \"1\" vs \"1.0\" drift between callers)
    - Promote get_sync_state/update_sync_state to shared session_usage
      module (4 copies -> 1)
    - run_step helper in lib.rs replaces 9 if-let-Err blocks
    - maybe_backfill_log_costs returns bool to skip duplicate total_cost
      parsing in caller
  • chore(codex): hide 1M context window toggle in provider edit form
    Codex models no longer accept model_context_window=1000000, so the
    toggle and its paired auto-compact-limit input are commented out in
    the provider edit form. State hooks, helper imports, and i18n keys
    are preserved so the UI can be restored in one batch if upstream
    support returns. The TOML editor remains visible, allowing manual
    edits if needed.
  • feat(provider-form): soften validation with "save anyway" prompt (#2307)
    * feat(provider-form): soften business-rule validation with "save anyway" prompt
    
    Refactor handleSubmit so empty-field / missing-item validations (provider
    name, endpoint, API key, opencode model, template variables, provider key
    required) no longer hard-reject with toast.error. Instead they are collected
    into an issues list and presented via a ConfirmDialog; the user can cancel
    or choose "Save anyway" to proceed.
    
    Integrity constraints stay as hard rejections:
    - providerKey regex / duplicate (would corrupt other providers)
    - Copilot / Codex OAuth not authenticated (no token, cannot establish)
    - omo Other Fields JSON not an object / parse failure
    
    This aligns the frontend with the backend's existing "relaxed save / strict
    switch" split (see gemini_config.rs: validate_gemini_settings vs
    validate_gemini_settings_strict) and unblocks legitimate configs such as
    AWS Bedrock, Vertex AI, and custom Gemini base URLs that the UI previously
    refused to save.
    
    Refs: #2196, #1204
    
    * fix(provider-form): address review feedback on soft-validation
    
    P1: move empty providerKey back to hard rejection for OpenCode / OpenClaw /
    Hermes. Since providerKey is the primary identity for these apps and the
    mutations layer throws "Provider key is required" when absent, letting users
    click "save anyway" would surface a generic error toast instead of a
    precise, actionable one. Treat empty providerKey as an integrity constraint
    alongside regex / duplicate checks.
    
    P2: give the soft-confirm submit path its own submitting state. The
    confirm-dialog path bypassed react-hook-form's isSubmitting lifecycle, so
    slow or failing saves left the outer submit button responsive and could
    spawn unhandled rejections. Now the confirm handler awaits performSubmit
    inside try/catch/finally, uses an isConfirmSubmitting flag to gate both
    confirm and cancel clicks, and folds the flag into the outer disabled
    state and onSubmittingChange callback.
    
    Refs: #2307 review comments
    
    * chore(clippy): use push for single char '…' in truncate_body
    
    Clippy 1.95 added single_char_add_str which flagged the push_str("…")
    in truncate_body. Rebased onto latest upstream/main and applied the
    suggested fix so the Backend Checks clippy job passes.
    
    Unrelated to this PR's core changes; bundled in so the PR is mergeable
    without waiting for a separate upstream fix.
    
    ---------
    
    Co-authored-by: Allen <allen@AllenMacBook-M4-Pro.local>
  • fix(model-fetch): support /models for Anthropic-compat subpath providers
    Providers like DeepSeek, Kimi, Zhipu GLM and MiniMax expose the
    Anthropic-compatible API on a subpath (e.g. /anthropic) while the
    OpenAI-style /models endpoint lives at the API root. The previous
    heuristic blindly appended /v1/models to the Base URL, so every such
    provider returned 404 and the UI mislabeled it as "provider does not
    support fetching models".
    
    Backend now generates a candidate list and tries them in order:
    preset override -> baseURL /v1/models -> stripped-subpath /v1/models ->
    stripped-subpath /models. Non-404/405 responses (auth, network) stop
    immediately so we never retry against hostile status codes. Known
    compat suffixes are kept in a length-descending constant so the
    longest match wins; response bodies are truncated to 512 chars to
    avoid HTML 404 pages bloating the error string.
    
    Preset type gains an optional modelsUrl (DeepSeek points at
    https://api.deepseek.com/models). Frontend threads the override
    through fetchModelsForConfig when the current Base URL still matches
    the preset default. A new fetchModelsEndpointNotFound i18n key
    replaces the misleading "not supported" toast for exhausted-candidate
    and 404/405 cases (zh/en/ja).
  • feat(tray): show coding-plan usage for Kimi / Zhipu / MiniMax
    dc04165f surfaced tray usage badges for Claude/Codex/Gemini official
    OAuth only. Chinese coding-plan providers already expose 5h + weekly
    windows through coding_plan::get_coding_plan_quota, but two gaps kept
    the tray from rendering them.
    
    - format_script_summary read only data.first(), truncating the tier-
      flattened UsageResult to a single window. Detect plan_name matching
      TIER_FIVE_HOUR / TIER_WEEKLY_LIMIT and emit the "🟢 h12% w80%" layout
      used by format_subscription_summary; worst utilization drives the
      emoji. Copilot / balance / custom scripts keep the legacy single-
      bucket output via fallback.
    
    - usage_script previously required manual activation through
      UsageScriptModal. Auto-inject meta.usage_script on Claude provider
      creation when ANTHROPIC_BASE_URL matches a known coding plan, so the
      tray lights up without the user opening the modal. Does not overwrite
      existing usage_script on update.
    
    Extract the URL route table out of UsageScriptModal into a shared
    codingPlanProviders module so the modal, the creation hook, and the
    Rust coding_plan::detect_provider mirror all agree on one list.
    Add TIER_WEEKLY_LIMIT alongside TIER_FIVE_HOUR and a createUsageScript()
    factory to collapse the duplicated default fields across four call
    sites and drop the remaining stringly-typed tier names.
  • refactor(hermes): drop config health check scanner
    The Hermes config.yaml schema has stabilized and users have migrated to
    the current provider fields, so the value of scanning for model.provider
    dangling references, custom_providers shape errors, v12 migration residue
    etc. no longer justifies the maintenance surface — and the scan produces
    false positives when users keep some providers under Hermes' v12+
    providers: dict (Hermes' runtime merges both shapes, but CC Switch's
    scanner only looked at the list form).
    
    Removes the whole HermesHealthWarning type, scan_hermes_config_health
    command, HermesHealthBanner React component, useHermesHealth hook,
    warnings field on HermesWriteOutcome, and the three helper functions
    (yaml_as_non_empty_str, collect_mapping_string_keys, hermes_warning)
    that only served the scanner. Drops the matching i18n keys in
    zh/en/ja and the fixInWebUI button label that only the banner used.
  • Fix/一键配置失效 (#2249)
    * style(FailoverQueueManager): 显示供应商备注信息
    
    * style(FailoverQueueItem): 添加供应商备注字段以支持备注信息显示
    
    * style(FailoverQueueManager): 显示供应商备注信息
    
    * style(FailoverQueueItem): 添加供应商备注字段以支持备注信息显示
    
    * style(FailoverQueueManager): 更新供应商备注信息的显示样式
    
    * style(FailoverQueueItem): 添加条件序列化以优化供应商备注字段
    
    * fix: 优化模型状态管理,确保配置更新时正确引用最新设置
    
    * fix(skill): improve error handling for skill source directory resolution
    
    Co-authored-by: Copilot <copilot@github.com>
    
    * fix(gemini): simplify project directory retrieval in scan_sessions function
    
    * fix(useModelState): optimize latestConfigRef assignment in useModelState hook
    
    * fix(useModelState): remove unnecessary blank line in useModelState hook
    
    ---------
    
    Co-authored-by: Copilot <copilot@github.com>
  • feat: Add Codex OAuth FAST mode toggle (#2210)
    * Add Codex OAuth FAST mode toggle
    
    * fix(codex-oauth): default FAST mode to off to avoid surprise quota burn
    
    service_tier="priority" consumes ChatGPT subscription quota at a higher
    rate. Users must now opt in explicitly rather than inherit FAST mode
    silently when this feature ships.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix(skills): prevent duplicate imports when import button is double-clicked (#2211)
    Closes #2139
    
    Two related defects let the installed-skills count balloon when users
    tap the import confirm button multiple times — either deliberately or
    because the button is still clickable while a slow import is in flight:
    
    - The confirm button only disabled itself while `selected.size === 0`,
      so it stayed clickable during a pending mutation. Each extra click
      triggered another `importFromApps` mutation.
    - `useImportSkillsFromApps` appended the server response to the
      installed cache without deduping, so re-firing the mutation stacked
      the same skills into the list again.
    
    Disable the confirm (and cancel) buttons while the mutation is pending
    — matching the `isRestoring` / `isDeleting` pattern already used by
    `RestoreSkillsDialog` — and merge success payloads by
    `InstalledSkill.id` so repeated results overwrite rather than
    accumulate.
    
    The merge is extracted as a pure `mergeImportedSkills` reducer to make
    the behaviour unit-testable and to short-circuit on an empty payload,
    returning the existing reference so React Query does not notify
    subscribers about a no-op cache update.
  • Style/session manager list UI (#2201)
    * style(FailoverQueueManager): 显示供应商备注信息
    
    * style(FailoverQueueItem): 添加供应商备注字段以支持备注信息显示
    
    * style(FailoverQueueManager): 显示供应商备注信息
    
    * style(FailoverQueueItem): 添加供应商备注字段以支持备注信息显示
    
    * style(FailoverQueueManager): 更新供应商备注信息的显示样式
    
    * style(FailoverQueueItem): 添加条件序列化以优化供应商备注字段
    
    * style(App, SettingsPage, ScrollArea): 调整组件样式以改善布局和视觉效果
    
    * style(App, SettingsPage, ScrollArea): 调整组件样式以改善布局和视觉效果
    
    * style(SettingsPage, useSettings): 统一代码格式,调整样式和变量声明
    
    * style(App): 调整底部内边距以改善布局
  • fix(usage): support Hermes and OpenClaw in usage query modal
    Extend getProviderCredentials to read flat settingsConfig fields for
    Hermes (snake_case base_url / api_key) and OpenClaw (camelCase baseUrl
    / apiKey), so the "official balance" template auto-selects for matching
    providers like SiliconFlow.
    
    Also refactor the BALANCE and TOKEN_PLAN test paths in handleTest to
    reuse the precomputed providerCredentials instead of re-reading
    env.ANTHROPIC_* directly, which previously caused empty key errors for
    non-Claude apps even when the key was configured.
  • style(providers): apply prettier to common-config hooks
    Reformat three hooks brought in from upstream #2191 so that
    format:check passes in CI.
  • feat(hermes): align provider schema with Hermes Agent 0.10.0
    Hermes 0.10.0 tightened custom_providers validation (commit 2cdae233):
    invalid base_urls are rejected, unknown fields produce warnings, and
    new fields (rate_limit_delay, bedrock_converse, key_env) landed.
    
    - Add bedrock_converse to the api_mode selector (and i18n labels)
    - Expose rate_limit_delay in a provider-level advanced panel
    - Validate base_url client-side (URL shape, template-token friendly)
    - Drop per-model max_tokens — not in _VALID_CUSTOM_PROVIDER_FIELDS
    - Round-trip test asserts set_provider preserves rate_limit_delay /
      key_env / any unknown forward-compat field
  • style: apply prettier and rustfmt
    No behavior changes. Brings three files back in line with the project
    formatters (CI runs `pnpm format:check` and `cargo fmt --check`).
  • fix(providers): drop legacy ANTHROPIC_REASONING_MODEL from Claude quick-set
    The model-mapping quick-set button referenced an undefined `reasoningModel`
    prop and wrote `ANTHROPIC_REASONING_MODEL`, which the backend explicitly
    marks as deprecated legacy (see services/provider/mod.rs and
    services/proxy.rs). Remove all three references so typecheck passes and
    the button matches the provider model schema.