diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5788a2f0a..b31f0cf01 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -635,6 +635,14 @@ pub fn run() { Ok(_) => log::debug!("○ No OpenCode MCP servers found to import"), Err(e) => log::warn!("✗ Failed to import OpenCode MCP: {e}"), } + + match crate::services::mcp::McpService::import_from_hermes(&app_state) { + Ok(count) if count > 0 => { + log::info!("✓ Imported {count} MCP server(s) from Hermes"); + } + Ok(_) => log::debug!("○ No Hermes MCP servers found to import"), + Err(e) => log::warn!("✗ Failed to import Hermes MCP: {e}"), + } } // 4. 导入提示词文件(表空时触发) diff --git a/src-tauri/src/services/stream_check.rs b/src-tauri/src/services/stream_check.rs index d85a79912..e7565939b 100644 --- a/src-tauri/src/services/stream_check.rs +++ b/src-tauri/src/services/stream_check.rs @@ -684,7 +684,7 @@ impl StreamCheckService { let result = match app_type { AppType::OpenClaw => { - Self::check_openclaw_stream( + Self::check_additive_app_stream( &client, provider, &model_to_test, @@ -704,8 +704,7 @@ impl StreamCheckService { .await } AppType::Hermes => { - // Hermes uses the same check path as OpenClaw for now - Self::check_openclaw_stream( + Self::check_additive_app_stream( &client, provider, &model_to_test, @@ -819,7 +818,7 @@ impl StreamCheckService { /// - `anthropic-messages` → check_claude_stream + api_format="anthropic" (ClaudeAuth 策略) /// - `google-generative-ai` → check_gemini_stream (Google API Key 策略) /// - `bedrock-converse-stream` → 不支持(需要 AWS SigV4 签名) - async fn check_openclaw_stream( + async fn check_additive_app_stream( client: &Client, provider: &Provider, model: &str, @@ -829,7 +828,7 @@ impl StreamCheckService { // 自定义认证头(如 Longcat 的 `apikey` 头)不走标准 Bearer, // 具体头名由 OpenClaw 网关内部决定,cc-switch 无法准确构造, // 因此直接返回友好错误而不是让用户看到一个误导性的 401。 - if Self::openclaw_uses_auth_header(provider) { + if Self::additive_app_uses_auth_header(provider) { return Err(AppError::localized( "openclaw_auth_header_not_supported", "该供应商使用自定义认证头,暂不支持流式健康检查。建议直接通过 OpenClaw 测试。", @@ -922,8 +921,8 @@ impl StreamCheckService { } } - /// 判断 OpenClaw 供应商是否使用自定义认证头(`authHeader: true`) - fn openclaw_uses_auth_header(provider: &Provider) -> bool { + /// 判断 additive-mode 供应商是否使用自定义认证头(`authHeader: true`) + fn additive_app_uses_auth_header(provider: &Provider) -> bool { provider .settings_config .get("authHeader") @@ -1040,7 +1039,7 @@ impl StreamCheckService { .await } Some("@ai-sdk/anthropic") => { - // 见 check_openclaw_stream 对 anthropic-messages 的注释: + // 见 check_additive_app_stream 对 anthropic-messages 的处理: // 用 ClaudeAuth(Bearer-only)兼容中转服务。 let auth = AuthInfo::new(api_key, AuthStrategy::ClaudeAuth); Self::check_claude_stream( @@ -1420,24 +1419,24 @@ mod tests { } #[test] - fn test_openclaw_uses_auth_header_true() { + fn test_additive_app_uses_auth_header_true() { let p = make_provider(serde_json::json!({ "baseUrl": "https://api.longcat.chat/v1", "apiKey": "k", "api": "openai-completions", "authHeader": true, })); - assert!(StreamCheckService::openclaw_uses_auth_header(&p)); + assert!(StreamCheckService::additive_app_uses_auth_header(&p)); } #[test] - fn test_openclaw_uses_auth_header_default_false() { + fn test_additive_app_uses_auth_header_default_false() { let p = make_provider(serde_json::json!({ "baseUrl": "https://api.deepseek.com/v1", "apiKey": "k", "api": "openai-completions", })); - assert!(!StreamCheckService::openclaw_uses_auth_header(&p)); + assert!(!StreamCheckService::additive_app_uses_auth_header(&p)); } #[test] diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index fba7b2624..63bf2a0eb 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -99,6 +99,7 @@ import { OPENCLAW_DEFAULT_CONFIG, normalizePricingSource, } from "./helpers/opencodeFormUtils"; +import { HERMES_DEFAULT_CONFIG } from "./hooks/useHermesFormState"; import { resolveManagedAccountId } from "@/lib/authBinding"; import { useOpenClawLiveProviderIds } from "@/hooks/useOpenClaw"; import { useHermesLiveProviderIds } from "@/hooks/useHermes"; @@ -261,7 +262,9 @@ export function ProviderForm({ ? OPENCODE_DEFAULT_CONFIG : appId === "openclaw" ? OPENCLAW_DEFAULT_CONFIG - : CLAUDE_DEFAULT_CONFIG, + : appId === "hermes" + ? HERMES_DEFAULT_CONFIG + : CLAUDE_DEFAULT_CONFIG, icon: initialData?.icon ?? "", iconColor: initialData?.iconColor ?? "", }), diff --git a/src/components/providers/forms/hooks/useHermesFormState.ts b/src/components/providers/forms/hooks/useHermesFormState.ts index a62f4fa49..09f5d135e 100644 --- a/src/components/providers/forms/hooks/useHermesFormState.ts +++ b/src/components/providers/forms/hooks/useHermesFormState.ts @@ -12,12 +12,14 @@ interface UseHermesFormStateParams { getSettingsConfig: () => string; } +const HERMES_DEFAULT_CONFIG_OBJ = { + name: "", + base_url: "", + api_key: "", +} as const; + export const HERMES_DEFAULT_CONFIG = JSON.stringify( - { - name: "", - base_url: "", - api_key: "", - }, + HERMES_DEFAULT_CONFIG_OBJ, null, 2, ); @@ -46,8 +48,7 @@ function parseHermesField( if (initialData?.settingsConfig) { return (initialData.settingsConfig[field] as T) || fallback; } - const config = JSON.parse(HERMES_DEFAULT_CONFIG); - return (config[field] as T) || fallback; + return ((HERMES_DEFAULT_CONFIG_OBJ as Record)[field] as T) || fallback; } catch { return fallback; } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 116b3d1b6..0e64ff3c8 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -663,7 +663,7 @@ }, "sessionManager": { "title": "Session Manager", - "subtitle": "Manage Claude Code, Codex, OpenCode, OpenClaw and Gemini CLI sessions", + "subtitle": "Manage Claude Code, Codex, OpenCode, OpenClaw, Hermes and Gemini CLI sessions", "searchPlaceholder": "Search by content, directory, or ID", "searchSessions": "Search sessions", "providerFilterAll": "All", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 0b6b380b1..b0101efe7 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -663,7 +663,7 @@ }, "sessionManager": { "title": "セッション管理", - "subtitle": "Claude Code / Codex / OpenCode / OpenClaw / Gemini CLI のセッションを管理", + "subtitle": "Claude Code / Codex / OpenCode / OpenClaw / Hermes / Gemini CLI のセッションを管理", "searchPlaceholder": "内容・ディレクトリ・ID で検索", "searchSessions": "セッションを検索", "providerFilterAll": "すべて", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 98624e93a..e44ebaf11 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -663,7 +663,7 @@ }, "sessionManager": { "title": "会话管理", - "subtitle": "管理 Claude Code、Codex、OpenCode、OpenClaw 与 Gemini CLI 会话记录", + "subtitle": "管理 Claude Code、Codex、OpenCode、OpenClaw、Hermes 与 Gemini CLI 会话记录", "searchPlaceholder": "搜索会话内容、目录或 ID", "searchSessions": "搜索会话", "providerFilterAll": "全部",