feat(hermes): P1-4 hermes_read_config_full 全字段解析 - 解锁 14+ Gateway 高价值配置

之前 hermes_read_config 只读 5 字段(model/base_url/provider/api_key/config_exists),
为「快速面板」服务。Hermes Gateway 实际有 14+ 个顶层配置项,ClawPanel 完全没读到。

本次新增 hermes_read_config_full 命令,作为高级配置编辑器的数据源。

## 后端实现
- 加 serde_yaml 0.9 依赖
- 新命令 hermes_read_config_full:
  · 用 serde_yaml 完整解析 config.yaml
  · 转 JSON 返回 { exists, raw, config, highlights }
  · highlights 字段单独抽出 14 个高价值顶层字段:
    streaming / stt_enabled / quick_commands / reset_triggers /
    default_reset_policy / unauthorized_dm_behavior /
    session_store_max_age_days / always_log_local /
    group_sessions_per_user / thread_sessions_per_user /
    platforms / dashboard / memory / skills
  · 已注册到 lib.rs

## 前端
- tauri-api.js 加 hermesReadConfigFull wrapper

## Web 模式
- dev-api.js 加 hermes_read_config_full handler(Web 模式不强制 yaml 解析,
  返回 raw + null highlights,前端按需 fallback)

## 后续
- 实际「高级配置编辑器」UI 后续单独开 — 本次仅打通数据通道
- 与轻量版 hermes_read_config 互补共存,model 配置页继续用轻量版
This commit is contained in:
晴天
2026-05-14 03:56:17 +08:00
parent 7eababad4a
commit c4bf769eab
6 changed files with 127 additions and 0 deletions

View File

@@ -7159,6 +7159,28 @@ const handlers = {
return { model: displayModel, model_raw: modelName, base_url: baseUrl, provider, api_key: apiKey, config_exists: fs.existsSync(configPath) }
},
// P1-4完整解析 config.yaml让前端能读 14+ 高价值字段
// Web 模式不引入 yaml 依赖,简单返回 raw + null highlights前端按需渲染
hermes_read_config_full() {
const configPath = path.join(hermesHome(), 'config.yaml')
if (!fs.existsSync(configPath)) {
return { exists: false, raw: '', config: {}, highlights: {} }
}
let raw = ''
try { raw = fs.readFileSync(configPath, 'utf8') } catch {}
// Web 模式下不强制 yaml 解析(避免新增依赖),前端可走 raw 自己 parse 或者 fallback 到桌面端
const highlightKeys = [
'streaming', 'stt_enabled', 'quick_commands', 'reset_triggers',
'default_reset_policy', 'unauthorized_dm_behavior',
'session_store_max_age_days', 'always_log_local',
'group_sessions_per_user', 'thread_sessions_per_user',
'platforms', 'dashboard', 'memory', 'skills',
]
const highlights = {}
highlightKeys.forEach(k => { highlights[k] = null })
return { exists: true, raw, config: {}, highlights }
},
hermes_list_providers() {
return HERMES_PROVIDER_REGISTRY.map(p => ({
...p,

20
src-tauri/Cargo.lock generated
View File

@@ -379,6 +379,7 @@ dependencies = [
"reqwest 0.12.28",
"serde",
"serde_json",
"serde_yaml",
"sha2",
"tar",
"tauri",
@@ -3537,6 +3538,19 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.14.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "serialize-to-javascript"
version = "0.1.2"
@@ -4638,6 +4652,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"

View File

@@ -20,6 +20,7 @@ tauri = { version = "2", features = ["tray-icon", "image-png"] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
dirs = "6"
chrono = "0.4"
zip = { version = "2", default-features = false, features = ["deflate"] }

View File

@@ -2068,6 +2068,88 @@ pub async fn hermes_read_config() -> Result<Value, String> {
}))
}
// ---------------------------------------------------------------------------
// hermes_read_config_full — 解析整个 config.yaml 为 JSON 返回给前端
//
// 与轻量版 hermes_read_config仅返回 5 个 model 相关字段)互补:
// 前者用于 model 配置页快速展示,本命令用于「高级配置编辑器」让用户能看到/改
// Gateway 端 14+ 个顶层配置项,比如 quick_commands / streaming / reset_triggers /
// stt_enabled / unauthorized_dm_behavior 等。
//
// 返回值结构:
// {
// "exists": true, // config.yaml 是否存在
// "raw": "...yaml string...", // 原文(给 yaml editor
// "config": { ...full json... }, // 整份 yaml 转成 JSON
// "highlights": { // 14 个高价值字段单独抽出,前端直接 .x 访问
// "streaming": {...}, "stt_enabled": true, "quick_commands": {...},
// "reset_triggers": [...], "default_reset_policy": {...},
// "unauthorized_dm_behavior": "pair", "session_store_max_age_days": 90,
// "always_log_local": true,
// "group_sessions_per_user": false, "thread_sessions_per_user": false,
// ... 等
// }
// }
// ---------------------------------------------------------------------------
#[tauri::command]
pub async fn hermes_read_config_full() -> Result<Value, String> {
let config_path = hermes_home().join("config.yaml");
if !config_path.exists() {
return Ok(serde_json::json!({
"exists": false,
"raw": "",
"config": {},
"highlights": {},
}));
}
let raw = std::fs::read_to_string(&config_path)
.map_err(|e| format!("Failed to read config.yaml: {e}"))?;
// 解析 YAML → JSON
let yaml_value: serde_yaml::Value = serde_yaml::from_str(&raw)
.map_err(|e| format!("Invalid YAML in config.yaml: {e}"))?;
let config_json: Value = serde_json::to_value(&yaml_value)
.map_err(|e| format!("YAML→JSON conversion failed: {e}"))?;
// 抽取 14 个高价值顶层字段(如不存在保持 null前端按需渲染
let highlight_keys = [
"streaming",
"stt_enabled",
"quick_commands",
"reset_triggers",
"default_reset_policy",
"unauthorized_dm_behavior",
"session_store_max_age_days",
"always_log_local",
"group_sessions_per_user",
"thread_sessions_per_user",
"platforms",
"dashboard",
"memory",
"skills",
];
let highlights: serde_json::Map<String, Value> = highlight_keys
.iter()
.map(|k| {
let v = config_json
.get(*k)
.cloned()
.unwrap_or(Value::Null);
((*k).to_string(), v)
})
.collect();
Ok(serde_json::json!({
"exists": true,
"raw": raw,
"config": config_json,
"highlights": Value::Object(highlights),
}))
}
// ---------------------------------------------------------------------------
// hermes_fetch_models — 从 API 获取模型列表(后端代理,避免 CORS
// ---------------------------------------------------------------------------

View File

@@ -240,6 +240,7 @@ pub fn run() {
hermes::hermes_api_proxy,
hermes::hermes_agent_run,
hermes::hermes_read_config,
hermes::hermes_read_config_full,
hermes::hermes_fetch_models,
hermes::hermes_update_model,
hermes::hermes_detect_environments,

View File

@@ -476,6 +476,7 @@ export const api = {
hermesAgentRun: (input, sessionId, conversationHistory, instructions) => invoke('hermes_agent_run', { input, sessionId: sessionId || null, conversationHistory: conversationHistory || null, instructions: instructions || null }),
hermesAgentRunStream: (input, sessionId, conversationHistory, instructions, onEvent, options) => webStreamInvoke('hermes_agent_run_stream', { input, sessionId: sessionId || null, conversationHistory: conversationHistory || null, instructions: instructions || null }, onEvent, options),
hermesReadConfig: () => invoke('hermes_read_config'),
hermesReadConfigFull: () => invoke('hermes_read_config_full'),
hermesFetchModels: (baseUrl, apiKey, apiType, provider) => invoke('hermes_fetch_models', { baseUrl, apiKey, apiType: apiType || null, provider: provider || null }),
hermesUpdateModel: (model, provider) => invoke('hermes_update_model', { model, provider: provider || null }),
hermesListProviders: () => cachedInvoke('hermes_list_providers', {}, 600000),