mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
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:
@@ -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,
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
+61
-10
@@ -17,6 +17,9 @@ import {
|
||||
FolderArchive,
|
||||
Search,
|
||||
FolderOpen,
|
||||
KeyRound,
|
||||
Shield,
|
||||
Cpu,
|
||||
} from "lucide-react";
|
||||
import type { Provider, VisibleApps } from "@/types";
|
||||
import type { EnvConflict } from "@/types/env";
|
||||
@@ -58,6 +61,9 @@ import { Button } from "@/components/ui/button";
|
||||
import { SessionManagerPage } from "@/components/sessions/SessionManagerPage";
|
||||
import { useDisableCurrentOmo } from "@/lib/query/omo";
|
||||
import WorkspaceFilesPanel from "@/components/workspace/WorkspaceFilesPanel";
|
||||
import EnvPanel from "@/components/openclaw/EnvPanel";
|
||||
import ToolsPanel from "@/components/openclaw/ToolsPanel";
|
||||
import AgentsDefaultsPanel from "@/components/openclaw/AgentsDefaultsPanel";
|
||||
|
||||
type View =
|
||||
| "providers"
|
||||
@@ -69,7 +75,10 @@ type View =
|
||||
| "agents"
|
||||
| "universal"
|
||||
| "sessions"
|
||||
| "workspace";
|
||||
| "workspace"
|
||||
| "openclawEnv"
|
||||
| "openclawTools"
|
||||
| "openclawAgents";
|
||||
|
||||
const DRAG_BAR_HEIGHT = isWindows() || isLinux() ? 0 : 28; // px
|
||||
const HEADER_HEIGHT = 64; // px
|
||||
@@ -104,6 +113,9 @@ const VALID_VIEWS: View[] = [
|
||||
"universal",
|
||||
"sessions",
|
||||
"workspace",
|
||||
"openclawEnv",
|
||||
"openclawTools",
|
||||
"openclawAgents",
|
||||
];
|
||||
|
||||
const getInitialView = (): View => {
|
||||
@@ -637,6 +649,12 @@ function App() {
|
||||
return <SessionManagerPage />;
|
||||
case "workspace":
|
||||
return <WorkspaceFilesPanel />;
|
||||
case "openclawEnv":
|
||||
return <EnvPanel />;
|
||||
case "openclawTools":
|
||||
return <ToolsPanel />;
|
||||
case "openclawAgents":
|
||||
return <AgentsDefaultsPanel />;
|
||||
default:
|
||||
return (
|
||||
<div className="px-6 flex flex-col h-[calc(100vh-8rem)] overflow-hidden">
|
||||
@@ -796,6 +814,10 @@ function App() {
|
||||
})}
|
||||
{currentView === "sessions" && t("sessionManager.title")}
|
||||
{currentView === "workspace" && t("workspace.title")}
|
||||
{currentView === "openclawEnv" && t("openclaw.env.title")}
|
||||
{currentView === "openclawTools" && t("openclaw.tools.title")}
|
||||
{currentView === "openclawAgents" &&
|
||||
t("openclaw.agents.title")}
|
||||
</h1>
|
||||
</div>
|
||||
) : (
|
||||
@@ -975,15 +997,44 @@ function App() {
|
||||
|
||||
<div className="flex items-center gap-1 p-1 bg-muted rounded-xl">
|
||||
{activeApp === "openclaw" ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("workspace")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5"
|
||||
title={t("workspace.manage")}
|
||||
>
|
||||
<FolderOpen className="w-4 h-4" />
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("workspace")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5"
|
||||
title={t("workspace.manage")}
|
||||
>
|
||||
<FolderOpen className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("openclawEnv")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5"
|
||||
title={t("openclaw.env.title")}
|
||||
>
|
||||
<KeyRound className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("openclawTools")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5"
|
||||
title={t("openclaw.tools.title")}
|
||||
>
|
||||
<Shield className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("openclawAgents")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5"
|
||||
title={t("openclaw.agents.title")}
|
||||
>
|
||||
<Cpu className="w-4 h-4" />
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Save } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { openclawApi } from "@/lib/api/openclaw";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import type { OpenClawAgentsDefaults } from "@/types";
|
||||
|
||||
const AgentsDefaultsPanel: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [defaults, setDefaults] = useState<OpenClawAgentsDefaults | null>(null);
|
||||
const [primaryModel, setPrimaryModel] = useState("");
|
||||
const [fallbacks, setFallbacks] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// Extra known fields from agents.defaults
|
||||
const [workspace, setWorkspace] = useState("");
|
||||
const [timeout, setTimeout_] = useState("");
|
||||
const [contextTokens, setContextTokens] = useState("");
|
||||
const [maxConcurrent, setMaxConcurrent] = useState("");
|
||||
|
||||
const loadDefaults = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await openclawApi.getAgentsDefaults();
|
||||
setDefaults(data);
|
||||
|
||||
if (data) {
|
||||
setPrimaryModel(data.model?.primary ?? "");
|
||||
setFallbacks((data.model?.fallbacks ?? []).join(", "));
|
||||
|
||||
// Extract known extra fields
|
||||
setWorkspace(String(data.workspace ?? ""));
|
||||
setTimeout_(String(data.timeout ?? ""));
|
||||
setContextTokens(String(data.contextTokens ?? ""));
|
||||
setMaxConcurrent(String(data.maxConcurrent ?? ""));
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(t("openclaw.agents.loadFailed"));
|
||||
console.error("Failed to load agents defaults:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadDefaults();
|
||||
}, [loadDefaults]);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
|
||||
// Preserve all unknown fields from original data
|
||||
const updated: OpenClawAgentsDefaults = { ...defaults };
|
||||
|
||||
// Model configuration
|
||||
const fallbackList = fallbacks
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (primaryModel.trim()) {
|
||||
updated.model = {
|
||||
primary: primaryModel.trim(),
|
||||
...(fallbackList.length > 0 ? { fallbacks: fallbackList } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
// Optional numeric fields
|
||||
if (workspace.trim()) updated.workspace = workspace.trim();
|
||||
else delete updated.workspace;
|
||||
|
||||
if (timeout.trim()) updated.timeout = Number(timeout);
|
||||
else delete updated.timeout;
|
||||
|
||||
if (contextTokens.trim()) updated.contextTokens = Number(contextTokens);
|
||||
else delete updated.contextTokens;
|
||||
|
||||
if (maxConcurrent.trim()) updated.maxConcurrent = Number(maxConcurrent);
|
||||
else delete updated.maxConcurrent;
|
||||
|
||||
await openclawApi.setAgentsDefaults(updated);
|
||||
toast.success(t("openclaw.agents.saveSuccess"));
|
||||
await loadDefaults();
|
||||
} catch (err) {
|
||||
toast.error(t("openclaw.agents.saveFailed"));
|
||||
console.error("Failed to save agents defaults:", err);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="px-6 pt-4 pb-8 flex items-center justify-center min-h-[200px]">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("common.loading")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-6 pt-4 pb-8">
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
{t("openclaw.agents.description")}
|
||||
</p>
|
||||
|
||||
{/* Model Configuration Card */}
|
||||
<div className="rounded-xl border border-border bg-card p-5 mb-4">
|
||||
<h3 className="text-sm font-medium mb-4">
|
||||
{t("openclaw.agents.modelSection")}
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="mb-1.5 block">
|
||||
{t("openclaw.agents.primaryModel")}
|
||||
</Label>
|
||||
<Input
|
||||
value={primaryModel}
|
||||
onChange={(e) => setPrimaryModel(e.target.value)}
|
||||
placeholder="provider/model-id"
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{t("openclaw.agents.primaryModelHint")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mb-1.5 block">
|
||||
{t("openclaw.agents.fallbackModels")}
|
||||
</Label>
|
||||
<Input
|
||||
value={fallbacks}
|
||||
onChange={(e) => setFallbacks(e.target.value)}
|
||||
placeholder="provider/model-a, provider/model-b"
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{t("openclaw.agents.fallbackModelsHint")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Runtime Parameters Card */}
|
||||
<div className="rounded-xl border border-border bg-card p-5 mb-4">
|
||||
<h3 className="text-sm font-medium mb-4">
|
||||
{t("openclaw.agents.runtimeSection")}
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="mb-1.5 block">
|
||||
{t("openclaw.agents.workspace")}
|
||||
</Label>
|
||||
<Input
|
||||
value={workspace}
|
||||
onChange={(e) => setWorkspace(e.target.value)}
|
||||
placeholder="~/projects"
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mb-1.5 block">
|
||||
{t("openclaw.agents.timeout")}
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={timeout}
|
||||
onChange={(e) => setTimeout_(e.target.value)}
|
||||
placeholder="300"
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mb-1.5 block">
|
||||
{t("openclaw.agents.contextTokens")}
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={contextTokens}
|
||||
onChange={(e) => setContextTokens(e.target.value)}
|
||||
placeholder="200000"
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mb-1.5 block">
|
||||
{t("openclaw.agents.maxConcurrent")}
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={maxConcurrent}
|
||||
onChange={(e) => setMaxConcurrent(e.target.value)}
|
||||
placeholder="4"
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save button */}
|
||||
<div className="flex justify-end">
|
||||
<Button size="sm" onClick={handleSave} disabled={saving}>
|
||||
<Save className="w-4 h-4 mr-1" />
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentsDefaultsPanel;
|
||||
@@ -0,0 +1,179 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Plus, Trash2, Save, Eye, EyeOff } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { openclawApi } from "@/lib/api/openclaw";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { OpenClawEnvConfig } from "@/types";
|
||||
|
||||
interface EnvEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
isNew?: boolean;
|
||||
}
|
||||
|
||||
const EnvPanel: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [entries, setEntries] = useState<EnvEntry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [visibleKeys, setVisibleKeys] = useState<Set<string>>(new Set());
|
||||
|
||||
const loadEnv = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const env = await openclawApi.getEnv();
|
||||
const items: EnvEntry[] = Object.entries(env).map(([key, value]) => ({
|
||||
key,
|
||||
value: String(value ?? ""),
|
||||
}));
|
||||
setEntries(items.length > 0 ? items : []);
|
||||
} catch (err) {
|
||||
toast.error(t("openclaw.env.loadFailed"));
|
||||
console.error("Failed to load env config:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadEnv();
|
||||
}, [loadEnv]);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
const env: OpenClawEnvConfig = {};
|
||||
for (const entry of entries) {
|
||||
const trimmedKey = entry.key.trim();
|
||||
if (trimmedKey) {
|
||||
env[trimmedKey] = entry.value;
|
||||
}
|
||||
}
|
||||
await openclawApi.setEnv(env);
|
||||
toast.success(t("openclaw.env.saveSuccess"));
|
||||
// Reload to normalize
|
||||
await loadEnv();
|
||||
} catch (err) {
|
||||
toast.error(t("openclaw.env.saveFailed"));
|
||||
console.error("Failed to save env config:", err);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const addEntry = () => {
|
||||
setEntries((prev) => [...prev, { key: "", value: "", isNew: true }]);
|
||||
};
|
||||
|
||||
const removeEntry = (index: number) => {
|
||||
setEntries((prev) => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const updateEntry = (index: number, field: "key" | "value", val: string) => {
|
||||
setEntries((prev) =>
|
||||
prev.map((entry, i) =>
|
||||
i === index ? { ...entry, [field]: val } : entry,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const toggleVisibility = (key: string) => {
|
||||
setVisibleKeys((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(key)) {
|
||||
next.delete(key);
|
||||
} else {
|
||||
next.add(key);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const isApiKey = (key: string) => /key|token|secret|password/i.test(key);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="px-6 pt-4 pb-8 flex items-center justify-center min-h-[200px]">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("common.loading")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-6 pt-4 pb-8">
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{t("openclaw.env.description")}
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{entries.map((entry, index) => {
|
||||
const sensitive = isApiKey(entry.key);
|
||||
const visible = visibleKeys.has(`${index}`);
|
||||
|
||||
return (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<div className="w-[200px] flex-shrink-0">
|
||||
<Input
|
||||
value={entry.key}
|
||||
onChange={(e) => updateEntry(index, "key", e.target.value)}
|
||||
placeholder={t("openclaw.env.keyPlaceholder")}
|
||||
className="font-mono text-xs"
|
||||
autoFocus={entry.isNew}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center gap-1">
|
||||
<Input
|
||||
type={sensitive && !visible ? "password" : "text"}
|
||||
value={entry.value}
|
||||
onChange={(e) => updateEntry(index, "value", e.target.value)}
|
||||
placeholder={t("openclaw.env.valuePlaceholder")}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
{sensitive && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="flex-shrink-0 h-9 w-9 text-muted-foreground"
|
||||
onClick={() => toggleVisibility(`${index}`)}
|
||||
>
|
||||
{visible ? (
|
||||
<EyeOff className="w-4 h-4" />
|
||||
) : (
|
||||
<Eye className="w-4 h-4" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="flex-shrink-0 h-9 w-9 text-muted-foreground hover:text-destructive"
|
||||
onClick={() => removeEntry(index)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<Button variant="outline" size="sm" onClick={addEntry}>
|
||||
<Plus className="w-4 h-4 mr-1" />
|
||||
{t("openclaw.env.add")}
|
||||
</Button>
|
||||
<div className="flex-1" />
|
||||
<Button size="sm" onClick={handleSave} disabled={saving}>
|
||||
<Save className="w-4 h-4 mr-1" />
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvPanel;
|
||||
@@ -0,0 +1,204 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Plus, Trash2, Save } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { openclawApi } from "@/lib/api/openclaw";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import type { OpenClawToolsConfig } from "@/types";
|
||||
|
||||
const PROFILE_OPTIONS = ["default", "strict", "permissive", "custom"];
|
||||
|
||||
const ToolsPanel: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [config, setConfig] = useState<OpenClawToolsConfig>({});
|
||||
const [allowList, setAllowList] = useState<string[]>([]);
|
||||
const [denyList, setDenyList] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const loadTools = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const tools = await openclawApi.getTools();
|
||||
setConfig(tools);
|
||||
setAllowList(tools.allow ?? []);
|
||||
setDenyList(tools.deny ?? []);
|
||||
} catch (err) {
|
||||
toast.error(t("openclaw.tools.loadFailed"));
|
||||
console.error("Failed to load tools config:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadTools();
|
||||
}, [loadTools]);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
const { profile, allow, deny, ...other } = config;
|
||||
const newConfig: OpenClawToolsConfig = {
|
||||
...other,
|
||||
profile: config.profile,
|
||||
allow: allowList.filter((s) => s.trim()),
|
||||
deny: denyList.filter((s) => s.trim()),
|
||||
};
|
||||
await openclawApi.setTools(newConfig);
|
||||
toast.success(t("openclaw.tools.saveSuccess"));
|
||||
await loadTools();
|
||||
} catch (err) {
|
||||
toast.error(t("openclaw.tools.saveFailed"));
|
||||
console.error("Failed to save tools config:", err);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const updateListItem = (
|
||||
list: string[],
|
||||
setList: React.Dispatch<React.SetStateAction<string[]>>,
|
||||
index: number,
|
||||
value: string,
|
||||
) => {
|
||||
setList(list.map((item, i) => (i === index ? value : item)));
|
||||
};
|
||||
|
||||
const removeListItem = (
|
||||
list: string[],
|
||||
setList: React.Dispatch<React.SetStateAction<string[]>>,
|
||||
index: number,
|
||||
) => {
|
||||
setList(list.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="px-6 pt-4 pb-8 flex items-center justify-center min-h-[200px]">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("common.loading")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-6 pt-4 pb-8">
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
{t("openclaw.tools.description")}
|
||||
</p>
|
||||
|
||||
{/* Profile selector */}
|
||||
<div className="mb-6">
|
||||
<Label className="mb-2 block">{t("openclaw.tools.profile")}</Label>
|
||||
<Select
|
||||
value={config.profile ?? "default"}
|
||||
onValueChange={(val) =>
|
||||
setConfig((prev) => ({ ...prev, profile: val }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[200px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{PROFILE_OPTIONS.map((opt) => (
|
||||
<SelectItem key={opt} value={opt}>
|
||||
{t(`openclaw.tools.profiles.${opt}`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Allow list */}
|
||||
<div className="mb-6">
|
||||
<Label className="mb-2 block">{t("openclaw.tools.allowList")}</Label>
|
||||
<div className="space-y-2">
|
||||
{allowList.map((item, index) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<Input
|
||||
value={item}
|
||||
onChange={(e) =>
|
||||
updateListItem(allowList, setAllowList, index, e.target.value)
|
||||
}
|
||||
placeholder={t("openclaw.tools.patternPlaceholder")}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="flex-shrink-0 h-9 w-9 text-muted-foreground hover:text-destructive"
|
||||
onClick={() => removeListItem(allowList, setAllowList, index)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setAllowList((prev) => [...prev, ""])}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-1" />
|
||||
{t("openclaw.tools.addAllow")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Deny list */}
|
||||
<div className="mb-6">
|
||||
<Label className="mb-2 block">{t("openclaw.tools.denyList")}</Label>
|
||||
<div className="space-y-2">
|
||||
{denyList.map((item, index) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<Input
|
||||
value={item}
|
||||
onChange={(e) =>
|
||||
updateListItem(denyList, setDenyList, index, e.target.value)
|
||||
}
|
||||
placeholder={t("openclaw.tools.patternPlaceholder")}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="flex-shrink-0 h-9 w-9 text-muted-foreground hover:text-destructive"
|
||||
onClick={() => removeListItem(denyList, setDenyList, index)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setDenyList((prev) => [...prev, ""])}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-1" />
|
||||
{t("openclaw.tools.addDeny")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save button */}
|
||||
<div className="flex justify-end">
|
||||
<Button size="sm" onClick={handleSave} disabled={saving}>
|
||||
<Save className="w-4 h-4 mr-1" />
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToolsPanel;
|
||||
+70
-3
@@ -1,12 +1,25 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { OpenClawDefaultModel, OpenClawModelCatalogEntry } from "@/types";
|
||||
import type {
|
||||
OpenClawDefaultModel,
|
||||
OpenClawModelCatalogEntry,
|
||||
OpenClawAgentsDefaults,
|
||||
OpenClawEnvConfig,
|
||||
OpenClawToolsConfig,
|
||||
} from "@/types";
|
||||
|
||||
/**
|
||||
* OpenClaw agents configuration API
|
||||
* OpenClaw configuration API
|
||||
*
|
||||
* Manages agents.defaults configuration in ~/.openclaw/openclaw.json
|
||||
* Manages ~/.openclaw/openclaw.json sections:
|
||||
* - agents.defaults (model, catalog)
|
||||
* - env (environment variables)
|
||||
* - tools (permissions)
|
||||
*/
|
||||
export const openclawApi = {
|
||||
// ============================================================
|
||||
// Agents Configuration
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Get default model configuration (agents.defaults.model)
|
||||
*/
|
||||
@@ -40,6 +53,24 @@ export const openclawApi = {
|
||||
return await invoke("set_openclaw_model_catalog", { catalog });
|
||||
},
|
||||
|
||||
/**
|
||||
* Get full agents.defaults config (all fields)
|
||||
*/
|
||||
async getAgentsDefaults(): Promise<OpenClawAgentsDefaults | null> {
|
||||
return await invoke("get_openclaw_agents_defaults");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set full agents.defaults config (all fields)
|
||||
*/
|
||||
async setAgentsDefaults(defaults: OpenClawAgentsDefaults): Promise<void> {
|
||||
return await invoke("set_openclaw_agents_defaults", { defaults });
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// Provider Import
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Import providers from live config (openclaw.json) to database
|
||||
*/
|
||||
@@ -53,4 +84,40 @@ export const openclawApi = {
|
||||
async getLiveProviderIds(): Promise<string[]> {
|
||||
return await invoke("get_openclaw_live_provider_ids");
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// Env Configuration
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Get env config (env section of openclaw.json)
|
||||
*/
|
||||
async getEnv(): Promise<OpenClawEnvConfig> {
|
||||
return await invoke("get_openclaw_env");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set env config (env section of openclaw.json)
|
||||
*/
|
||||
async setEnv(env: OpenClawEnvConfig): Promise<void> {
|
||||
return await invoke("set_openclaw_env", { env });
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// Tools Configuration
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Get tools config (tools section of openclaw.json)
|
||||
*/
|
||||
async getTools(): Promise<OpenClawToolsConfig> {
|
||||
return await invoke("get_openclaw_tools");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set tools config (tools section of openclaw.json)
|
||||
*/
|
||||
async setTools(tools: OpenClawToolsConfig): Promise<void> {
|
||||
return await invoke("set_openclaw_tools", { tools });
|
||||
},
|
||||
};
|
||||
|
||||
@@ -467,3 +467,23 @@ export interface OpenClawProviderConfig {
|
||||
api?: string; // API 协议类型(如 "openai-completions"、"anthropic")
|
||||
models?: OpenClawModel[]; // 可用模型列表
|
||||
}
|
||||
|
||||
// OpenClaw agents.defaults 完整配置
|
||||
export interface OpenClawAgentsDefaults {
|
||||
model?: OpenClawDefaultModel;
|
||||
models?: Record<string, OpenClawModelCatalogEntry>;
|
||||
[key: string]: unknown; // preserve unknown fields
|
||||
}
|
||||
|
||||
// OpenClaw env 配置(openclaw.json 的 env 节点)
|
||||
export interface OpenClawEnvConfig {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// OpenClaw tools 配置(openclaw.json 的 tools 节点)
|
||||
export interface OpenClawToolsConfig {
|
||||
profile?: string;
|
||||
allow?: string[];
|
||||
deny?: string[];
|
||||
[key: string]: unknown; // preserve unknown fields
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user