From cdcc423122380c60cb4238bbb05b0d1e93c42cc4 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 23 Apr 2026 11:59:25 +0800 Subject: [PATCH] refactor(hermes): drop config health check scanner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Hermes config.yaml schema has stabilized and users have migrated to the current provider fields, so the value of scanning for model.provider dangling references, custom_providers shape errors, v12 migration residue etc. no longer justifies the maintenance surface — and the scan produces false positives when users keep some providers under Hermes' v12+ providers: dict (Hermes' runtime merges both shapes, but CC Switch's scanner only looked at the list form). Removes the whole HermesHealthWarning type, scan_hermes_config_health command, HermesHealthBanner React component, useHermesHealth hook, warnings field on HermesWriteOutcome, and the three helper functions (yaml_as_non_empty_str, collect_mapping_string_keys, hermes_warning) that only served the scanner. Drops the matching i18n keys in zh/en/ja and the fixInWebUI button label that only the banner used. --- src-tauri/src/commands/hermes.rs | 6 - src-tauri/src/hermes_config.rs | 341 ------------------- src-tauri/src/lib.rs | 1 - src/App.tsx | 15 +- src/components/hermes/HermesHealthBanner.tsx | 127 ------- src/hooks/useHermes.ts | 11 - src/i18n/locales/en.json | 14 - src/i18n/locales/ja.json | 14 - src/i18n/locales/zh.json | 14 - src/lib/api/hermes.ts | 11 +- src/types.ts | 11 - 11 files changed, 4 insertions(+), 561 deletions(-) delete mode 100644 src/components/hermes/HermesHealthBanner.tsx diff --git a/src-tauri/src/commands/hermes.rs b/src-tauri/src/commands/hermes.rs index ee1aebe34..4c7809cc3 100644 --- a/src-tauri/src/commands/hermes.rs +++ b/src-tauri/src/commands/hermes.rs @@ -40,12 +40,6 @@ pub fn get_hermes_live_provider( hermes_config::get_provider(&providerId).map_err(|e| e.to_string()) } -/// Scan config.yaml for known configuration hazards. -#[tauri::command] -pub fn scan_hermes_config_health() -> Result, String> { - hermes_config::scan_hermes_config_health().map_err(|e| e.to_string()) -} - // ============================================================================ // Model Configuration Commands // ============================================================================ diff --git a/src-tauri/src/hermes_config.rs b/src-tauri/src/hermes_config.rs index 4e8b5bd7c..30f24267c 100644 --- a/src-tauri/src/hermes_config.rs +++ b/src-tauri/src/hermes_config.rs @@ -72,24 +72,12 @@ fn hermes_write_lock() -> &'static Mutex<()> { // Type Definitions // ============================================================================ -/// Hermes 健康检查警告 -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct HermesHealthWarning { - pub code: String, - pub message: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, -} - /// Hermes 写入结果 #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct HermesWriteOutcome { #[serde(skip_serializing_if = "Option::is_none")] pub backup_path: Option, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub warnings: Vec, } /// Hermes model section config @@ -343,8 +331,6 @@ fn write_yaml_section_to_config_locked( atomic_write(&config_path, new_raw.as_bytes())?; - let warnings = scan_hermes_health_internal(&new_raw); - log::debug!( "Hermes config section '{}' written to {:?}", section_key, @@ -352,7 +338,6 @@ fn write_yaml_section_to_config_locked( ); Ok(HermesWriteOutcome { backup_path: backup_path.map(|p| p.display().to_string()), - warnings, }) } @@ -486,28 +471,6 @@ fn denormalize_provider_models_for_read(config: &mut serde_json::Value) { } } -/// Borrow a YAML scalar as a non-empty trimmed `&str`, or `None` when the -/// value is absent, non-string, or blank after trimming. -fn yaml_as_non_empty_str(v: &serde_yaml::Value) -> Option<&str> { - v.as_str().map(str::trim).filter(|s| !s.is_empty()) -} - -/// Extract string keys from a YAML mapping as owned strings, dropping non-string keys. -fn collect_mapping_string_keys(m: &serde_yaml::Mapping) -> Vec { - m.iter() - .filter_map(|(k, _)| k.as_str().map(str::to_string)) - .collect() -} - -/// Boilerplate-free constructor for [`HermesHealthWarning`]. -fn hermes_warning(code: &str, message: String, path: &str) -> HermesHealthWarning { - HermesHealthWarning { - code: code.to_string(), - message, - path: Some(path.to_string()), - } -} - /// Marker field injected on provider payloads sourced from Hermes v12+ /// `providers:` dict. CC Switch treats those as read-only — writes have to /// go through Hermes' own Web UI to keep its overlay semantics intact. @@ -856,160 +819,6 @@ pub fn apply_switch_defaults( set_model_config(&merged) } -// ============================================================================ -// Health Check -// ============================================================================ - -/// Scan Hermes config for known configuration hazards. -/// -/// Parse failures are reported as warnings (not errors) so the UI can -/// display them without blocking. -pub fn scan_hermes_config_health() -> Result, AppError> { - let path = get_hermes_config_path(); - if !path.exists() { - return Ok(Vec::new()); - } - let content = fs::read_to_string(&path).map_err(|e| AppError::io(&path, e))?; - Ok(scan_hermes_health_internal(&content)) -} - -fn scan_hermes_health_internal(content: &str) -> Vec { - let mut warnings = Vec::new(); - - if content.trim().is_empty() { - return warnings; - } - - let config = match serde_yaml::from_str::(content) { - Ok(cfg) => cfg, - Err(err) => { - warnings.push(hermes_warning( - "config_parse_failed", - format!("Hermes config could not be parsed as YAML: {err}"), - &get_hermes_config_path().display().to_string(), - )); - return warnings; - } - }; - - if let Some(model) = config.get("model") { - if model.get("default").is_none() && model.get("provider").is_none() { - warnings.push(hermes_warning( - "model_no_default", - "No default model or provider configured in 'model' section".to_string(), - "model", - )); - } - } - - let cp_is_mapping = config - .get("custom_providers") - .map(|v| v.is_mapping()) - .unwrap_or(false); - if cp_is_mapping { - warnings.push(hermes_warning( - "custom_providers_not_list", - "custom_providers should be a YAML list (sequence), not a mapping".to_string(), - "custom_providers", - )); - } - - let mut provider_models: HashMap> = HashMap::new(); - let mut name_counts: HashMap = HashMap::new(); - let mut base_url_counts: HashMap = HashMap::new(); - - if let Some(seq) = config.get("custom_providers").and_then(|v| v.as_sequence()) { - for item in seq { - if let Some(name) = item.get("name").and_then(yaml_as_non_empty_str) { - *name_counts.entry(name.to_string()).or_insert(0) += 1; - if let Some(models) = item.get("models").and_then(|m| m.as_mapping()) { - provider_models - .entry(name.to_string()) - .or_insert_with(|| collect_mapping_string_keys(models)); - } - } - if let Some(url) = item - .get("base_url") - .and_then(|n| n.as_str()) - .map(|s| s.trim().trim_end_matches('/').to_lowercase()) - .filter(|s| !s.is_empty()) - { - *base_url_counts.entry(url).or_insert(0) += 1; - } - } - } - - // name_counts keys are unique by construction, so iterating it already - // reports each duplicate name exactly once — no extra dedupe set needed. - for (name, count) in &name_counts { - if *count > 1 { - warnings.push(hermes_warning( - "duplicate_provider_name", - format!( - "Duplicate provider name '{name}' in custom_providers — only one entry will be used" - ), - "custom_providers", - )); - } - } - for (url, count) in &base_url_counts { - if *count > 1 { - warnings.push(hermes_warning( - "duplicate_provider_base_url", - format!( - "Duplicate base_url '{url}' in custom_providers — possible accidental copy" - ), - "custom_providers", - )); - } - } - - if let Some(model) = config.get("model") { - if let Some(provider_ref) = model.get("provider").and_then(yaml_as_non_empty_str) { - if !name_counts.contains_key(provider_ref) { - warnings.push(hermes_warning( - "model_provider_unknown", - format!( - "model.provider '{provider_ref}' does not match any configured provider" - ), - "model.provider", - )); - } else if let Some(default) = model.get("default").and_then(yaml_as_non_empty_str) { - if let Some(ids) = provider_models.get(provider_ref) { - if !ids.is_empty() && !ids.iter().any(|id| id == default) { - warnings.push(hermes_warning( - "model_default_not_in_provider", - format!( - "model.default '{default}' is not in provider '{provider_ref}' models list" - ), - "model.default", - )); - } - } - } - } - } - - let version = config - .get("_config_version") - .and_then(|v| v.as_u64()) - .unwrap_or(0); - let providers_dict_populated = config - .get("providers") - .and_then(|v| v.as_mapping()) - .map(|m| !m.is_empty()) - .unwrap_or(false); - if version >= 12 && providers_dict_populated { - warnings.push(hermes_warning( - "schema_migrated_v12", - "Hermes' newer schema moved some entries into the 'providers:' dict. They are shown read-only in CC Switch — edit or remove those entries via Hermes Web UI.".to_string(), - "providers", - )); - } - - warnings -} - // ============================================================================ // MCP Section Access (for mcp/hermes.rs to use in Phase 4) // ============================================================================ @@ -1727,45 +1536,6 @@ custom_providers: }); } - // ---- Health check tests ---- - - #[test] - fn health_check_on_invalid_yaml() { - let warnings = scan_hermes_health_internal("not: valid: yaml: ["); - assert!(!warnings.is_empty()); - assert_eq!(warnings[0].code, "config_parse_failed"); - } - - #[test] - fn health_check_model_no_default() { - let yaml = "model:\n context_length: 200000\n"; - let warnings = scan_hermes_health_internal(yaml); - assert!(warnings.iter().any(|w| w.code == "model_no_default")); - } - - #[test] - fn health_check_custom_providers_not_list() { - let yaml = "custom_providers:\n foo:\n base_url: http://localhost\n"; - let warnings = scan_hermes_health_internal(yaml); - assert!(warnings - .iter() - .any(|w| w.code == "custom_providers_not_list")); - } - - #[test] - fn health_check_valid_config() { - let yaml = "\ -model: - default: gpt-4 - provider: openrouter -custom_providers: - - name: openrouter - base_url: https://openrouter.ai/api/v1 -"; - let warnings = scan_hermes_health_internal(yaml); - assert!(warnings.is_empty()); - } - // ---- yaml_to_json / json_to_yaml ---- #[test] @@ -2174,115 +1944,4 @@ user_profile_enabled: false assert_eq!(user, MemoryKind::User); assert!(serde_json::from_str::("\"bogus\"").is_err()); } - - // ---- Extended health scan rules ---- - - #[test] - fn health_check_model_provider_unknown() { - let yaml = "\ -model: - default: gpt-4 - provider: ghost -custom_providers: - - name: real - base_url: https://real.example.com -"; - let warnings = scan_hermes_health_internal(yaml); - assert!(warnings.iter().any(|w| w.code == "model_provider_unknown")); - } - - #[test] - fn health_check_model_default_not_in_provider() { - let yaml = "\ -model: - default: missing-model - provider: real -custom_providers: - - name: real - base_url: https://real.example.com - models: - present-model: {} -"; - let warnings = scan_hermes_health_internal(yaml); - assert!(warnings - .iter() - .any(|w| w.code == "model_default_not_in_provider")); - } - - #[test] - fn health_check_duplicate_provider_name() { - let yaml = "\ -custom_providers: - - name: twin - base_url: https://a.example.com - - name: twin - base_url: https://b.example.com -"; - let warnings = scan_hermes_health_internal(yaml); - let dup_count = warnings - .iter() - .filter(|w| w.code == "duplicate_provider_name") - .count(); - assert_eq!(dup_count, 1, "each duplicate name should be reported once"); - } - - #[test] - fn health_check_duplicate_provider_base_url() { - let yaml = "\ -custom_providers: - - name: a - base_url: https://same.example.com/ - - name: b - base_url: https://SAME.example.com -"; - let warnings = scan_hermes_health_internal(yaml); - assert!(warnings - .iter() - .any(|w| w.code == "duplicate_provider_base_url")); - } - - #[test] - fn health_check_schema_migrated_v12_warning() { - let yaml = "\ -_config_version: 19 -providers: - ds-1: - base_url: https://api.deepseek.com -"; - let warnings = scan_hermes_health_internal(yaml); - assert!(warnings.iter().any(|w| w.code == "schema_migrated_v12")); - } - - #[test] - fn health_check_schema_migrated_not_reported_when_dict_empty() { - // v19 with empty providers: {} should NOT surface the migration warning — - // otherwise fresh installs would see it spuriously. - let yaml = "\ -_config_version: 19 -providers: {} -"; - let warnings = scan_hermes_health_internal(yaml); - assert!(!warnings.iter().any(|w| w.code == "schema_migrated_v12")); - } - - #[test] - fn health_check_valid_config_with_matching_model_and_provider() { - // Regression guard: none of the new rules should fire on a well-formed config. - let yaml = "\ -model: - default: gpt-4 - provider: openrouter -custom_providers: - - name: openrouter - base_url: https://openrouter.ai/api/v1 - models: - gpt-4: {} -"; - let warnings = scan_hermes_health_internal(yaml); - assert!( - warnings.is_empty(), - "expected no warnings, got: {:?}", - warnings - ); - } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0966cd49d..9e7b937ce 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1264,7 +1264,6 @@ pub fn run() { commands::import_hermes_providers_from_live, commands::get_hermes_live_provider_ids, commands::get_hermes_live_provider, - commands::scan_hermes_config_health, commands::get_hermes_model_config, commands::open_hermes_web_ui, commands::launch_hermes_dashboard, diff --git a/src/App.tsx b/src/App.tsx index d3295100e..c91b1983a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -41,11 +41,7 @@ import { import { checkAllEnvConflicts, checkEnvConflicts } from "@/lib/api/env"; import { useProviderActions } from "@/hooks/useProviderActions"; import { openclawKeys, useOpenClawHealth } from "@/hooks/useOpenClaw"; -import { - hermesKeys, - useHermesHealth, - useOpenHermesWebUI, -} from "@/hooks/useHermes"; +import { hermesKeys, useOpenHermesWebUI } from "@/hooks/useHermes"; import { hermesApi } from "@/lib/api/hermes"; import { useProxyStatus } from "@/hooks/useProxyStatus"; import { useAutoCompact } from "@/hooks/useAutoCompact"; @@ -91,7 +87,6 @@ import EnvPanel from "@/components/openclaw/EnvPanel"; import ToolsPanel from "@/components/openclaw/ToolsPanel"; import AgentsDefaultsPanel from "@/components/openclaw/AgentsDefaultsPanel"; import OpenClawHealthBanner from "@/components/openclaw/OpenClawHealthBanner"; -import HermesHealthBanner from "@/components/hermes/HermesHealthBanner"; import HermesMemoryPanel from "@/components/hermes/HermesMemoryPanel"; type View = @@ -274,8 +269,6 @@ function App() { currentView === "openclawAgents"); const { data: openclawHealthWarnings = [] } = useOpenClawHealth(isOpenClawView); - const isHermesView = activeApp === "hermes" && currentView === "providers"; - const { data: hermesHealthWarnings = [] } = useHermesHealth(isHermesView); const hasSkillsSupport = true; const hasSessionSupport = activeApp === "claude" || @@ -685,9 +678,6 @@ function App() { await queryClient.invalidateQueries({ queryKey: hermesKeys.liveProviderIds, }); - await queryClient.invalidateQueries({ - queryKey: hermesKeys.health, - }); } toast.success( t("notifications.removeFromConfigSuccess", { @@ -1555,9 +1545,6 @@ function App() { {isOpenClawView && openclawHealthWarnings.length > 0 && ( )} - {isHermesView && hermesHealthWarnings.length > 0 && ( - - )} {renderContent()} diff --git a/src/components/hermes/HermesHealthBanner.tsx b/src/components/hermes/HermesHealthBanner.tsx deleted file mode 100644 index ad3750f78..000000000 --- a/src/components/hermes/HermesHealthBanner.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { ExternalLink, TriangleAlert } from "lucide-react"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { useOpenHermesWebUI } from "@/hooks/useHermes"; -import type { HermesHealthWarning } from "@/types"; - -interface HermesHealthBannerProps { - warnings: HermesHealthWarning[]; -} - -function getWarningText( - code: string, - fallback: string, - t: ReturnType["t"], -) { - switch (code) { - case "config_parse_failed": - return t("hermes.health.parseFailed", { - defaultValue: - "config.yaml could not be parsed as valid YAML. Fix the file before editing it here.", - }); - case "config_not_found": - return t("hermes.health.configNotFound", { - defaultValue: - "Hermes config.yaml not found. Create it at ~/.hermes/config.yaml or configure the path in settings.", - }); - case "env_parse_failed": - return t("hermes.health.envParseFailed", { - defaultValue: "The .env file could not be parsed.", - }); - case "model_no_default": - return t("hermes.health.modelNoDefault", { - defaultValue: - "No default model or provider is configured in the 'model' section.", - }); - case "custom_providers_not_list": - return t("hermes.health.customProvidersNotList", { - defaultValue: - "custom_providers should be a YAML list (items prefixed with '-'), not a mapping.", - }); - case "model_provider_unknown": - return t("hermes.health.modelProviderUnknown", { - defaultValue: - "model.provider references a provider that is not configured.", - }); - case "model_default_not_in_provider": - return t("hermes.health.modelDefaultNotInProvider", { - defaultValue: - "model.default is not in the selected provider's models list.", - }); - case "duplicate_provider_name": - return t("hermes.health.duplicateProviderName", { - defaultValue: - "custom_providers contains duplicate provider names — only one entry will be used.", - }); - case "duplicate_provider_base_url": - return t("hermes.health.duplicateProviderBaseUrl", { - defaultValue: - "custom_providers contains duplicate base_urls — possible accidental copy.", - }); - case "schema_migrated_v12": - return t("hermes.health.schemaMigratedV12", { - defaultValue: - "Hermes' newer schema moved some providers into the 'providers:' dict. They are shown read-only in CC Switch — edit or remove those entries via Hermes Web UI.", - }); - default: - return fallback; - } -} - -const HermesHealthBanner: React.FC = ({ - warnings, -}) => { - const { t } = useTranslation(); - const openHermesWebUI = useOpenHermesWebUI(); - - const items = useMemo( - () => - warnings.map((warning) => ({ - ...warning, - text: getWarningText(warning.code, warning.message, t), - })), - [t, warnings], - ); - - if (warnings.length === 0) { - return null; - } - - return ( -
- - - - - {t("hermes.health.title", { - defaultValue: "Hermes config warnings detected", - })} - - - - -
    - {items.map((warning) => ( -
  • - {warning.text} - {warning.path ? ` (${warning.path})` : ""} -
  • - ))} -
-
-
-
- ); -}; - -export default HermesHealthBanner; diff --git a/src/hooks/useHermes.ts b/src/hooks/useHermes.ts index 246aba7c5..74bacf0ca 100644 --- a/src/hooks/useHermes.ts +++ b/src/hooks/useHermes.ts @@ -27,7 +27,6 @@ export const hermesKeys = { all: ["hermes"] as const, liveProviderIds: ["hermes", "liveProviderIds"] as const, modelConfig: ["hermes", "modelConfig"] as const, - health: ["hermes", "health"] as const, memory: (kind: HermesMemoryKind) => ["hermes", "memory", kind] as const, memoryLimits: ["hermes", "memoryLimits"] as const, }; @@ -41,7 +40,6 @@ export function invalidateHermesProviderCaches(queryClient: QueryClient) { return Promise.all([ queryClient.invalidateQueries({ queryKey: hermesKeys.liveProviderIds }), queryClient.invalidateQueries({ queryKey: hermesKeys.modelConfig }), - queryClient.invalidateQueries({ queryKey: hermesKeys.health }), ]); } @@ -65,15 +63,6 @@ export function useHermesModelConfig(enabled: boolean) { }); } -export function useHermesHealth(enabled: boolean) { - return useQuery({ - queryKey: hermesKeys.health, - queryFn: () => hermesApi.scanHealth(), - staleTime: 30_000, - enabled, - }); -} - export function useHermesMemory(kind: HermesMemoryKind, enabled: boolean) { return useQuery({ queryKey: hermesKeys.memory(kind), diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 79a167f12..c473ff59a 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1682,26 +1682,12 @@ "open": "Open Hermes Web UI", "offline": "Hermes Web UI is not running. Start it with `hermes dashboard` first.", "openFailed": "Failed to open Hermes Web UI", - "fixInWebUI": "Fix in Hermes Web UI", "launchConfirmTitle": "Hermes Dashboard is not running", "launchConfirmMessage": "Open a terminal and start it now with `hermes dashboard`?\n\nThe browser will open automatically once startup completes.\n\nIf the terminal reports that `hermes` cannot be found or the web extras are missing, run first:\npip install hermes-agent[web]", "launchConfirmAction": "Open terminal & launch", "launching": "Started `hermes dashboard` in a terminal.", "launchFailed": "Failed to open terminal" }, - "health": { - "title": "Hermes config warnings detected", - "parseFailed": "config.yaml could not be parsed as valid YAML. Fix the file before editing it here.", - "configNotFound": "Hermes config.yaml not found. Create it at ~/.hermes/config.yaml or configure the path in settings.", - "envParseFailed": "The .env file could not be parsed.", - "modelNoDefault": "No default model or provider is configured in the 'model' section.", - "customProvidersNotList": "custom_providers should be a YAML list (items prefixed with '-'), not a mapping.", - "modelProviderUnknown": "model.provider references a provider that is not configured.", - "modelDefaultNotInProvider": "model.default is not in the selected provider's models list.", - "duplicateProviderName": "custom_providers contains duplicate provider names — only one entry will be used.", - "duplicateProviderBaseUrl": "custom_providers contains duplicate base_urls — possible accidental copy.", - "schemaMigratedV12": "Hermes' newer schema moved some providers into the 'providers:' dict. They are shown read-only in CC Switch — edit or remove those entries via Hermes Web UI." - }, "memory": { "title": "Memory", "agentTab": "Agent Memory (MEMORY.md)", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 405d627a7..1734b71da 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -1682,26 +1682,12 @@ "open": "Hermes Web UI を開く", "offline": "Hermes Web UI が起動していません。まず `hermes dashboard` を実行してください。", "openFailed": "Hermes Web UI を開けませんでした", - "fixInWebUI": "Hermes Web UI で修正", "launchConfirmTitle": "Hermes Dashboard が起動していません", "launchConfirmMessage": "ターミナルを開いて `hermes dashboard` を実行しますか?\n\n起動完了後、ブラウザが自動的に開きます。\n\nターミナルに `hermes` が見つからない、または web 依存が不足するというエラーが表示される場合は、まず次を実行してください:\npip install hermes-agent[web]", "launchConfirmAction": "ターミナルを開いて起動", "launching": "ターミナルで hermes dashboard を起動しました。", "launchFailed": "ターミナルを開けませんでした" }, - "health": { - "title": "Hermes 設定の警告を検出", - "parseFailed": "config.yaml を有効な YAML として解析できません。ここで編集する前にファイルを修正してください。", - "configNotFound": "Hermes config.yaml が見つかりません。~/.hermes/config.yaml に作成するか、設定でパスを構成してください。", - "envParseFailed": ".env ファイルを解析できません。", - "modelNoDefault": "'model' セクションに既定モデルまたはプロバイダーが設定されていません。", - "customProvidersNotList": "custom_providers はマッピングではなく、YAML リスト('-' で始まる項目)である必要があります。", - "modelProviderUnknown": "model.provider が指すプロバイダーは設定に存在しません。", - "modelDefaultNotInProvider": "model.default は選択中のプロバイダーのモデル一覧に含まれていません。", - "duplicateProviderName": "custom_providers にプロバイダー名の重複があります。1 件のみ有効になります。", - "duplicateProviderBaseUrl": "custom_providers に重複した base_url があります。誤ってコピーされた可能性があります。", - "schemaMigratedV12": "Hermes の新しいスキーマでは一部のプロバイダーが 'providers:' dict に移動しました。CC Switch では読み取り専用で表示されます。編集・削除は Hermes Web UI から行ってください。" - }, "memory": { "title": "メモリ", "agentTab": "エージェント記憶 (MEMORY.md)", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 6a1abe87a..a0c81d524 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -1682,26 +1682,12 @@ "open": "打开 Hermes Web UI", "offline": "Hermes Web UI 未启动,请先运行 `hermes dashboard` 启动服务。", "openFailed": "打开 Hermes Web UI 失败", - "fixInWebUI": "在 Hermes Web UI 修复", "launchConfirmTitle": "Hermes Dashboard 未启动", "launchConfirmMessage": "是否打开终端并运行 `hermes dashboard` 启动服务?\n\n启动完成后会自动打开浏览器。\n\n若终端提示找不到 hermes 或缺少 web 依赖,请先运行:\npip install hermes-agent[web]", "launchConfirmAction": "打开终端并启动", "launching": "已在终端启动 hermes dashboard", "launchFailed": "打开终端失败" }, - "health": { - "title": "检测到 Hermes 配置警告", - "parseFailed": "config.yaml 无法解析为有效 YAML。请先修复文件再在此编辑。", - "configNotFound": "未找到 Hermes config.yaml。请在 ~/.hermes/config.yaml 创建或在设置中配置路径。", - "envParseFailed": ".env 文件无法解析。", - "modelNoDefault": "model 段未设置默认模型或供应商。", - "customProvidersNotList": "custom_providers 应为 YAML 列表(以 `-` 开头),而不是映射。", - "modelProviderUnknown": "model.provider 指向的供应商未在配置中找到。", - "modelDefaultNotInProvider": "model.default 指向的模型不在所选供应商的模型列表中。", - "duplicateProviderName": "custom_providers 中存在重复的供应商名,只会有一条生效。", - "duplicateProviderBaseUrl": "custom_providers 中存在重复的 base_url,可能是意外复制。", - "schemaMigratedV12": "Hermes 新版 schema 把部分供应商移到了 'providers:' dict。CC Switch 中以只读方式显示,编辑或删除请通过 Hermes Web UI 操作。" - }, "memory": { "title": "记忆管理", "agentTab": "Agent 记忆 (MEMORY.md)", diff --git a/src/lib/api/hermes.ts b/src/lib/api/hermes.ts index e8dea5d54..c212a0429 100644 --- a/src/lib/api/hermes.ts +++ b/src/lib/api/hermes.ts @@ -1,6 +1,5 @@ import { invoke } from "@tauri-apps/api/core"; import type { - HermesHealthWarning, HermesMemoryKind, HermesMemoryLimits, HermesModelConfig, @@ -12,19 +11,15 @@ import type { * CC Switch intentionally keeps its Hermes surface minimal — deep configuration * (model, agent behavior, env vars, skills, cron, logs, analytics) lives in * the Hermes Web UI at http://127.0.0.1:9119. CC Switch only reads the `model` - * section to highlight the active provider, scans config health, and launches - * the Hermes Web UI for everything else. Writes to `model` happen implicitly - * via `apply_switch_defaults` when the user switches providers. + * section to highlight the active provider and launches the Hermes Web UI for + * everything else. Writes to `model` happen implicitly via + * `apply_switch_defaults` when the user switches providers. */ export const hermesApi = { async getModelConfig(): Promise { return await invoke("get_hermes_model_config"); }, - async scanHealth(): Promise { - return await invoke("scan_hermes_config_health"); - }, - /** * Probe the local Hermes Web UI and open it in the system browser. * Optional `path` lets callers deep-link to specific pages like `/config`. diff --git a/src/types.ts b/src/types.ts index 68b938842..611387755 100644 --- a/src/types.ts +++ b/src/types.ts @@ -589,17 +589,6 @@ export interface HermesModelConfig { [key: string]: unknown; } -export interface HermesHealthWarning { - code: string; - message: string; - path?: string; -} - -export interface HermesWriteOutcome { - backupPath?: string; - warnings: HermesHealthWarning[]; -} - export type HermesMemoryKind = "memory" | "user"; export interface HermesMemoryLimits {