feat(openclaw): add Env/Tools/Agents config panels

- Migrate OpenClaw commands from provider.rs to dedicated commands/openclaw.rs
- Add backend types and read/write for env, tools, agents.defaults sections
- Create EnvPanel (API key + custom vars KV editor)
- Create ToolsPanel (profile selector + allow/deny lists)
- Create AgentsDefaultsPanel (default model + runtime parameters)
- Extend App.tsx menu bar with Env/Tools/Agents buttons
- Remove Prompts button for OpenClaw (overlaps with Workspace AGENTS.md)
This commit is contained in:
Jason
2026-02-07 23:52:54 +08:00
parent 9035784aa4
commit 2b1f0f0f7e
15 changed files with 1017 additions and 81 deletions
+6 -1
View File
@@ -735,7 +735,12 @@ impl MultiAppConfig {
let mut conflicts = Vec::new();
// 收集所有应用的 MCP
for app in [AppType::Claude, AppType::Codex, AppType::Gemini, AppType::OpenCode] {
for app in [
AppType::Claude,
AppType::Codex,
AppType::Gemini,
AppType::OpenCode,
] {
let old_servers = match app {
AppType::Claude => &self.mcp.claude.servers,
AppType::Codex => &self.mcp.codex.servers,
+2
View File
@@ -9,6 +9,7 @@ mod import_export;
mod mcp;
mod misc;
mod omo;
mod openclaw;
mod plugin;
mod prompt;
mod provider;
@@ -31,6 +32,7 @@ pub use import_export::*;
pub use mcp::*;
pub use misc::*;
pub use omo::*;
pub use openclaw::*;
pub use plugin::*;
pub use prompt::*;
pub use provider::*;
+108
View File
@@ -0,0 +1,108 @@
use std::collections::HashMap;
use tauri::State;
use crate::openclaw_config;
use crate::store::AppState;
// ============================================================================
// OpenClaw Provider Commands (migrated from provider.rs)
// ============================================================================
/// Import providers from OpenClaw live config to database.
///
/// OpenClaw uses additive mode — users may already have providers
/// configured in openclaw.json.
#[tauri::command]
pub fn import_openclaw_providers_from_live(state: State<'_, AppState>) -> Result<usize, String> {
crate::services::provider::import_openclaw_providers_from_live(state.inner())
.map_err(|e| e.to_string())
}
/// Get provider IDs in the OpenClaw live config.
#[tauri::command]
pub fn get_openclaw_live_provider_ids() -> Result<Vec<String>, String> {
openclaw_config::get_providers()
.map(|providers| providers.keys().cloned().collect())
.map_err(|e| e.to_string())
}
// ============================================================================
// Agents Configuration Commands
// ============================================================================
/// Get OpenClaw default model config (agents.defaults.model)
#[tauri::command]
pub fn get_openclaw_default_model() -> Result<Option<openclaw_config::OpenClawDefaultModel>, String>
{
openclaw_config::get_default_model().map_err(|e| e.to_string())
}
/// Set OpenClaw default model config (agents.defaults.model)
#[tauri::command]
pub fn set_openclaw_default_model(
model: openclaw_config::OpenClawDefaultModel,
) -> Result<(), String> {
openclaw_config::set_default_model(&model).map_err(|e| e.to_string())
}
/// Get OpenClaw model catalog/allowlist (agents.defaults.models)
#[tauri::command]
pub fn get_openclaw_model_catalog(
) -> Result<Option<HashMap<String, openclaw_config::OpenClawModelCatalogEntry>>, String> {
openclaw_config::get_model_catalog().map_err(|e| e.to_string())
}
/// Set OpenClaw model catalog/allowlist (agents.defaults.models)
#[tauri::command]
pub fn set_openclaw_model_catalog(
catalog: HashMap<String, openclaw_config::OpenClawModelCatalogEntry>,
) -> Result<(), String> {
openclaw_config::set_model_catalog(&catalog).map_err(|e| e.to_string())
}
/// Get full agents.defaults config (all fields)
#[tauri::command]
pub fn get_openclaw_agents_defaults(
) -> Result<Option<openclaw_config::OpenClawAgentsDefaults>, String> {
openclaw_config::get_agents_defaults().map_err(|e| e.to_string())
}
/// Set full agents.defaults config (all fields)
#[tauri::command]
pub fn set_openclaw_agents_defaults(
defaults: openclaw_config::OpenClawAgentsDefaults,
) -> Result<(), String> {
openclaw_config::set_agents_defaults(&defaults).map_err(|e| e.to_string())
}
// ============================================================================
// Env Configuration Commands
// ============================================================================
/// Get OpenClaw env config (env section of openclaw.json)
#[tauri::command]
pub fn get_openclaw_env() -> Result<openclaw_config::OpenClawEnvConfig, String> {
openclaw_config::get_env_config().map_err(|e| e.to_string())
}
/// Set OpenClaw env config (env section of openclaw.json)
#[tauri::command]
pub fn set_openclaw_env(env: openclaw_config::OpenClawEnvConfig) -> Result<(), String> {
openclaw_config::set_env_config(&env).map_err(|e| e.to_string())
}
// ============================================================================
// Tools Configuration Commands
// ============================================================================
/// Get OpenClaw tools config (tools section of openclaw.json)
#[tauri::command]
pub fn get_openclaw_tools() -> Result<openclaw_config::OpenClawToolsConfig, String> {
openclaw_config::get_tools_config().map_err(|e| e.to_string())
}
/// Set OpenClaw tools config (tools section of openclaw.json)
#[tauri::command]
pub fn set_openclaw_tools(tools: openclaw_config::OpenClawToolsConfig) -> Result<(), String> {
openclaw_config::set_tools_config(&tools).map_err(|e| e.to_string())
}
+1 -56
View File
@@ -320,60 +320,5 @@ pub fn get_opencode_live_provider_ids() -> Result<Vec<String>, String> {
}
// ============================================================================
// OpenClaw 专属命令
// OpenClaw 专属命令 → 已迁移至 commands/openclaw.rs
// ============================================================================
/// 从 OpenClaw live 配置导入供应商到数据库
///
/// 这是 OpenClaw 特有的功能,因为 OpenClaw 使用累加模式,
/// 用户可能已经在 openclaw.json 中配置了供应商。
#[tauri::command]
pub fn import_openclaw_providers_from_live(state: State<'_, AppState>) -> Result<usize, String> {
crate::services::provider::import_openclaw_providers_from_live(state.inner())
.map_err(|e| e.to_string())
}
/// 获取 OpenClaw live 配置中的供应商 ID 列表
///
/// 用于前端判断供应商是否已添加到 openclaw.json
#[tauri::command]
pub fn get_openclaw_live_provider_ids() -> Result<Vec<String>, String> {
crate::openclaw_config::get_providers()
.map(|providers| providers.keys().cloned().collect())
.map_err(|e| e.to_string())
}
// ============================================================================
// OpenClaw Agents Configuration Commands
// ============================================================================
/// 获取 OpenClaw 默认模型配置(agents.defaults.model
#[tauri::command]
pub fn get_openclaw_default_model(
) -> Result<Option<crate::openclaw_config::OpenClawDefaultModel>, String> {
crate::openclaw_config::get_default_model().map_err(|e| e.to_string())
}
/// 设置 OpenClaw 默认模型配置(agents.defaults.model
#[tauri::command]
pub fn set_openclaw_default_model(
model: crate::openclaw_config::OpenClawDefaultModel,
) -> Result<(), String> {
crate::openclaw_config::set_default_model(&model).map_err(|e| e.to_string())
}
/// 获取 OpenClaw 模型目录/允许列表(agents.defaults.models
#[tauri::command]
pub fn get_openclaw_model_catalog(
) -> Result<Option<std::collections::HashMap<String, crate::openclaw_config::OpenClawModelCatalogEntry>>, String>
{
crate::openclaw_config::get_model_catalog().map_err(|e| e.to_string())
}
/// 设置 OpenClaw 模型目录/允许列表(agents.defaults.models
#[tauri::command]
pub fn set_openclaw_model_catalog(
catalog: std::collections::HashMap<String, crate::openclaw_config::OpenClawModelCatalogEntry>,
) -> Result<(), String> {
crate::openclaw_config::set_model_catalog(&catalog).map_err(|e| e.to_string())
}
+7 -1
View File
@@ -13,8 +13,8 @@ mod gemini_config;
mod gemini_mcp;
mod init_status;
mod mcp;
mod opencode_config;
mod openclaw_config;
mod opencode_config;
mod panic_hook;
mod prompt;
mod prompt_files;
@@ -1013,6 +1013,12 @@ pub fn run() {
commands::set_openclaw_default_model,
commands::get_openclaw_model_catalog,
commands::set_openclaw_model_catalog,
commands::get_openclaw_agents_defaults,
commands::set_openclaw_agents_defaults,
commands::get_openclaw_env,
commands::set_openclaw_env,
commands::get_openclaw_tools,
commands::set_openclaw_tools,
// Global upstream proxy
commands::get_global_proxy_url,
commands::set_global_proxy_url,
+129 -6
View File
@@ -220,12 +220,8 @@ pub fn read_openclaw_config() -> Result<Value, AppError> {
let content = std::fs::read_to_string(&path).map_err(|e| AppError::io(&path, e))?;
// 尝试 JSON5 解析(支持注释和尾随逗号)
json5::from_str(&content).map_err(|e| {
AppError::Config(format!(
"Failed to parse OpenClaw config as JSON5: {}",
e
))
})
json5::from_str(&content)
.map_err(|e| AppError::Config(format!("Failed to parse OpenClaw config as JSON5: {}", e)))
}
/// 写入 OpenClaw 配置文件(原子写入)
@@ -420,3 +416,130 @@ fn ensure_agents_defaults_path(config: &mut Value) {
config["agents"]["defaults"] = json!({});
}
}
// ============================================================================
// Full Agents Defaults Functions
// ============================================================================
/// Read the full agents.defaults config
pub fn get_agents_defaults() -> Result<Option<OpenClawAgentsDefaults>, AppError> {
let config = read_openclaw_config()?;
let Some(defaults_value) = config.get("agents").and_then(|a| a.get("defaults")) else {
return Ok(None);
};
let defaults = serde_json::from_value(defaults_value.clone())
.map_err(|e| AppError::Config(format!("Failed to parse agents.defaults: {e}")))?;
Ok(Some(defaults))
}
/// Write the full agents.defaults config
pub fn set_agents_defaults(defaults: &OpenClawAgentsDefaults) -> Result<(), AppError> {
let mut config = read_openclaw_config()?;
if config.get("agents").is_none() {
config["agents"] = json!({});
}
let value =
serde_json::to_value(defaults).map_err(|e| AppError::JsonSerialize { source: e })?;
config["agents"]["defaults"] = value;
write_openclaw_config(&config)
}
// ============================================================================
// Env Configuration
// ============================================================================
/// OpenClaw env configuration (env section of openclaw.json)
///
/// Stores environment variables like API keys and custom vars.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenClawEnvConfig {
/// All environment variable key-value pairs
#[serde(flatten)]
pub vars: HashMap<String, Value>,
}
/// Read the env config section
pub fn get_env_config() -> Result<OpenClawEnvConfig, AppError> {
let config = read_openclaw_config()?;
let Some(env_value) = config.get("env") else {
return Ok(OpenClawEnvConfig {
vars: HashMap::new(),
});
};
serde_json::from_value(env_value.clone())
.map_err(|e| AppError::Config(format!("Failed to parse env config: {e}")))
}
/// Write the env config section
pub fn set_env_config(env: &OpenClawEnvConfig) -> Result<(), AppError> {
let mut config = read_openclaw_config()?;
let value = serde_json::to_value(env).map_err(|e| AppError::JsonSerialize { source: e })?;
config["env"] = value;
write_openclaw_config(&config)
}
// ============================================================================
// Tools Configuration
// ============================================================================
/// OpenClaw tools configuration (tools section of openclaw.json)
///
/// Controls tool permissions with profile-based allow/deny lists.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenClawToolsConfig {
/// Active permission profile (e.g. "default", "strict", "permissive")
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
/// Allowed tool patterns
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allow: Vec<String>,
/// Denied tool patterns
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub deny: Vec<String>,
/// Other custom fields (preserve unknown fields)
#[serde(flatten)]
pub other: HashMap<String, Value>,
}
/// Read the tools config section
pub fn get_tools_config() -> Result<OpenClawToolsConfig, AppError> {
let config = read_openclaw_config()?;
let Some(tools_value) = config.get("tools") else {
return Ok(OpenClawToolsConfig {
profile: None,
allow: Vec::new(),
deny: Vec::new(),
other: HashMap::new(),
});
};
serde_json::from_value(tools_value.clone())
.map_err(|e| AppError::Config(format!("Failed to parse tools config: {e}")))
}
/// Write the tools config section
pub fn set_tools_config(tools: &OpenClawToolsConfig) -> Result<(), AppError> {
let mut config = read_openclaw_config()?;
let value = serde_json::to_value(tools).map_err(|e| AppError::JsonSerialize { source: e })?;
config["tools"] = value;
write_openclaw_config(&config)
}
+1 -1
View File
@@ -5,8 +5,8 @@ use crate::codex_config::get_codex_auth_path;
use crate::config::get_claude_settings_path;
use crate::error::AppError;
use crate::gemini_config::get_gemini_dir;
use crate::opencode_config::get_opencode_dir;
use crate::openclaw_config::get_openclaw_dir;
use crate::opencode_config::get_opencode_dir;
/// 返回指定应用所使用的提示词文件路径。
pub fn prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
+4 -1
View File
@@ -216,7 +216,10 @@ pub(crate) fn write_live_snapshot(app_type: &AppType, provider: &Provider) -> Re
|| provider.settings_config.get("api").is_some()
|| provider.settings_config.get("models").is_some()
{
openclaw_config::set_provider(&provider.id, provider.settings_config.clone())?;
openclaw_config::set_provider(
&provider.id,
provider.settings_config.clone(),
)?;
log::info!(
"OpenClaw provider '{}' written as raw JSON to live config",
provider.id
+2 -2
View File
@@ -21,8 +21,8 @@ use crate::store::AppState;
// Re-export sub-module functions for external access
pub use live::{
import_default_config, import_openclaw_providers_from_live, import_opencode_providers_from_live,
read_live_settings, sync_current_to_live,
import_default_config, import_openclaw_providers_from_live,
import_opencode_providers_from_live, read_live_settings, sync_current_to_live,
};
// Internal re-exports (pub(crate))