Commit Graph

808 Commits

  • fix(proxy): respect existing token field when syncing Claude config
    - Add support for ANTHROPIC_API_KEY in Claude auth extraction
    - Only update existing token fields during sync, avoid adding fields
      that weren't originally configured by the user
    - Add tests for both scenarios
  • fix(proxy): add fallback recovery for orphaned takeover state
    - Detect takeover residue in Live configs even when proxy is not running
    - Implement 3-tier fallback: backup → SSOT → cleanup placeholders
    - Only delete backup after successful restore to prevent data loss
    - Fix EditProviderDialog to check current app's takeover status only
  • refactor(proxy): remove global auto-start flag
    - Remove global proxy auto-start flag from config and UI.
    - Simplify per-app takeover start/stop and stop server when the last takeover is disabled.
    - Restore live takeover detection used for crash recovery.
    - Keep proxy_config.enabled column but always write 0 for compatibility.
    - Tests: not run (not requested).
  • fix(import): refresh all providers immediately after SQL import
    - Remove setTimeout delay that could be cancelled on component unmount
    - Invalidate all providers cache (not just current app) since import affects all apps
    - Call onImportSuccess before sync to ensure UI refresh even if sync fails
    - Update i18n: "Data refreshed" (past tense, reflecting immediate action)
  • fix(backup): restrict SQL import to CC Switch exported backups only
    - Add validation to reject SQL files without CC Switch export header
    - Remove redundant sanitize_import_sql (sqlite_* objects already excluded at export time)
    - Fix backup filename collision by appending counter suffix
    - Update i18n hints to clarify import restriction
  • Fix/about section UI (#419)
    * fix(ui): improve AboutSection styling and version detection
    
    - Add framer-motion animations for smooth page transitions
    - Unify button sizes and add icons for consistency
    - Add gradient backgrounds and hover effects to cards
    - Add notInstalled i18n translations (zh/en/ja)
    - Fix version detection when stdout/stderr is empty
    
    * fix(proxy): persist per-app takeover state across app restarts
    
    - Fix proxy toggle color to reflect current app's takeover state only
    - Restore proxy service on startup if Live config is still in takeover state
    - Preserve per-app backup records instead of clearing all on restart
    - Only recover Live config when proxy service fails to start
  • chore: rename version to 3.9.0-1 for MSI compatibility
    MSI installer requires numeric-only pre-release identifiers.
    Changed from 3.9.0-beta.1 to 3.9.0-1.
  • fix: import RunEvent for all platforms
    The #[cfg(target_os = "macos")] restriction was a historical artifact
    from when RunEvent was only used for macOS-specific events (Reopen, Opened).
    After c9ea13a added ExitRequested handling for all platforms, the import
    should have been updated but was overlooked.
  • chore: bump version to 3.9.0-beta.1
    - Update version in package.json, Cargo.toml, tauri.conf.json
    - Add CHANGELOG entry for v3.9.0-beta.1 with:
      - Local Proxy Server feature
      - Auto Failover with circuit breaker
      - Skills multi-app support
      - Provider icon colors
      - 25+ bug fixes
    - Add proxy feature guide documentation (Chinese)
  • fix(ui): restore fade transition for Skills button
    The Skills button transition was accidentally removed in commit 1fb2d5e
    when adding multi-app support. This restores the smooth fade-in/out
    animation when switching between apps that support Skills (Claude/Codex)
    and those that don't (Gemini).
  • fix(mcp): skip sync when target CLI app is not installed
    Add guard functions to check if Claude/Codex/Gemini CLI has been
    initialized before attempting to sync MCP configurations. This prevents
    creating unwanted config files in directories that don't exist.
    
    - Claude: check ~/.claude dir OR ~/.claude.json file exists
    - Codex: check ~/.codex dir exists
    - Gemini: check ~/.gemini dir exists
    
    When the target app is not installed, sync operations now silently
    succeed without writing any files, allowing users to manage MCP servers
    for apps they actually use without side effects on others.
  • fix(mcp): improve upsert and import robustness
    - Remove server from live config when app is disabled during upsert
    - Merge enabled flags instead of overwriting when importing from multiple apps
    - Normalize Gemini MCP type field (url-only → sse, command → stdio)
    - Use atomic write for Codex config updates
    - Add tests for disable-removal, multi-app merge, and Gemini SSE import
  • feat(proxy): implement per-app takeover mode
    Replace global live takeover with granular per-app control:
    - Add start_proxy_server command (start without takeover)
    - Add get_proxy_takeover_status to query each app's state
    - Add set_proxy_takeover_for_app for individual app control
    - Use live backup existence as SSOT for takeover state
    - Refactor sync_live_to_provider to eliminate code duplication
    - Update ProxyToggle to show status per active app
  • fix(proxy): takeover Codex base_url via model_provider
    - Update Codex `model_providers.<model_provider>.base_url` to the proxy origin with `/v1`
    - Add route fallbacks for `/responses` and `/chat/completions` (plus double-`/v1` safeguard)
    - Add unit tests for the TOML base_url takeover logic
  • fix(proxy): harden crash recovery with fallback detection
    - Set takeover flag before writing proxy config to fix race condition
      where crash during takeover left Live configs corrupted but flag unset
    - Add fallback detection by checking for placeholder tokens in Live
      configs when backups exist but flag is false (handles legacy/edge cases)
    - Improve error handling with proper rollback at each stage of startup
    - Clean up stale backups when Live configs are not in takeover state
      to avoid long-term storage of sensitive tokens
  • fix(proxy): sync UI when active provider differs from current setting
    Previously, UI sync was triggered only when failover happened (retry count > 1).
    This missed cases where the first provider in the failover queue succeeded but
    was different from the user's selected provider in settings.
    
    Now we capture the current provider ID at request start and compare it with
    the actually used provider. This ensures UI/tray always reflects the real
    provider handling requests.
  • fix(proxy): resolve circuit breaker race condition and error classification
    This commit addresses two critical issues in the proxy failover logic:
    
    1. Circuit Breaker HalfOpen Concurrency Bug:
       - Introduced `AllowResult` struct to track half-open permit usage
       - Added state guard in `transition_to_half_open()` to prevent duplicate resets
       - Replaced `fetch_sub` with CAS loop in `release_half_open_permit()` to prevent underflow
       - Separated `is_available()` (routing) from `allow_request()` (permit acquisition)
    
    2. Error Classification Conflation:
       - Split retry logic into `should_retry_same_provider()` and `categorize_proxy_error()`
       - Same-provider retry: only for transient errors (timeout, 429, 5xx)
       - Cross-provider failover: now includes ConfigError, TransformError, AuthError
       - 4xx errors (401/403) no longer waste retries on the same provider
  • fix(proxy): stabilize live takeover and provider editing
    - Skip live writes when takeover is active and proxy is running
    - Refresh live backups from provider edits during takeover
    - Sync live tokens to DB without clobbering real keys with placeholders
    - Avoid injecting extra placeholder keys into Claude live env
    - Reapply takeover after proxy listen address/port changes
    - In takeover mode, edit dialog uses DB config and keeps API key state in sync
  • Feature/error request logging (#401)
    * feat(proxy): add error mapper for HTTP status code mapping
    
    - Add error_mapper.rs module to map ProxyError to HTTP status codes
    - Implement map_proxy_error_to_status() for error classification
    - Implement get_error_message() for user-friendly error messages
    - Support all error types: upstream, timeout, connection, provider failures
    - Include comprehensive unit tests for all mappings
    
    * feat(proxy): enhance error logging with context support
    
    - Add log_error_with_context() method for detailed error recording
    - Support streaming flag, session_id, and provider_type fields
    - Remove dead_code warning from log_error() method
    - Enable comprehensive error request tracking in database
    
    * feat(proxy): implement error capture and logging in all handlers
    
    - Capture and log all failed requests in handle_messages (Claude)
    - Capture and log all failed requests in handle_gemini (Gemini)
    - Capture and log all failed requests in handle_responses (Codex)
    - Capture and log all failed requests in handle_chat_completions (Codex)
    - Record error status codes, messages, and latency for all failures
    - Generate unique session_id for each request
    - Support both streaming and non-streaming error scenarios
    
    * style: fix clippy warnings and typescript errors
    
    - Add allow(dead_code) for CircuitBreaker::get_state (reserved for future)
    - Fix all uninlined format string warnings (27 instances)
    - Use inline format syntax for better readability
    - Fix unused import and parameter warnings in ProviderActions.tsx
    - Achieve zero warnings in both Rust and TypeScript
    
    * style: apply code formatting
    
    - Remove trailing whitespace in misc.rs
    - Add trailing comma in App.tsx
    - Format multi-line className in ProviderCard.tsx
    
    * feat(proxy): add settings button to proxy panel
    
    Add configuration buttons in both running and stopped states to
    provide easy access to proxy settings dialog.
    
    * fix(speedtest): skip client build for invalid inputs
    
    * chore(clippy): fix uninlined format args
    
    * Merge branch 'main' into feature/error-request-logging
  • refactor(proxy): remove is_proxy_target in favor of failover_queue
    - Remove `is_proxy_target` field from Provider struct (Rust & TypeScript)
    - Remove related DAO methods: get_proxy_target_provider, set_proxy_target
    - Remove deprecated Tauri commands: get_proxy_targets, set_proxy_target
    - Add `is_available()` method to CircuitBreaker for availability checks
      without consuming HalfOpen probe permits (used in select_providers)
    - Keep `allow_request()` for actual request gating with permit tracking
    - Update stream_check to use failover_queue instead of is_proxy_target
    - Clean up commented-out reset circuit breaker button in ProviderActions
    - Remove unused useProxyTargets and useSetProxyTarget hooks
  • chore(i18n): remove restart prompt from provider switch notification
    Claude Code now supports hot reload, so users no longer need to restart
    the terminal after switching providers. Also removes unused `restartClaude`
    i18n string.
  • fix(proxy): invalidate health cache when proxy stops
    The previous fix (e081c75) only cleared the database records but
    didn't invalidate the React Query cache. This caused stale health
    badges to appear when restarting the proxy.
    
    Now invalidates all providerHealth queries on stop, ensuring the
    frontend fetches fresh data from the database.
  • fix(ui): prevent card jitter when health badge appears
    Increase min-height of the name row from 20px to 28px (min-h-7)
    to accommodate the health badge height, preventing layout shift
    when the badge dynamically appears.
  • fix(proxy): reset health badges when proxy stops
    Clear all provider_health records when stopping the proxy server,
    ensuring health badges reset to "healthy" state. This fixes the
    inconsistency where circuit breakers (in memory) would reset on
    stop but health badges (in database) would retain stale state.
  • fix(proxy): retry failover for all HTTP errors including 4xx
    Previously, only 429, 408, and 5xx errors triggered failover to the next
    provider. Other 4xx errors (like 400) were considered non-retryable and
    caused immediate disconnection.
    
    This was problematic because different providers have different restrictions
    (e.g., "Do not use this API outside Claude Code CLI"), and a 400 error from
    one provider doesn't mean other providers will fail.
    
    Now all upstream HTTP errors trigger failover, allowing the system to try
    all configured providers before giving up.
  • feat(proxy): sync UI when failover succeeds
    Add FailoverSwitchManager to handle provider switching after successful
    failover. This ensures the UI reflects the actual provider in use:
    
    - Create failover_switch.rs with deduplication and async switching logic
    - Pass AppHandle through ProxyService -> ProxyServer -> RequestForwarder
    - Update is_current in database when failover succeeds
    - Emit provider-switched event for frontend refresh
    - Update tray menu and live backup synchronously
    
    The switching runs asynchronously via tokio::spawn to avoid blocking
    API responses while still providing immediate UI feedback.
  • fix(usage): add fallback to provider config for usage credentials (#360)
    - Make usage script credential fields optional with provider config fallback
    - Optimize multi-plan card display: show plan count by default, expandable for details
    - Add hint text to explain credential fallback mechanism
  • feat(deeplink): 深链支持用量查询配置 (#400)
    ## 新增功能
    - 深链导入支持用量查询配置参数:
      - `usageEnabled`: 是否启用用量查询
      - `usageScript`: Base64 编码的用量查询脚本
      - `usageApiKey`: 用量查询专用 API Key
      - `usageBaseUrl`: 用量查询专用 Base URL
      - `usageAccessToken`: 访问令牌(NewAPI 模板)
      - `usageUserId`: 用户 ID(NewAPI 模板)
      - `usageAutoInterval`: 自动查询间隔(分钟)
    
    ## 修改文件
    - **mod.rs**: DeepLinkImportRequest 结构体添加用量查询字段
    - **parser.rs**: 解析 URL 中的用量查询参数
    - **provider.rs**: 构建 ProviderMeta 包含 UsageScript 配置
    - **deeplink.ts**: 添加 TypeScript 类型定义
    - **DeepLinkImportDialog.tsx**: 确认对话框显示用量查询配置
    
    ## Bug 修复
    - **formatters.ts**: 修复 formatJSON() 格式化时删除 "env" 键的问题
    
    ## 深链格式示例
    ```
    ccswitch://v1/import?resource=provider&app=claude&name=xxx&usageEnabled=true&usageScript={base64}&usageAutoInterval=30
    ```
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
  • fix(proxy): resolve HalfOpen counter underflow and config field inconsistencies
    - Fix HalfOpen counter underflow: increment half_open_requests when
      transitioning from Open to HalfOpen to prevent underflow in
      record_success/record_failure
    
    - Fix Gemini config field names: unify to GEMINI_API_KEY and
      GOOGLE_GEMINI_BASE_URL (removed GOOGLE_API_KEY and GEMINI_API_BASE)
    
    - Fix Codex proxy takeover: write base_url to config.toml instead of
      OPENAI_BASE_URL in auth.json (Codex CLI reads from config.toml)
  • fix(proxy): resolve circuit breaker state persistence and HalfOpen deadlock
    This commit addresses several critical issues in the failover system:
    
    **Circuit breaker state persistence (previous fix)**
    - Promote ProviderRouter to ProxyState for cross-request state sharing
    - Remove redundant router.rs module
    - Fix 429 errors to be retryable (rate limiting should try other providers)
    
    **Hot-update circuit breaker config**
    - Add update_circuit_breaker_configs() to ProxyServer and ProxyService
    - Connect update_circuit_breaker_config command to running circuit breakers
    - Add reset_provider_circuit_breaker() for manual breaker reset
    
    **Fix HalfOpen deadlock bug**
    - Change half_open_requests from cumulative count to in-flight count
    - Release quota in record_success()/record_failure() when in HalfOpen state
    - Prevents permanent deadlock when success_threshold > 1
    
    **Fix duplicate select_providers() call**
    - Store providers list in RequestContext, pass to forward_with_retry()
    - Avoid consuming HalfOpen quota twice per request
    - Single call to select_providers() per request lifecycle
    
    **Add per-provider retry with exponential backoff**
    - Implement forward_with_provider_retry() with configurable max_retries
    - Backoff delays: 100ms, 200ms, 400ms, etc.
  • feat(proxy): implement independent failover queue management
    Add a new failover queue system that operates independently from provider
    sortIndex, allowing users to configure failover order per app type.
    
    Backend changes:
    - Add failover_queue table to schema.rs for persistent storage
    - Create dao/failover.rs with CRUD operations for queue management
    - Add Tauri commands for queue operations (get, add, remove, reorder, toggle)
    - Refactor provider_router.rs select_providers() to use failover queue:
      - Current provider always takes first priority
      - Queue providers ordered by queue_order as fallback
      - Only providers with open circuit breakers are included
    
    Frontend changes:
    - Add FailoverQueueItem type to proxy.ts
    - Extend failover.ts API with queue management methods
    - Add React Query hooks for queue data fetching and mutations
    - Create FailoverQueueManager component with drag-and-drop reordering
    - Integrate queue management into SettingsPage under "Auto Failover"
    - Add i18n translations for zh and en locales
  • fix(proxy): auto-recover live config after abnormal exit
    When the app crashes or is force-killed while proxy mode is active,
    the live config files remain pointing to the dead proxy server with
    placeholder tokens, causing CLI tools to fail.
    
    This change adds startup detection:
    - Check `live_takeover_active` flag on app launch
    - If flag is true but proxy is not running → abnormal exit detected
    - Automatically restore live configs from database backup
    - Clear takeover flag and delete backups
    
    The recovery runs before auto-start, ensuring correct sequence even
    when proxy auto-start is enabled.
  • refactor(proxy): modularize handlers.rs to reduce code duplication
    Extract common request handling logic into dedicated modules:
    - handler_config.rs: Usage parser configurations for each API type
    - handler_context.rs: Request lifecycle context management
    - response_processor.rs: Unified streaming/non-streaming response handling
    
    Reduces handlers.rs from ~1130 lines to ~418 lines (-63%), eliminating
    repeated initialization and response processing patterns across the
    four API handlers (Claude, Codex Chat, Codex Responses, Gemini).
  • fix(proxy): update live backup when hot-switching provider in proxy mode
    When proxy is active, switching providers only updated the database flags
    but not the live backup. This caused the wrong provider config to be
    restored when stopping the proxy.
    
    Added `update_live_backup_from_provider()` method to ProxyService that
    generates backup from provider's settings_config instead of reading from
    live files (which are already taken over by proxy).
  • fix(proxy): wait for server shutdown before exiting app
    The previous cleanup logic only sent a shutdown signal but didn't wait
    for the proxy server to actually stop. This caused a race condition
    where the app would exit before cleanup completed, leaving Live configs
    in an inconsistent state.
    
    Changes:
    - Add `server_handle` field to ProxyServer to track the spawned task
    - Modify `stop()` to wait for server task completion (5s timeout)
    - Add 100ms delay before process exit to ensure I/O flush
    - Export ProxyService and fix test files that were missing proxy_service field
  • Feat/provider icon color (#385)
    * feat(ui): add color prop support to ProviderIcon component
    
    * feat(health): add stream check core functionality
    
    Add new stream-based health check module to replace model_test:
    - Add stream_check command layer with single and batch provider checks
    - Add stream_check DAO layer for config and log persistence
    - Add stream_check service layer with retry mechanism and health status evaluation
    - Add frontend HealthStatusIndicator component
    - Add frontend useStreamCheck hook
    
    This provides more comprehensive health checking capabilities.
    
    * refactor(health): replace model_test with stream_check
    
    Replace model_test module with stream_check across the codebase:
    - Remove model_test command and service modules
    - Update command registry in lib.rs to use stream_check commands
    - Update module exports in commands/mod.rs and services/mod.rs
    - Remove frontend useModelTest hook
    - Update stream_check command implementation
    
    This refactoring provides clearer naming and better separation of concerns.
    
    * refactor(db): clean up unused database tables and optimize schema
    
    Remove deprecated and unused database tables:
    - Remove proxy_usage table (replaced by proxy_request_logs)
    - Remove usage_daily_stats table (aggregation done on-the-fly)
    - Rename model_test_logs to stream_check_logs with updated schema
    - Remove related DAO methods for proxy_usage
    - Update usage_stats service to use proxy_request_logs only
    - Refactor usage_script to work with new schema
    
    This simplifies the database schema and removes redundant data storage.
    
    * refactor(ui): update frontend components for stream check
    
    Update frontend components to use stream check API:
    - Refactor ModelTestConfigPanel to use stream check config
    - Update API layer to use stream_check commands
    - Add HealthStatus type and StreamCheckResult interface
    - Update ProviderList to use new health check integration
    - Update AutoFailoverConfigPanel with stream check references
    - Improve UI layout and configuration options
    
    This completes the frontend migration from model_test to stream_check.
    
    * feat(health): add configurable test models and reasoning effort support
    
    Enhance stream check service with configurable test models:
    - Add claude_model, codex_model, gemini_model to StreamCheckConfig
    - Support reasoning effort syntax (model@level or model#level)
    - Parse and apply reasoning_effort for OpenAI-compatible models
    - Remove hardcoded model names from check functions
    - Add unit tests for model parsing logic
    - Remove obsolete model_test source files
    
    This allows users to customize which models are used for health checks.
  • Fix/misc updates (#387)
    * fix(misc): improve CLI version detection with path scanning
    
    - Extract version helper to parse semver from raw output
    - Add fallback path scanning when direct execution fails
    - Scan common npm global paths: .npm-global, .local/bin, homebrew
    - Support nvm by scanning all node versions under ~/.nvm/versions/node
    - Add platform-specific paths for macOS, Linux, and Windows
    - Ensure node is in PATH when executing CLI tools from scanned paths
    
    * fix(ui): use semantic color tokens for text foreground
    
    - Replace hardcoded gray-900/gray-100 with text-foreground in EndpointSpeedTest
    - Add text-foreground class to Input component for consistent theming
    - Ensures proper text color in both light and dark modes
  • feat(proxy): cleanup proxy server on app exit and remove unused non-takeover mode
    - Add cleanup_before_exit() to gracefully stop proxy and restore live configs
    - Handle ExitRequested event to catch Cmd+Q, Alt+F4, and tray quit
    - Use std::process::exit(0) after cleanup to avoid infinite loop
    - Remove unused start_proxy_server and stop_proxy_server commands
    - Remove unused startMutation and stopMutation from useProxyStatus hook
    - Simplify API to only expose takeover mode (start_proxy_with_takeover/stop_proxy_with_restore)
  • fix(proxy): disable auto-start on app launch by resetting enabled flag on stop
    Previously, when proxy was started, the enabled flag was set to true and
    persisted to database. However, stopping the proxy didn't reset this flag,
    causing the proxy to auto-start on every subsequent app launch.
    
    Now the enabled flag is set to false when proxy stops, ensuring the proxy
    remains off after restart unless explicitly started by the user.
  • fix(proxy): sync live config tokens to database before takeover
    When proxy takeover is activated, tokens are replaced with placeholders
    in live config files. However, the proxy reads tokens from the database,
    not from live files. If the user's token only exists in the live config
    (e.g., manually added), the database won't have it, causing auth failures.
    
    Changes:
    - Add sync_live_to_providers() to sync tokens from live configs to DB
    - Add update_provider_settings_config() DAO method for partial updates
    - Use "PROXY_MANAGED" placeholder instead of empty string to avoid
      "missing API key" warnings in Claude Code status bar
    - Integrate sync step into start_with_takeover() flow before clearing tokens
    
    The new takeover flow:
    1. setup_proxy_targets()
    2. backup_live_configs()
    3. sync_live_to_providers() <- NEW
    4. takeover_live_configs()
    5. Start proxy server
  • style(ui): improve provider card hover animation and layout
    - Increase translate offset for usage footer to prevent overlap with action buttons
    - Add ease-out timing function and extend duration to 300ms for smoother animation
    - Temporarily disable circuit breaker reset button to reduce button clutter
  • style(ui): apply emerald theme when proxy takeover is active
    - Change provider card gradient background from hover to selected state
    - Use emerald color for card hover border, selected border, and gradient
      when proxy takeover mode is active
    - Update "CC Switch" title to emerald when proxy is running
    - Update "Enable" button to emerald in proxy takeover mode
  • refactor(proxy): simplify provider selection to use is_current directly
    Changes:
    - Modify provider_router to select provider based on is_current flag
      instead of is_proxy_target queue
    - Remove proxy target toggle UI from ProviderCard
    - Remove proxyPriority and allProviders props from ProviderList
    - Remove isProxyTarget prop from ProviderHealthBadge
    - Use start_with_takeover() for auto-start to ensure proper setup
    
    This simplifies the proxy architecture by directly using the current
    provider for proxying, eliminating the need for separate proxy target
    management. Switching providers now immediately takes effect in proxy
    mode.
  • fix(proxy): resolve 404 error and auto-setup proxy targets
    Issues fixed:
    1. Route prefix mismatch - Live config was set to use /claude, /codex,
       /gemini prefixes but server only had routes without prefixes
    2. Proxy targets not auto-configured - start_with_takeover only modified
       Live config but didn't set is_proxy_target=true for current providers
    
    Changes:
    - Add prefixed routes (/claude/v1/messages, /codex/v1/*, /gemini/v1beta/*)
      to server.rs for backward compatibility
    - Remove URL prefixes from takeover_live_configs() - server can route
      by API endpoint path alone (/v1/messages=Claude, /v1/chat/completions=Codex)
    - Add setup_proxy_targets() method that automatically sets current providers
      as proxy targets when starting proxy with takeover
    - Unify proxy toggle in SettingsPage to use takeover mode (same as main UI)
    - Fix error message extraction in useProxyStatus hooks
    - Fix provider switch logic to check both takeover flag AND proxy running state