Commit Graph

12 Commits

  • feat(opencode): complete Phase 4 - MCP sync module
    Add mcp/opencode.rs with format conversion between CC Switch and OpenCode:
    - stdio ↔ local type conversion
    - command+args ↔ command array format
    - env ↔ environment field mapping
    - sse/http ↔ remote type conversion
    
    Public API:
    - sync_enabled_to_opencode: Batch sync all enabled servers
    - sync_single_server_to_opencode: Sync individual server
    - remove_server_from_opencode: Remove from live config
    - import_from_opencode: Import servers from OpenCode config
    
    Also fix test files to include new opencode field in McpApps struct.
    All 4 unit tests pass for format conversion.
  • 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(skill): implement recursive scanning for skill repositories (#309)
    Add recursive directory scanning to discover SKILL.md files in nested
    directories. When a SKILL.md is found, treat sibling directories as
    functional folders rather than separate skills.
  • Merge feat/sqlite-migration: add database schema migration system
    This merge brings the SQLite migration system from feat/sqlite-migration branch:
    
    ## New Features
    - Schema version control with SCHEMA_VERSION constant
    - Automatic migration of missing columns for providers table
    - Dry-run validation mode for schema compatibility checks
    - JSON→SQLite migration feature gate (CC_SWITCH_ENABLE_JSON_DB_MIGRATION)
    - Settings reload mechanism after imports
    
    ## Test Updates
    - Updated tests to use SQLite database instead of config.json
    - Removed obsolete import_config_from_path tests (replaced by db.import_sql)
    - Fixed MCP tests to use unified McpServer structure (v3.7.0+)
    - Updated provider switch tests to reflect no-backfill behavior
    - Adjusted error type matching for new error variants
  • test: migrate tests to SQLite database architecture
    This commit refactors all tests to work with the new database-based
    architecture, replacing the previous JSON config approach.
    
    Key changes:
    - Add Database export to lib.rs for test access
    - Create test helper functions in support.rs:
      - create_test_state(): Creates empty test state with fresh DB
      - create_test_state_with_config(): Migrates JSON config to DB
    - Fix environment isolation in provider_service tests:
      - provider_service_switch_missing_provider_returns_error
      - provider_service_switch_codex_missing_auth_returns_error
    - Replace ignored export tests with working alternatives:
      - export_sql_writes_to_target_path (tests Database::export_sql)
      - export_sql_returns_error_for_invalid_path (tests error handling)
    - Update error type matching to align with current implementation
    
    All tests now:
    - Use isolated test environments (test_mutex + reset_test_fs)
    - Access data via Database API instead of RwLock<MultiAppConfig>
    - Work with SQLite persistence layer
    - Pass without environment pollution or race conditions
    
    Fixes test compilation errors after database migration.
  • fix(mcp): correct Codex MCP configuration format to [mcp_servers]
    BREAKING CHANGE: The [mcp.servers] format was completely incorrect and not
    any official Codex format. The only correct format is [mcp_servers] at the
    top level of config.toml.
    
    Changes:
    - Remove incorrect [mcp.servers] nested table support
    - Always use [mcp_servers] top-level table (official Codex format)
    - Auto-migrate and cleanup erroneous [mcp.servers] entries on write
    - Preserve error-tolerant import for migrating old incorrect configs
    - Simplify sync logic by removing format selection branches (~60 lines)
    - Update all documentation and tests to reflect correct format
    - Add warning logs when detecting and cleaning incorrect format
    
    Backend (Rust):
    - mcp.rs: Simplify sync_enabled_to_codex by removing Target enum
    - mcp.rs: sync_single_server_to_codex now always uses [mcp_servers]
    - mcp.rs: remove_server_from_codex cleans both locations
    - mcp.rs: Update import_from_codex comments to clarify format status
    - tests: Rename test to sync_enabled_to_codex_migrates_erroneous_*
    - tests: Update assertions to verify migration behavior
    
    Frontend (TypeScript):
    - tomlUtils.ts: Prioritize [mcp_servers] format in parsing
    - tomlUtils.ts: Update error messages to guide correct format
    
    Documentation:
    - README.md: Correct MCP format reference to [mcp_servers]
    - CLAUDE.md: Add comprehensive format specification with examples
    
    All 79 tests pass. This ensures backward compatibility while enforcing
    the correct Codex official standard going forward.
    
    Refs: https://github.com/openai/codex/issues/3441
  • fix(mcp): migrate import functions to unified v3.7.0 structure
    - Rewrite import_from_claude/codex/gemini to write directly to mcp.servers
    - Implement skip-on-error strategy for fault tolerance (single invalid item no longer aborts entire batch)
    - Smart merge logic: existing servers only enable corresponding app, preserve other configs
    - Remove deprecated markers from service layer
    - Export McpApps type for test usage
    - Update mcp_commands tests to use unified structure
    
    Fixes runtime import issue where data was written to legacy structure (mcp.claude/codex.servers)
    but unified panel reads from new structure (mcp.servers), causing "imported but invisible" bug.
  • refactor(backend): implement transaction mechanism and i18n errors for provider service
    This commit completes phase 4 service layer extraction by introducing:
    
    1. **Transaction mechanism with 2PC (Two-Phase Commit)**:
       - Introduced `run_transaction()` wrapper with snapshot-based rollback
       - Implemented `LiveSnapshot` enum to capture and restore live config files
       - Added `PostCommitAction` to separate config.json persistence from live file writes
       - Applied to critical operations: add, update, switch providers
       - Ensures atomicity: memory + config.json + live files stay consistent
    
    2. **Internationalized error handling**:
       - Added `AppError::Localized` variant with key + zh + en messages
       - Implemented `AppError::localized()` helper function
       - Migrated 24 error sites to use i18n-ready errors
       - Enables frontend to display errors in user's preferred language
    
    3. **Concurrency optimization**:
       - Fixed `get_custom_endpoints()` to use read lock instead of write lock
       - Ensured async IO operations (usage query) execute outside lock scope
       - Added defensive RAII lock management with explicit scope blocks
    
    4. **Code organization improvements**:
       - Reduced commands/provider.rs from ~800 to ~320 lines (-60%)
       - Expanded services/provider.rs with transaction infrastructure
       - Added unit tests for validation and credential extraction
       - Documented legacy file cleanup logic with inline comments
    
    5. **Backfill mechanism refinement**:
       - Ensured live config is synced back to memory before switching
       - Maintains SSOT (Single Source of Truth) architecture principle
       - Handles Codex dual-file (auth.json + config.toml) atomically
    
    Breaking changes: None (internal refactoring only)
    Performance: Improved read concurrency, no measurable overhead from snapshots
    Test coverage: Added validation tests, updated service layer tests
  • refactor(backend): extract MCP service layer with snapshot isolation
    Extract all MCP business logic from command layer into `services/mcp.rs`,
    implementing snapshot isolation pattern to optimize lock granularity after
    RwLock migration in Phase 5.
    
    ## Key Changes
    
    ### Service Layer (`services/mcp.rs`)
    - Add `McpService` with 7 methods: `get_servers`, `upsert_server`,
      `delete_server`, `set_enabled`, `sync_enabled`, `import_from_claude`,
      `import_from_codex`
    - Implement snapshot isolation: acquire write lock only for in-memory
      modifications, clone config snapshot, release lock, then perform file I/O
      with snapshot
    - Use conditional cloning: only clone config when sync is actually needed
      (e.g., when `enabled` flag is true or `sync_other_side` is requested)
    
    ### Command Layer (`commands/mcp.rs`)
    - Reduce to thin wrappers: parse parameters and delegate to `McpService`
    - Remove all `*_internal` and `*_test_hook` functions (-94 lines)
    - Each command now 5-10 lines (parameter parsing + service call + error mapping)
    
    ### Core Logic Refactoring (`mcp.rs`)
    - Rename `set_enabled_and_sync_for` → `set_enabled_flag_for`
    - Remove file sync logic from low-level function, move sync responsibility
      to service layer for better separation of concerns
    
    ### Test Adaptation (`tests/mcp_commands.rs`)
    - Replace test hooks with direct `McpService` calls
    - All 5 MCP integration tests pass
    
    ### Additional Fixes
    - Add `Default` impl for `AppState` (clippy suggestion)
    - Remove unnecessary auto-deref in `commands/provider.rs` and `lib.rs`
    - Update Phase 4/5 progress in `BACKEND_REFACTOR_PLAN.md`
    
    ## Performance Impact
    
    **Before**: Write lock held during file I/O (~10ms), blocking all readers
    **After**: Write lock held only for memory ops (~100μs), file I/O lock-free
    
    Estimated throughput improvement: ~2x in high-concurrency read scenarios
    
    ## Testing
    
    -  All tests pass: 5 MCP commands + 7 provider service tests
    -  Zero clippy warnings with `-D warnings`
    -  No behavioral changes, maintains original save semantics
    
    Part of Phase 4 (Service Layer Abstraction) of backend refactoring roadmap.
  • refactor(backend): phase 5 - optimize concurrency with RwLock and async IO
    Replace Mutex with RwLock for AppState.config to enable concurrent reads,
    improving performance for tray menu building and query operations that
    previously blocked each other unnecessarily.
    
    Key changes:
    - Migrate AppState.config from Mutex<MultiAppConfig> to RwLock<MultiAppConfig>
    - Distinguish read-only operations (read()) from mutations (write()) across
      all command handlers and service layers
    - Offload blocking file I/O in import/export commands to spawn_blocking threads,
      minimizing lock hold time and preventing main thread blocking
    - Extract load_config_for_import() to separate I/O logic from state updates
    - Update all integration tests to use RwLock semantics
    
    Performance impact:
    - Concurrent reads: Multiple threads can now query config simultaneously
      (tray menu, provider list, MCP config)
    - Reduced contention: Write locks only acquired during actual mutations
    - Non-blocking I/O: Config import/export no longer freezes UI thread
    
    All existing tests pass with new locking semantics.
  • refactor(backend): phase 4 - add test hooks and extend service layer
    - Extract internal functions in commands/mcp.rs and commands/provider.rs
      to enable unit testing without Tauri context
    - Add test hooks: set_mcp_enabled_test_hook, import_mcp_from_claude_test_hook,
      import_mcp_from_codex_test_hook, import_default_config_test_hook
    - Migrate error types from String to AppError for precise error matching in tests
    - Extend ProviderService with delete() method to unify Codex/Claude cleanup logic
    - Add comprehensive test coverage:
      - tests/mcp_commands.rs: command-level tests for MCP operations
      - tests/provider_service.rs: service-level tests for switch/delete operations
    - Run cargo fmt to fix formatting issues (EOF newlines)
    - Update BACKEND_REFACTOR_PLAN.md to mark phase 3 complete