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