Commit Graph

1562 Commits

  • 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`).
  • test: sync stale fixtures and isolate openclaw env tests
    Three unrelated test failures surfaced after rebase:
    
    - McpFormModal expected the apps boolean set without `hermes`; Hermes MCP
      support is now wired, so the fixture must include `hermes: false`.
    - therouter Gemini preset was bumped to `gemini-3.1-pro` in a later
      commit; update the assertions to match current config.
    - openclaw_config tests mutate process-level `CC_SWITCH_TEST_HOME` and
      `HOME` inside a module-local Mutex, but hermes_config does the same
      under its own separate Mutex. Running both modules in parallel let the
      env races corrupt hermes_config's `with_test_home`. Tag the four
      env-mutating openclaw tests with `#[serial]` so they serialize across
      modules via serial_test's process-wide default key.
  • 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.
  • refactor(hermes): drop "Auto" api_mode and require explicit protocol
    Hermes' built-in api_mode detection only matches a handful of official
    endpoints (api.openai.com, api.anthropic.com, api.x.ai, AWS Bedrock);
    third-party / proxy endpoints silently fall back to chat_completions,
    which causes opaque 401/404s on Anthropic-protocol or Codex-Responses
    providers. The "Auto" option was misleading for the common third-party
    case.
    
    - Drop the "Auto" option from the API Mode dropdown; remove the
      HermesApiModeChoice sentinel type so writes always emit api_mode.
    - Default new providers and legacy entries lacking api_mode to
      chat_completions (only persisted on user save).
    - Deeplink imports now write api_mode: chat_completions explicitly
      instead of relying on URL heuristics; test renamed accordingly.
    - Rename the "Codex Responses (Copilot / OpenCode)" label to
      "OpenAI Responses" to match OpenAI's /v1/responses naming.
  • docs(readme): update SiliconFlow signup bonus to ¥16
    Sync the bonus credit amount across en/zh/ja README files to reflect
    the current SiliconFlow sponsor offer.
  • refactor(hermes): share provider-source marker constants and write guard
    After /simplify review of the P1-3 second wave, two small cleanups:
    
    - Lift the `_cc_source` / `providers_dict` magic strings out of
      ProviderCard into a shared helper (`isHermesReadOnlyProvider`) and
      named constants in hermesProviderPresets.ts. Front-end and back-end
      now document the same marker contract in two mirrored places
      instead of drifting strings.
    
    - Replace the duplicate `is_dict_only_provider` + `format!` branches
      at the top of `set_provider` / `remove_provider` with a single
      `ensure_provider_writable(config, name, verb)` guard. Future error
      copy tweaks only have to happen once.
    
    No behaviour change; all 52 hermes_config tests stay green.
  • feat(hermes): render providers: dict overlays as read-only cards
    ProviderCard now detects Hermes provider entries sourced from the
    v12+ `providers:` dict via the `_cc_source` marker that the backend
    injects, and renders a "Hermes Managed" badge beside the title.
    ProviderActions receives an `isReadOnly` prop that disables the Edit
    and Delete buttons (with a tooltip pointing the user at Hermes Web
    UI) while keeping Switch and Duplicate enabled — switching only
    touches `model.*`, and duplicate lets users fork the overlay into
    their own `custom_providers:` list.
    
    Three-locale i18n keys `provider.managedByHermes` /
    `provider.managedByHermesHint` added.
  • feat(hermes): surface providers: dict entries read-only
    Hermes v12+ migrated some provider entries from the `custom_providers:`
    list into a `providers:` dict (keyed by id). CC Switch previously
    ignored that source entirely, leaving users blind to providers they had
    configured via Hermes' own Web UI; the only feedback was a generic
    migration warning in the health banner.
    
    `get_providers()` now unions both sources, matching upstream
    `get_compatible_custom_providers` dedup order (list wins on name
    collision). Entries coming from the dict carry a `_cc_source =
    "providers_dict"` marker plus the original `provider_key`, which the
    UI layer will use to render them read-only. `set_provider` and
    `remove_provider` now refuse to touch dict-only entries, steering the
    user to Hermes Web UI. `sanitize_hermes_provider_keys` strips the UI
    markers on write so they never reach YAML.
    
    The `schema_migrated_v12` health warning copy reframes the situation:
    entries are shown read-only in CC Switch rather than invisible.
  • fix(hermes): prevent YAML pollution and drop of OAuth mcp auth
    DeepLink Hermes import was emitting camelCase (baseUrl / apiKey /
    apiMode) that the Hermes runtime does not recognise, poisoning
    `custom_providers:` entries on activation. The MCP sync path was
    also stripping `auth: oauth` on round-trip, silently downgrading
    OAuth-type servers to unauthenticated calls.
    
    The Hermes deeplink branch now emits snake_case via a dedicated
    builder; `sanitize_hermes_provider_keys` runs on both `set_provider`
    and `get_providers` so legacy DB records heal on next access.
    `HERMES_EXTRA_FIELDS` preserves `auth`. The `api_mode` dropdown gains
    `codex_responses` (Copilot / OpenCode), and the schema-migrated
    warning copy no longer hard-codes "v12" (upstream `_config_version`
    is now 19).
  • feat(hermes): memory enable switch + clearer migration warning copy
    Replaces the greyed-out "Memory is disabled" banner with a real Switch
    at the top of each memory tab. Users can now toggle Hermes' memory/user
    blobs without leaving CC Switch; the underlying write goes through the
    merge-aware `set_memory_enabled`, so budgets and external-provider
    settings survive toggle operations. The new `useToggleHermesMemoryEnabled`
    mutation invalidates the limits query so the Switch state and the
    amber disabled-hint update in lockstep.
    
    Reworks the `schema_migrated_v12` health banner copy to match the
    simplified "CC Switch only manages custom_providers" posture — it now
    tells users to reconcile migrated dict entries via Hermes Web UI,
    instead of the earlier (and now inaccurate) "CC Switch reads both".
  • feat(settings): add Hermes config dir override with data-driven dispatch
    Adds a dedicated Hermes row to the directory-override settings so users
    can point CC Switch at alternate Hermes config locations (e.g. a second
    profile directory for work/personal split). `get_config_dir` on the
    Rust side already supports hermes; this just wires up the frontend row.
    
    Wiring it through `useDirectorySettings` revealed a scaling problem:
    every supported app required five parallel ternary chains across
    `computeDefaultConfigDir`, `updateDirectory`, `browseDirectory`,
    `resetDirectory`, and `updateDirectoryState`. Replaces those with two
    lookup tables (`APP_DIRECTORY_META`, `DIRECTORY_KEY_TO_SETTINGS_FIELD`)
    so adding the next app is two entries, not fifteen edit sites.
    
    Drive-by cleanup from the same touch:
    * `resetAllDirectories` takes a `ResolvedAppDirectoryOverrides` object
      instead of five positional optional strings.
    * `setResolvedDirs` returns the same reference when the sanitized
      value is unchanged, so no-op edits don't cascade renders.
    
    Also lands all i18n updates for this series (`hermesConfigDir` and
    placeholder, Memory section's enable/disable/toggleFailed copy, and
    the reworded `schemaMigratedV12` warning) in zh/en/ja together.
  • refactor(hermes): simplify schema handling + preserve unknown provider fields
    Drops the v11→v12 providers-dict compat layer: CC Switch now only
    reads/writes `custom_providers:`, leaving migrated `providers:` dict
    entries to Hermes Web UI for reconciliation (Hermes' runtime already
    merges both shapes via `get_compatible_custom_providers`). The
    `schema_migrated_v12` health warning now points users there when a
    dict-migrated config is detected.
    
    Adds forward-compat merge to `set_provider`: when updating an existing
    entry, on-disk fields the UI payload didn't submit (e.g. Hermes-only
    `request_timeout_seconds`, `key_env`) are carried over. Without this,
    editing one field via CC Switch would silently strip the rest.
    
    Adds `set_memory_enabled` + `set_hermes_memory_enabled` Tauri command
    for the upcoming memory-switch UI. Writes go through a merge-aware
    section replacement so character budgets and external-provider fields
    survive toggle operations.
    
    Removes four dict-only helpers (`normalize_providers_dict_entry_for_read`,
    `rename_alias_key`, `json_obj_non_empty_str`,
    `resolve_provider_name_from_yaml_entry`) and the multi-section write
    helper. Simplifies `get_providers` / `remove_provider` / health scan
    back to list-only. Replaces nine obsolete dict-related tests with
    `set_provider_preserves_unknown_fields_on_update` and
    `set_memory_enabled_preserves_other_fields`.
  • feat(hermes): replace Prompts entry with Memory panel
    Hermes has no slash-prompt concept (templates live as Skills), so the
    Prompts tab for the Hermes app was always empty. Swap the toolbar Book
    button for a Brain button that opens a new Memory panel editing
    ~/.hermes/memories/{MEMORY,USER}.md — Hermes' first-class memory store
    which its Web UI exposes only as on/off toggles, never as an editor.
    
    The panel shows each file in its own tab with a character-budget bar
    read from config.yaml's nested memory.* section (memory_char_limit /
    user_char_limit, default 2200 / 1375). Edits are written atomically;
    Hermes picks them up on the next session start per MemoryStore.
    
    Also extract useDarkMode to src/hooks/useDarkMode.ts — the codebase
    already repeats the same MutationObserver pattern in 12+ places; this
    PR introduces the shared hook and uses it once, leaving the migration
    of the other copies to a follow-up.
  • refactor(hermes): delegate deep config to Hermes Web UI
    Slim the Hermes surface in CC Switch to match its core positioning —
    cross-client provider switching and shared MCP/prompts/skills — and
    delegate deep configuration (model, agent, env, skills, cron, logs)
    to the Hermes Web UI at http://127.0.0.1:9119.
    
    - Drop AgentPanel/EnvPanel/ModelPanel and their mutation commands,
      hooks, types, and i18n keys across zh/en/ja.
    - Add open_hermes_web_ui Tauri command that probes /api/status and
      launches the URL in the system browser. Hermes injects its own
      session token into the returned HTML, so CC Switch doesn't need
      to touch auth.
    - Surface the launcher from the Hermes toolbar and the health banner
      via a shared useOpenHermesWebUI() hook; the offline error code is
      defined once per side and referenced across the contract.
    - Keep read-only access to model.provider so ProviderList can still
      highlight the active supplier; apply_switch_defaults continues to
      write the top-level model section when switching providers.
    
    Net diff: +152 / -1253.
  • fix(presets): refresh stale context windows for DeepSeek and Claude 1M
    - DeepSeek V3.2 / R1 (Hermes, OpenClaw): 64K → 128K context
    - DeepSeek R1 max output: 8K → 64K (includes CoT tokens)
    - Claude Opus 4.7 / Sonnet 4.6 via OpenRouter: 200K → 1M context
  • fix(presets): refresh stale model IDs and backfill Hermes model lists
    - Bump NewAPI universal preset to Claude 4.7 / Sonnet 4.6 / Haiku 4.5 and
      Gemini 3.1; fix opusModel mistakenly pointing to Sonnet
    - Bump Gemini Native (Claude preset) to gemini-3.1-pro / gemini-3-flash
    - Bump TheRouter Gemini preset to gemini-3.1-pro
    - Backfill models[] + suggestedDefaults for 15 Hermes anthropic_messages
      presets:
        * Bailian For Coding: qwen3-coder-plus / qwen3-max
        * Kimi For Coding: kimi-for-coding
        * 13 third-party Claude proxies: claude-opus-4-7 / sonnet-4-6 /
          haiku-4-5-20251001
    - Add Claude Haiku 4.5 entry to Hermes OpenRouter model list
  • feat(hermes): switch eligible presets to chat_completions + GPT-5.4
    Migrate 18 Hermes provider presets from anthropic_messages to
    chat_completions to sidestep known upstream Hermes bugs (model-name
    dot-mangling in normalize_model_name, api_mode drop after v11->v12
    migration, and auxiliary_client OpenAI hardcode).
    
    Native providers now target each vendor's official OpenAI-compatible
    endpoint with correct model IDs: Kimi (kimi-k2.5-preview on /v1),
    Bailian (compatible-mode/v1 with Qwen3 defaults), Xiaomi MiMo, Longcat
    (/openai/v1), Zhipu GLM (/api/paas/v4), ModelScope, MiniMax, SiliconFlow,
    and Novita (/v3/openai).
    
    Aggregators (Shengsuanyun, AiHubMix, DMXAPI, Compshare, TheRouter)
    default to GPT-5.4 on chat_completions, mirroring the Codex preset
    lineup. TheRouter omits gpt-5.4-pro since that variant is Responses-only
    and Hermes implements only chat_completions. OpenRouter's existing
    openai/gpt-5 entry is bumped to openai/gpt-5.4.
    
    Claude-only proxies are left on anthropic_messages; their Codex
    counterparts use wire_api=responses, so there is no evidence their
    chat_completions endpoints serve OpenAI models.
  • chore(hermes): prune unused official presets and fix Nous endpoint
    Remove the Anthropic, OpenAI, and Google AI presets from the Hermes
    preset list. They were placeholder samples introduced when the Hermes
    module first landed and do not match the actual user paths in
    CC Switch (Claude / Codex go through OAuth, Gemini Native is its own
    adapter), and the upstream endpoints are not reachable for most of
    the target users anyway.
    
    Fix the Nous Research preset: its base_url was a fabricated domain
    (inference.nous.hermes.dev) that has never resolved. Point it at the
    real Nous Portal endpoint (inference-api.nousresearch.com/v1) and
    add apiKeyUrl so users can jump straight to portal.nousresearch.com
    to provision a key.
    
    Drop the now-orphan providerForm.presets.{anthropic,openai,googleai}
    i18n keys from zh / en / ja since no preset references them anymore.
  • feat(presets): sync Claude provider presets to Hermes
    Import 38 Claude presets into Hermes by mapping env-style
    ANTHROPIC_BASE_URL/AUTH_TOKEN to flat base_url/api_key, deriving
    api_mode from apiFormat (anthropic_messages or chat_completions),
    deduping ANTHROPIC_*_MODEL into models[], and pointing
    suggestedDefaults at ANTHROPIC_MODEL. Skip OAuth-only presets
    (Codex, Copilot), Bedrock SigV4, Gemini Native, and the three
    already shipped on the Hermes side (OpenRouter, Anthropic,
    DeepSeek). Place Shengsuanyun at the head of the Hermes array so
    the partner shows first in the preset panel.
    
    In the Claude preset list, restore Shengsuanyun back ahead of
    Gemini Native. The Gemini Native preset (#1918) was inserted
    between Claude Official and Shengsuanyun, which made the
    third_party category register first in the reduce-based grouping
    and pushed the aggregator block (and Shengsuanyun) behind it.
    
    Backfill the missing providerForm.presets translations across zh,
    en, and ja (openrouter, anthropic, openai, googleai, deepseek,
    together; plus shengsuanyun for en and ja) so existing Hermes
    preset names no longer render literal i18n keys.
  • fix(hermes): show active provider and wire add/enable/remove actions
    Switching a Hermes provider previously only fired a toast because the frontend treated Hermes as non-additive (unlike backend AppType::is_additive_mode, which lists OpenCode | OpenClaw | Hermes) and relied on the unused is_current DB flag for highlighting. Align the UI model with the backend:
    
    - Include Hermes in ProviderActions' isAdditiveMode so the main button switches between "Add" and "Remove".
    - Drive the "current" highlight from model.provider (via useHermesModelConfig) instead of the DB is_current field; model.provider is Hermes's real SSOT for the active provider.
    - Reuse OpenClaw's set-as-default button slot to expose an "Enable" action on Hermes that calls switchProvider, so providers already in config can be activated without re-adding. switch_normal + apply_switch_defaults already atomically update custom_providers and model.provider, so no backend change is needed.
    - Invalidate liveProviderIds + modelConfig + health in parallel after add/update/delete/switch via a new invalidateHermesProviderCaches helper, replacing four copies of three sequential awaits.
  • fix(hermes): persist providers under custom_providers so api_mode/model survive
    Writing to the v12+ `providers:` dict broke every anthropic_messages
    provider. Hermes `runtime_provider.py::_get_named_custom_provider` has a
    bug in its `providers:` branch: the returned entry drops `api_mode`,
    `transport`, `models`, and singular `model:`, and
    `_resolve_named_custom_runtime` then falls back to `chat_completions` —
    so an Anthropic-format endpoint receives OpenAI-format requests and
    returns 404.
    
    Keep using the legacy `custom_providers:` list; its normalization path
    (`_normalize_custom_provider_entry`) preserves every field. In addition,
    write a singular `model:` alongside the plural `models:` dict so the
    Hermes runtime and `/model` picker see the default model id.
    
    Also keep the `apply_switch_defaults` fix from the prior attempt:
    `model.provider` is always updated, and `model.default` is only
    overwritten when the new provider declares at least one model — so
    switching to an incomplete provider no longer silently no-ops.
  • feat(hermes): add API mode dropdown and per-provider model editor in form
    The Hermes provider form previously only exposed Base URL and API Key,
    forcing users to drop into the Model panel to hand-edit model IDs after
    adding a provider. Following OpenClaw's shape, the form now carries:
    
    - An API Mode selector (auto-detect / chat_completions / anthropic_messages).
      "auto" is a UI-only sentinel — selecting it deletes api_mode from the
      config so the YAML doesn't leak a redundant field.
    - A model list editor where the first row is badged as the default and
      each row has a collapsible Advanced panel for context_length and
      max_tokens. Adding/removing rows uses a UUID-keyed ref so typing in
      one input doesn't drop focus when another row is added.
    - A Fetch Models button that pulls /v1/models from the configured
      endpoint and exposes the catalog in a per-row dropdown, identical to
      OpenClaw's flow. The vendor grouping is memoized so keystrokes don't
      trigger a reduce+sort per model row — Radix DropdownMenuContent does
      not lazy-mount, so the inner JSX evaluates on every render regardless
      of whether the menu is open.
    
    Three-locale i18n keys are added together (zh/en/ja).
  • feat(hermes): bind per-provider models to top-level model: on switch
    Hermes custom_providers entries now carry an ordered models array
    (id / context_length / max_tokens) plus suggestedDefaults. The backend
    serializes the array to the YAML dict shape Hermes expects on write and
    inverts it on read, preserving insertion order via the preserve_order
    feature on serde_json.
    
    When a user switches providers, switch_normal calls apply_switch_defaults
    so the top-level model.default / model.provider follow the selected
    provider's first model. Previously switching a Hermes provider only
    shuffled custom_providers[] and left Hermes pointing at whatever
    model.provider was set before.
    
    Seven existing Hermes presets now ship with a curated models list so
    switching lands on a working default without a detour through the
    Model panel.
  • feat(copilot): strip thinking blocks before forwarding to save premium quota
    Copilot routes through OpenAI-compatible endpoints that reject Anthropic's
    thinking and redacted_thinking blocks. Previously the request would fail
    upstream, burning one premium interaction, and only then trigger
    thinking_rectifier to retry. This adds a proactive strip_thinking_blocks
    pass in the Copilot optimization pipeline (step 3.5, after tool_result
    merging). Signature fields and top-level thinking are left alone — those
    are the reactive rectifier's job on the error path.
    
    Also fixes a default-value inconsistency where CopilotOptimizerConfig's
    Default impl used "gpt-4o-mini" while the serde default function returned
    "gpt-5-mini" (aligned to gpt-5-mini, matching the reference implementation).
    
    Aligned with yuegongzi/copilot-api's /v1/messages handler behavior.
  • docs(readme): trim SSSAiCode sponsor blurb and sync across locales
    Drop the ¥0.5/$ pricing claim and monthly/paygo line from the SSSAiCode
    entry in all three READMEs, keeping only the fast-invoicing mention.
    Also collapse a duplicate blank line after the LemonData row in the JA
    README to match the ZH version.
  • fix(icons): replace placeholder Hermes icon with Nous brand artwork
    The previous Hermes icon was an inline purple "H" SVG unrelated to the
    real Hermes Agent brand. Replace it with the official Nous Research
    avatar sourced from hermes-agent/landingpage/icon-512.png, routed
    through iconUrls as a Vite URL import. The PNG is post-processed to
    strip the 4px black rectangle border (cropped inward by 6px and pasted
    back onto a 512x512 transparent canvas). Also switch defaultColor to
    black to match the monochrome artwork when the asset falls back to an
    initial glyph.
  • feat(presets): migrate all aggregator and Bedrock presets to Claude Opus 4.7
    - OpenClaw: replace opus-4-6 with opus-4-7 across 17 aggregator presets
      (id, name, primary, modelCatalog); AWS Bedrock entry rewritten to new
      SKU anthropic.claude-opus-4-7 (drops -v1 and dated suffix per official
      4.7 model card) and pricing corrected to $5/$25/$0.50/$6.25 during the
      SKU swap, aligning with schema.rs source of truth
    - OpenCode: same replacement for 13 aggregators plus
      OPENCODE_PRESET_MODEL_VARIANTS entries for @ai-sdk/amazon-bedrock and
      @ai-sdk/anthropic, plus AWS Bedrock provider models map
    - OpenRouter / TheRouter / GitHub Copilot in claudeProviderPresets use
      dot-style id; update to anthropic/claude-opus-4.7 (missed by 509d2250)
    - omo: switch agent/category recommended to opus-4-7; replace key in
      OMO_BACKGROUND_TASK_PLACEHOLDER priority map
    - hermes_config.rs: update doc comments and test fixtures to opus-4-7;
      Hermes ModelPanel placeholder and i18n defaultHint examples follow
    - i18n unspecifiedHigh category description bumped to 'Claude Opus 4.7
      max variant' to match omo recommended
    - Test fixtures updated: therouter preset assertion and opencode Bedrock
      variant lookup now check for opus-4-7
    - Sonnet 4.6 / Haiku 4.5 untouched - no official 4.7 release for them
  • feat(pricing): add Claude Opus 4.7 with adaptive thinking and Bedrock SKU
    - Seed claude-opus-4-7 pricing (same tier as 4.6: $5 / $25 / $0.50 /
      $6.25 per million tokens). Relies on incremental INSERT OR IGNORE
      seeding; no SCHEMA_VERSION bump needed.
    - Whitelist opus-4-7 in thinking optimizer so it uses adaptive
      thinking + max effort + 1M context beta, matching 4.6 behavior.
    - Bump default OPUS model in PIPELLM and AWS Bedrock (AKSK / API Key)
      presets to 4.7. Bedrock SKU drops the -v1 suffix per the official
      4.7 model card (anthropic.claude-opus-4-7 and
      global.anthropic.claude-opus-4-7).
  • feat(usage-script): add User-Agent header to New API template
    Align the New API usage query template with the GENERAL template by
    including "User-Agent: cc-switch/1.0" in its request headers, so
    cc-switch requests are identifiable in provider server logs and less
    likely to be blocked by UA-based rate limiting on some New API
    deployments.
  • feat(claude): upgrade effort toggle from "high" to "max"
    Per Anthropic's effort parameter docs, "high" is the API default and
    setting effortLevel="high" is equivalent to omitting the field entirely.
    The toggle previously produced no effect.
    
    Claude Opus 4.6, Sonnet 4.6, and Opus 4.7 now support a "max" level
    that enables unconstrained reasoning. Rename the checkbox (effortHigh
    -> effortMax) and write effortLevel="max" when toggled on. Existing
    "high" values in user configs are left untouched.
    
    Updates zh/en/ja locales and user-manual entries accordingly.
  • chore(lint): pin Rust toolchain to 1.95 and adopt clippy 1.95 suggestions
    - Add rust-toolchain.toml to align local and CI Rust versions, eliminating
      clippy roulette caused by `dtolnay/rust-toolchain@stable` drift.
    - Fix 9 clippy 1.95 findings introduced by Hermes Phase 4-8 modules:
      * 4x unnecessary_sort_by  -> sort_by_key (with Reverse for desc)
      * 3x collapsible_match    -> match guards
      * 1x while_let_loop       -> while let
      * 1x useless_conversion   -> drop redundant .into_iter()
  • fix: address Hermes review findings (5 medium issues)
    - Add missing Hermes MCP import on first launch (lib.rs)
    - Add Hermes branch in ProviderForm defaultValues fallback
    - Include Hermes in session manager subtitle (zh/en/ja)
    - Rename check_openclaw_stream to check_additive_app_stream
    - Cache parsed HERMES_DEFAULT_CONFIG to avoid repeated JSON.parse
  • feat: implement Hermes session manager with SQLite + JSONL support (Phase 6)
    - Add hermes.rs session provider with dual-source scanning:
      SQLite (state.db) as primary, JSONL transcripts as fallback
    - Dynamic schema discovery via PRAGMA table_info for SQLite resilience
    - Use read_head_tail_lines for efficient JSONL metadata extraction
      (head 30 lines for metadata, tail 10 for last_active_at)
    - Support both flat and nested JSONL message formats
    - Add SQLite session loading and transactional deletion
    - Register hermes in parallel session scan (thread::scope)
    - Add "hermes" to frontend ProviderFilter type
    - 7 unit tests covering JSONL parsing, SQLite source parsing, deletion
  • feat: add Hermes UI components, presets, and config panels (Phase 8)
    - Add 7 provider presets (OpenRouter, Anthropic, OpenAI, Google, DeepSeek, Together, Nous)
    - Create HermesFormFields + useHermesFormState for provider form integration
    - Create Model/Agent/Env config panels with save/load functionality
    - Create HermesHealthBanner for config warnings
    - Add hermes icon (violet winged H) to icon system
    - Integrate into App.tsx: 3 new view types (hermesModel/hermesAgent/hermesEnv),
      sidebar buttons (Brain/Bot/KeyRound), health banner, session support
    - Integrate into ProviderForm: presets, form state, key validation, rendering
    - Integrate into AddProviderDialog: universal tab exclusion, providerKey, base_url extraction
    - Add i18n keys for all Hermes UI (zh/en/ja)
  • feat: add Hermes frontend types, API layer, and hooks (Phase 7)
    - Add "hermes" to AppId union type and all exhaustive Record<AppId>
    - Add HermesModelConfig, HermesAgentConfig, HermesEnvConfig types
    - Add hermes field to VisibleApps, McpApps, ProxyTakeoverStatus
    - Create src/lib/api/hermes.ts with Tauri invoke wrappers
    - Create src/hooks/useHermes.ts with 5 query + 3 mutation hooks
    - Register hermes in APP_IDS, APP_ICON_MAP (violet color scheme)
    - Split MCP_SKILLS_APP_IDS into MCP_APP_IDS (includes hermes) and
      SKILLS_APP_IDS (excludes hermes, since Hermes has no Skills support)
    - Wire hermes additive-mode into App.tsx (remove/duplicate handlers),
      ProviderList.tsx (live provider ID query + In Config badge),
      mutations.ts (cache invalidation on switch/add/delete)
    - Add Hermes checkbox to McpFormModal
    - Add basic hermes i18n keys (en/zh/ja)
  • feat: implement Hermes MCP sync module (Phase 4)
    Add mcp/hermes.rs with bidirectional MCP format conversion:
    - convert_to_hermes_format: strip type field, infer from command/url
    - convert_from_hermes_format: infer type, strip Hermes-specific fields
    - Merge-on-write: existing Hermes fields (tools, sampling, timeout,
      roots, enabled) preserved when user has customized them
    - update_mcp_servers_yaml: closure-based read-modify-write under write
      lock to prevent TOCTOU races in concurrent sync operations
    - 9 unit tests for format conversion and merge logic
    
    Wire up all MCP service dispatch:
    - Replace Hermes TODO stubs with real sync/remove calls
    - Remove Hermes from sync_all_enabled skip list
    - Enable deep link hermes MCP flag (apps.hermes = true)
    - Add Hermes import to import_mcp_from_apps command
  • feat: implement Hermes config module and commands (Phase 3)
    Add hermes_config.rs (~1190 lines) with YAML section-level replacement
    that preserves comments and formatting in unmanaged sections:
    - Type definitions: HermesModelConfig, HermesAgentConfig, HermesEnvConfig
    - YAML section finder (find_yaml_section_range) with column-0 key detection
    - Provider CRUD on custom_providers array (indexed by name field)
    - Model/Agent config get/set via yaml<->json conversion
    - .env dotenv read/write preserving comments and line ordering
    - Health check, backup with rotation, write lock (OnceLock<Mutex>)
    - MCP section access stubs for Phase 4
    - 19 unit tests
    
    Add commands/hermes.rs with 10 Tauri commands registered in lib.rs.
    Replace all Hermes TODO stubs in services/provider/live.rs with real
    implementations (import, remove, write-to-live, read-live-settings).
  • feat: add database migration v9→v10 for Hermes support (Phase 2)
    - Bump SCHEMA_VERSION from 9 to 10
    - Add enabled_hermes column to mcp_servers and skills tables
    - Add migrate_v9_to_v10 with table_exists guard for skills (may not
      exist in databases migrated from very old versions)
    - Update dao/mcp.rs to fully read/write enabled_hermes in all queries
    - Update dao/skills.rs: don't SELECT enabled_hermes (Hermes doesn't
      support Skills yet), keep column indices clean
  • feat: add Hermes Agent as 6th supported app type (Phase 1)
    Register AppType::Hermes across the entire Rust backend:
    - Add Hermes variant to AppType enum with additive mode and MCP support
    - Add hermes field to McpApps, SkillApps, CommonConfigSnippets, and all
      per-app structs (McpRoot, PromptRoot, VisibleApps, AppSettings)
    - Create minimal hermes_config.rs with get_hermes_dir() respecting
      settings override, matching the pattern of other app config modules
    - Update all match arms in commands, services, deeplink, proxy, mcp,
      session_manager, and test files
    - Extract shared build_additive_app_settings() to eliminate duplication
      between OpenClaw and Hermes deep link handling
    - Combine identical OpenClaw/Hermes proxy match arms into unified arms
  • fix: surface backend error details in proxy toast messages
    The takeover.failed i18n template lacked the {{detail}} placeholder
    and three useProxyStatus onError callbacks omitted the detail variable,
    so proxy start/stop/takeover failures all displayed a generic message
    regardless of the underlying cause.
  • fix: remove ANTHROPIC_REASONING_MODEL to decouple thinking from model selection (#2081)
    ANTHROPIC_REASONING_MODEL was a non-official env var that forced all
    requests with thinking params to use a single "reasoning model",
    overriding the user's /model selection. Since new Claude Code versions
    send adaptive thinking by default, this caused /model to silently fail.
    
    - Remove reasoning_model field and has_thinking_enabled() from model_mapper
    - Simplify map_model() to pure type-based matching (haiku/sonnet/opus)
    - Remove reasoning model UI field from provider form
    - Retain ANTHROPIC_REASONING_MODEL in ENV_EXCLUDES and override-key
      cleanup lists so legacy configs don't leak into common config
  • fix(claude-plugin): sync current provider config to settings.json (#1905)
    * fix(claude-plugin): sync current provider config to settings.json on toggle enable
    
    - Extract syncClaudePluginIfChanged to share logic between autoSaveSettings and saveSettings
    - Fix P1: enableClaudePluginIntegration toggle in General tab now actually syncs ~/.claude/settings.json
    - Fix P2: check syncCurrentProvidersLiveSafe() return value and show toast on failure
    - Fix P3: sync providers on both enable and disable, not just enable
    - Fix P4: avoid double syncCurrentProvidersLiveSafe when plugin toggle + dir change happen together
    - Remove duplicate comment
    - Add missing providersApi.getCurrent/getAll mocks in tests
    
    * style: reformat after rebase onto main
    
    Prettier flagged a line-break introduced by the openclaw directory
    change (from main) after rebase.
    
    * fix(claude-plugin): read prev enabled state from live cache to avoid stale closure
    
    syncClaudePluginIfChanged compared enabled against data?.enableClaudePluginIntegration
    captured in a useCallback closure. After invalidateQueries + refetch, the React
    Query cache is up to date, but the consuming hook's closure does not see the new
    value until React re-renders. Quick on->off toggles could therefore skip
    applyClaudePluginConfig, leaving ~/.claude/config.json in the previously enabled
    state even though settings.json was persisted as disabled.
    
    Read the previous value synchronously from queryClient.getQueryData(["settings"])
    before saveMutation.mutateAsync(), then pass it to the helper as prevEnabled.
    getQueryData bypasses the closure and reflects the live cache at call time.
    
    Test covers the race: closure data stays at false while the cache reports true;
    the helper must still call applyClaudePluginConfig({ official: true }).
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix: common config checkbox state not persisting for Codex/Claude/Gemini (#2191)
    修复:Codex/Claude/Gemini 通用配置勾选状态无法正确保存的问题
    
    Co-authored-by: 曾兴 <zengx@gantsoftware.com>
  • Add StepFun and StepFun en Step Plan presets (#2155)
    * Add StepFun CN and EN presets
    
    * Add StepFun 2603 model presets
    
    * Make StepFun 2603 the default model
    
    * Revert StepFun branding assets
  • Fix Ghostty session restore launch path (#1976)
    * fix: launch Ghostty via shell command
    
    Use Ghostty's shell execution path instead of injecting raw terminal input so Claude resume commands run reliably when opening a session terminal.
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    * fix(ghostty): pass cwd via --working-directory instead of shell string
    
    Use Ghostty's native --working-directory flag to set the working
    directory, matching the pattern used by Alacritty. This avoids shell
    expansion of special characters (e.g. $VAR, spaces) in project paths.
    
    The command is now passed directly to -c without a cd prefix.
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>