From 8e219b5eb1eec51de0313edb1fac8a8752237c49 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 19 Feb 2026 20:47:55 +0800 Subject: [PATCH] feat(omo): add OMO Slim (oh-my-opencode-slim) support Implement full OMO Slim profile management to align with ai-toolbox: - Backend: Slim service methods, DAO, Tauri commands, plugin conflict handling - Frontend: types, API, query hooks, form integration with isSlim parameterization - Slim variant: 6 agents (no categories), separate config file and plugin name - Mutual exclusion: standard OMO and Slim cannot coexist as plugins - i18n: zh/en/ja translations for all Slim agent descriptions --- src-tauri/src/commands/config.rs | 12 +- src-tauri/src/commands/omo.rs | 49 ++++ src-tauri/src/database/dao/omo.rs | 19 ++ src-tauri/src/database/dao/providers.rs | 127 +++++++++ src-tauri/src/lib.rs | 35 ++- src-tauri/src/opencode_config.rs | 11 + src-tauri/src/services/omo.rs | 241 ++++++++++++++++-- src-tauri/src/services/provider/mod.rs | 86 ++++++- src/App.tsx | 27 +- src/components/providers/ProviderCard.tsx | 33 ++- src/components/providers/ProviderList.tsx | 36 ++- .../providers/forms/OmoCommonConfigEditor.tsx | 3 + .../providers/forms/OmoFormFields.tsx | 130 ++++++---- .../providers/forms/OmoGlobalConfigFields.tsx | 106 +++++--- .../providers/forms/ProviderForm.tsx | 67 +++-- .../providers/forms/hooks/useOmoDraftState.ts | 44 +++- .../forms/hooks/useOmoModelSource.ts | 2 +- src/config/opencodeProviderPresets.ts | 13 + src/i18n/locales/en.json | 16 ++ src/i18n/locales/ja.json | 16 ++ src/i18n/locales/zh.json | 16 ++ src/lib/api/config.ts | 2 +- src/lib/api/omo.ts | 10 + src/lib/query/omo.ts | 101 +++++++- src/types.ts | 3 +- src/types/omo.ts | 136 ++++++++++ 26 files changed, 1176 insertions(+), 165 deletions(-) diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index 87430b46f..6fd8c4dad 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -215,7 +215,7 @@ pub async fn set_common_config_snippet( ) -> Result<(), String> { if !snippet.trim().is_empty() { match app_type.as_str() { - "claude" | "gemini" | "omo" => { + "claude" | "gemini" | "omo" | "omo-slim" => { serde_json::from_str::(&snippet) .map_err(invalid_json_format_error)?; } @@ -245,6 +245,16 @@ pub async fn set_common_config_snippet( crate::services::OmoService::write_config_to_file(state.inner()) .map_err(|e| e.to_string())?; } + if app_type == "omo-slim" + && state + .db + .get_current_omo_slim_provider("opencode") + .map_err(|e| e.to_string())? + .is_some() + { + crate::services::OmoService::write_config_to_file_slim(state.inner()) + .map_err(|e| e.to_string())?; + } Ok(()) } diff --git a/src-tauri/src/commands/omo.rs b/src-tauri/src/commands/omo.rs index 9b3b4be47..a2b91794d 100644 --- a/src-tauri/src/commands/omo.rs +++ b/src-tauri/src/commands/omo.rs @@ -48,3 +48,52 @@ pub async fn get_omo_provider_count(state: State<'_, AppState>) -> Result Result { + OmoService::read_local_file_slim().map_err(|e| e.to_string()) +} + +#[tauri::command] +pub async fn get_current_omo_slim_provider_id( + state: State<'_, AppState>, +) -> Result { + let provider = state + .db + .get_current_omo_slim_provider("opencode") + .map_err(|e| e.to_string())?; + Ok(provider.map(|p| p.id).unwrap_or_default()) +} + +#[tauri::command] +pub async fn disable_current_omo_slim(state: State<'_, AppState>) -> Result<(), String> { + let providers = state + .db + .get_all_providers("opencode") + .map_err(|e| e.to_string())?; + for (id, p) in &providers { + if p.category.as_deref() == Some("omo-slim") { + state + .db + .clear_omo_slim_provider_current("opencode", id) + .map_err(|e| e.to_string())?; + } + } + OmoService::delete_config_file_slim().map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +pub async fn get_omo_slim_provider_count(state: State<'_, AppState>) -> Result { + let providers = state + .db + .get_all_providers("opencode") + .map_err(|e| e.to_string())?; + let count = providers + .values() + .filter(|p| p.category.as_deref() == Some("omo-slim")) + .count(); + Ok(count) +} diff --git a/src-tauri/src/database/dao/omo.rs b/src-tauri/src/database/dao/omo.rs index 00f4fdb34..fc01631fc 100644 --- a/src-tauri/src/database/dao/omo.rs +++ b/src-tauri/src/database/dao/omo.rs @@ -70,4 +70,23 @@ impl Database { self.set_setting("common_config_omo", &json_str)?; Ok(()) } + + // ── OMO Slim global config ────────────────────────────────── + + pub fn get_omo_slim_global_config(&self) -> Result { + let json_str = self.get_setting("common_config_omo_slim")?; + match json_str { + Some(s) => serde_json::from_str::(&s).map_err(|e| { + AppError::Config(format!("Failed to parse common_config_omo_slim: {e}")) + }), + None => Ok(OmoGlobalConfig::default()), + } + } + + pub fn save_omo_slim_global_config(&self, config: &OmoGlobalConfig) -> Result<(), AppError> { + let json_str = serde_json::to_string(config) + .map_err(|e| AppError::Config(format!("JSON serialization failed: {e}")))?; + self.set_setting("common_config_omo_slim", &json_str)?; + Ok(()) + } } diff --git a/src-tauri/src/database/dao/providers.rs b/src-tauri/src/database/dao/providers.rs index 0bf9961d2..c514b3661 100644 --- a/src-tauri/src/database/dao/providers.rs +++ b/src-tauri/src/database/dao/providers.rs @@ -481,4 +481,131 @@ impl Database { in_failover_queue: false, })) } + + // ── OMO Slim provider management ──────────────────────────── + + pub fn set_omo_slim_provider_current( + &self, + app_type: &str, + provider_id: &str, + ) -> Result<(), AppError> { + let mut conn = lock_conn!(self.conn); + let tx = conn + .transaction() + .map_err(|e| AppError::Database(e.to_string()))?; + tx.execute( + "UPDATE providers SET is_current = 0 WHERE app_type = ?1 AND category = 'omo-slim'", + params![app_type], + ) + .map_err(|e| AppError::Database(e.to_string()))?; + let updated = tx + .execute( + "UPDATE providers SET is_current = 1 WHERE id = ?1 AND app_type = ?2 AND category = 'omo-slim'", + params![provider_id, app_type], + ) + .map_err(|e| AppError::Database(e.to_string()))?; + if updated != 1 { + return Err(AppError::Database(format!( + "Failed to set OMO Slim provider current: provider '{provider_id}' not found in app '{app_type}'" + ))); + } + tx.commit().map_err(|e| AppError::Database(e.to_string()))?; + Ok(()) + } + + pub fn is_omo_slim_provider_current( + &self, + app_type: &str, + provider_id: &str, + ) -> Result { + let conn = lock_conn!(self.conn); + match conn.query_row( + "SELECT is_current FROM providers + WHERE id = ?1 AND app_type = ?2 AND category = 'omo-slim'", + params![provider_id, app_type], + |row| row.get(0), + ) { + Ok(is_current) => Ok(is_current), + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(false), + Err(e) => Err(AppError::Database(e.to_string())), + } + } + + pub fn clear_omo_slim_provider_current( + &self, + app_type: &str, + provider_id: &str, + ) -> Result<(), AppError> { + let conn = lock_conn!(self.conn); + conn.execute( + "UPDATE providers SET is_current = 0 + WHERE id = ?1 AND app_type = ?2 AND category = 'omo-slim'", + params![provider_id, app_type], + ) + .map_err(|e| AppError::Database(e.to_string()))?; + Ok(()) + } + + pub fn get_current_omo_slim_provider( + &self, + app_type: &str, + ) -> Result, AppError> { + let conn = lock_conn!(self.conn); + let row_data: Result = conn.query_row( + "SELECT id, name, settings_config, category, created_at, sort_index, notes, meta + FROM providers + WHERE app_type = ?1 AND category = 'omo-slim' AND is_current = 1 + LIMIT 1", + params![app_type], + |row| { + Ok(( + row.get(0)?, + row.get(1)?, + row.get(2)?, + row.get(3)?, + row.get(4)?, + row.get(5)?, + row.get(6)?, + row.get(7)?, + )) + }, + ); + + let (id, name, settings_config_str, category, created_at, sort_index, notes, meta_str) = + match row_data { + Ok(v) => v, + Err(rusqlite::Error::QueryReturnedNoRows) => return Ok(None), + Err(e) => return Err(AppError::Database(e.to_string())), + }; + + let settings_config = serde_json::from_str(&settings_config_str).map_err(|e| { + AppError::Database(format!( + "Failed to parse OMO Slim provider settings_config (provider_id={id}): {e}" + )) + })?; + let meta: crate::provider::ProviderMeta = if meta_str.trim().is_empty() { + crate::provider::ProviderMeta::default() + } else { + serde_json::from_str(&meta_str).map_err(|e| { + AppError::Database(format!( + "Failed to parse OMO Slim provider meta (provider_id={id}): {e}" + )) + })? + }; + + Ok(Some(Provider { + id, + name, + settings_config, + website_url: None, + category, + created_at, + sort_index, + notes, + meta: Some(meta), + icon: None, + icon_color: None, + in_failover_queue: false, + })) + } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2293899ff..11f0abecc 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -526,7 +526,36 @@ pub fn run() { } } - // 2.3 OpenClaw 供应商导入(累加式模式,需特殊处理) + // 2.3 OMO Slim config import (when no omo-slim provider in DB, import from local) + { + let has_omo_slim = app_state + .db + .get_all_providers("opencode") + .map(|providers| { + providers + .values() + .any(|p| p.category.as_deref() == Some("omo-slim")) + }) + .unwrap_or(false); + if !has_omo_slim { + match crate::services::OmoService::import_from_local_slim(&app_state) { + Ok(provider) => { + log::info!( + "✓ Imported OMO Slim config from local as provider '{}'", + provider.name + ); + } + Err(AppError::OmoConfigNotFound) => { + log::debug!("○ No OMO Slim config to import"); + } + Err(e) => { + log::warn!("✗ Failed to import OMO Slim config from local: {e}"); + } + } + } + } + + // 2.4 OpenClaw 供应商导入(累加式模式,需特殊处理) // OpenClaw 与 OpenCode 类似:配置文件中可同时存在多个供应商 // 需要遍历 models.providers 字段下的每个供应商并导入 match crate::services::provider::import_openclaw_providers_from_live(&app_state) { @@ -1035,6 +1064,10 @@ pub fn run() { commands::get_current_omo_provider_id, commands::get_omo_provider_count, commands::disable_current_omo, + commands::read_omo_slim_local_file, + commands::get_current_omo_slim_provider_id, + commands::get_omo_slim_provider_count, + commands::disable_current_omo_slim, // Workspace files (OpenClaw) commands::read_workspace_file, commands::write_workspace_file, diff --git a/src-tauri/src/opencode_config.rs b/src-tauri/src/opencode_config.rs index f79fcbc4b..34806d678 100644 --- a/src-tauri/src/opencode_config.rs +++ b/src-tauri/src/opencode_config.rs @@ -145,14 +145,25 @@ pub fn add_plugin(plugin_name: &str) -> Result<(), AppError> { match plugins { Some(arr) => { + // Mutual exclusion: standard OMO and OMO Slim cannot coexist as plugins if plugin_name.starts_with("oh-my-opencode") && !plugin_name.starts_with("oh-my-opencode-slim") { + // Adding standard OMO -> remove all Slim variants arr.retain(|v| { v.as_str() .map(|s| !s.starts_with("oh-my-opencode-slim")) .unwrap_or(true) }); + } else if plugin_name.starts_with("oh-my-opencode-slim") { + // Adding Slim -> remove all standard OMO variants (but keep slim) + arr.retain(|v| { + v.as_str() + .map(|s| { + !s.starts_with("oh-my-opencode") || s.starts_with("oh-my-opencode-slim") + }) + .unwrap_or(true) + }); } let already_exists = arr.iter().any(|v| v.as_str() == Some(plugin_name)); diff --git a/src-tauri/src/services/omo.rs b/src-tauri/src/services/omo.rs index 2e9cf4905..3e963b3f9 100644 --- a/src-tauri/src/services/omo.rs +++ b/src-tauri/src/services/omo.rs @@ -52,26 +52,48 @@ impl OmoService { .ok_or_else(|| AppError::Config("Expected JSON object".to_string())) } - fn extract_other_fields(obj: &Map) -> Map { - const KNOWN_KEYS: [&str; 13] = [ - "$schema", - "agents", - "categories", - "sisyphus_agent", - "disabled_agents", - "disabled_mcps", - "disabled_hooks", - "disabled_skills", - "lsp", - "experimental", - "background_task", - "browser_automation_engine", - "claude_code", - ]; + const KNOWN_KEYS: [&str; 13] = [ + "$schema", + "agents", + "categories", + "sisyphus_agent", + "disabled_agents", + "disabled_mcps", + "disabled_hooks", + "disabled_skills", + "lsp", + "experimental", + "background_task", + "browser_automation_engine", + "claude_code", + ]; + const KNOWN_KEYS_SLIM: [&str; 8] = [ + "$schema", + "agents", + "sisyphus_agent", + "disabled_agents", + "disabled_mcps", + "disabled_hooks", + "lsp", + "experimental", + ]; + + fn extract_other_fields(obj: &Map) -> Map { + Self::extract_other_fields_with_keys(obj, &Self::KNOWN_KEYS) + } + + fn extract_other_fields_slim(obj: &Map) -> Map { + Self::extract_other_fields_with_keys(obj, &Self::KNOWN_KEYS_SLIM) + } + + fn extract_other_fields_with_keys( + obj: &Map, + known: &[&str], + ) -> Map { let mut other = Map::new(); for (k, v) in obj { - if !KNOWN_KEYS.contains(&k.as_str()) { + if !known.contains(&k.as_str()) { other.insert(k.clone(), v.clone()); } } @@ -142,6 +164,24 @@ impl OmoService { } } + fn config_path_slim() -> PathBuf { + get_opencode_dir().join("oh-my-opencode-slim.jsonc") + } + + fn resolve_local_config_path_slim() -> Result { + let config_path = Self::config_path_slim(); + if config_path.exists() { + return Ok(config_path); + } + + let json_path = config_path.with_extension("json"); + if json_path.exists() { + return Ok(json_path); + } + + Err(AppError::OmoConfigNotFound) + } + pub fn delete_config_file() -> Result<(), AppError> { let config_path = Self::config_path(); if config_path.exists() { @@ -152,6 +192,16 @@ impl OmoService { Ok(()) } + pub fn delete_config_file_slim() -> Result<(), AppError> { + let config_path = Self::config_path_slim(); + if config_path.exists() { + std::fs::remove_file(&config_path).map_err(|e| AppError::io(&config_path, e))?; + log::info!("OMO Slim config file deleted: {config_path:?}"); + } + crate::opencode_config::remove_plugin_by_prefix("oh-my-opencode-slim")?; + Ok(()) + } + pub fn write_config_to_file(state: &AppState) -> Result<(), AppError> { let global = state.db.get_omo_global_config()?; let current_omo = state.db.get_current_omo_provider("opencode")?; @@ -183,6 +233,36 @@ impl OmoService { Ok(()) } + pub fn write_config_to_file_slim(state: &AppState) -> Result<(), AppError> { + let global = state.db.get_omo_slim_global_config()?; + let current_omo = state.db.get_current_omo_slim_provider("opencode")?; + + let profile_data = current_omo.as_ref().map(|p| { + let agents = p.settings_config.get("agents").cloned(); + let other_fields = p.settings_config.get("otherFields").cloned(); + let use_common_config = p + .settings_config + .get("useCommonConfig") + .and_then(|v| v.as_bool()) + .unwrap_or(true); + (agents, None, other_fields, use_common_config) + }); + + let merged = Self::merge_config_slim(&global, profile_data.as_ref()); + let config_path = Self::config_path_slim(); + + if let Some(parent) = config_path.parent() { + std::fs::create_dir_all(parent).map_err(|e| AppError::io(parent, e))?; + } + + write_json_file(&config_path, &merged)?; + + crate::opencode_config::add_plugin("oh-my-opencode-slim@latest")?; + + log::info!("OMO Slim config written to {config_path:?}"); + Ok(()) + } + fn merge_config(global: &OmoGlobalConfig, profile_data: Option<&OmoProfileData>) -> Value { let mut result = Map::new(); let use_common_config = profile_data.map(|(_, _, _, v)| *v).unwrap_or(true); @@ -219,6 +299,36 @@ impl OmoService { Value::Object(result) } + /// Merge config for Slim variant (no categories, no disabled_skills, no background_task, etc.) + fn merge_config_slim(global: &OmoGlobalConfig, profile_data: Option<&OmoProfileData>) -> Value { + let mut result = Map::new(); + let use_common_config = profile_data.map(|(_, _, _, v)| *v).unwrap_or(true); + + if use_common_config { + if let Some(url) = &global.schema_url { + result.insert("$schema".to_string(), Value::String(url.clone())); + } + + Self::insert_opt_value(&mut result, "sisyphus_agent", &global.sisyphus_agent); + Self::insert_string_array(&mut result, "disabled_agents", &global.disabled_agents); + Self::insert_string_array(&mut result, "disabled_mcps", &global.disabled_mcps); + Self::insert_string_array(&mut result, "disabled_hooks", &global.disabled_hooks); + // Slim does NOT support: disabled_skills, background_task, browser_automation_engine, claude_code + Self::insert_opt_value(&mut result, "lsp", &global.lsp); + Self::insert_opt_value(&mut result, "experimental", &global.experimental); + + Self::insert_object_entries(&mut result, global.other_fields.as_ref()); + } + + if let Some((agents, _categories, other_fields, _)) = profile_data { + Self::insert_opt_value(&mut result, "agents", agents); + // Slim does NOT have categories + Self::insert_object_entries(&mut result, other_fields.as_ref()); + } + + Value::Object(result) + } + pub fn import_from_local(state: &AppState) -> Result { let actual_path = Self::resolve_local_config_path()?; Self::import_from_path(state, &actual_path) @@ -277,6 +387,58 @@ impl OmoService { Ok(provider) } + pub fn import_from_local_slim(state: &AppState) -> Result { + let actual_path = Self::resolve_local_config_path_slim()?; + let obj = Self::read_jsonc_object(&actual_path)?; + + let mut settings = Map::new(); + if let Some(agents) = obj.get("agents") { + settings.insert("agents".to_string(), agents.clone()); + } + // Slim has no categories + settings.insert("useCommonConfig".to_string(), Value::Bool(true)); + + let other = Self::extract_other_fields_slim(&obj); + if !other.is_empty() { + settings.insert("otherFields".to_string(), Value::Object(other)); + } + + let mut global = state.db.get_omo_slim_global_config()?; + Self::merge_global_from_obj(&obj, &mut global); + global.updated_at = chrono::Utc::now().to_rfc3339(); + state.db.save_omo_slim_global_config(&global)?; + + let provider_id = format!("omo-slim-{}", uuid::Uuid::new_v4()); + let name = format!( + "Imported Slim {}", + chrono::Local::now().format("%Y-%m-%d %H:%M") + ); + let settings_config = + serde_json::to_value(&settings).unwrap_or_else(|_| serde_json::json!({})); + + let provider = crate::provider::Provider { + id: provider_id, + name, + settings_config, + website_url: None, + category: Some("omo-slim".to_string()), + created_at: Some(chrono::Utc::now().timestamp_millis()), + sort_index: None, + notes: None, + meta: None, + icon: None, + icon_color: None, + in_failover_queue: false, + }; + + state.db.save_provider("opencode", &provider)?; + state + .db + .set_omo_slim_provider_current("opencode", &provider.id)?; + Self::write_config_to_file_slim(state)?; + Ok(provider) + } + pub fn read_local_file() -> Result { let actual_path = Self::resolve_local_config_path()?; let metadata = std::fs::metadata(&actual_path).ok(); @@ -293,6 +455,22 @@ impl OmoService { )) } + pub fn read_local_file_slim() -> Result { + let actual_path = Self::resolve_local_config_path_slim()?; + let metadata = std::fs::metadata(&actual_path).ok(); + let last_modified = metadata + .and_then(|m| m.modified().ok()) + .map(|t| chrono::DateTime::::from(t).to_rfc3339()); + + let obj = Self::read_jsonc_object(&actual_path)?; + + Ok(Self::build_local_file_data_slim_from_obj( + &obj, + actual_path.to_string_lossy().to_string(), + last_modified, + )) + } + fn build_local_file_data_from_obj( obj: &Map, file_path: String, @@ -322,6 +500,35 @@ impl OmoService { } } + fn build_local_file_data_slim_from_obj( + obj: &Map, + file_path: String, + last_modified: Option, + ) -> OmoLocalFileData { + let agents = obj.get("agents").cloned(); + // Slim has no categories + + let other = Self::extract_other_fields_slim(obj); + let other_fields = if other.is_empty() { + None + } else { + Some(Value::Object(other)) + }; + + let mut global = OmoGlobalConfig::default(); + Self::merge_global_from_obj(obj, &mut global); + global.other_fields = other_fields.clone(); + + OmoLocalFileData { + agents, + categories: None, + other_fields, + global, + file_path, + last_modified, + } + } + fn strip_jsonc_comments(input: &str) -> String { let mut result = String::with_capacity(input.len()); let mut chars = input.chars().peekable(); diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index 5f523c722..3327428d2 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -167,8 +167,7 @@ impl ProviderService { // Additive mode apps (OpenCode, OpenClaw) - always write to live config if app_type.is_additive_mode() { // OMO providers use exclusive mode and write to dedicated config file. - if matches!(app_type, AppType::OpenCode) - && provider.category.as_deref() == Some("omo") + if matches!(app_type, AppType::OpenCode) && provider.category.as_deref() == Some("omo") { // Do not auto-enable newly added OMO providers. // Users must explicitly switch/apply an OMO provider to activate it. @@ -207,8 +206,7 @@ impl ProviderService { // Additive mode apps (OpenCode, OpenClaw) - always update in live config if app_type.is_additive_mode() { - if matches!(app_type, AppType::OpenCode) - && provider.category.as_deref() == Some("omo") + if matches!(app_type, AppType::OpenCode) && provider.category.as_deref() == Some("omo") { let is_omo_current = state .db @@ -218,6 +216,17 @@ impl ProviderService { } return Ok(true); } + if matches!(app_type, AppType::OpenCode) + && provider.category.as_deref() == Some("omo-slim") + { + let is_current = state + .db + .is_omo_slim_provider_current(app_type.as_str(), &provider.id)?; + if is_current { + crate::services::OmoService::write_config_to_file_slim(state)?; + } + return Ok(true); + } write_live_snapshot(&app_type, &provider)?; return Ok(true); } @@ -264,14 +273,12 @@ impl ProviderService { // Additive mode apps - no current provider concept if app_type.is_additive_mode() { if matches!(app_type, AppType::OpenCode) { - let is_omo = state + let provider_category = state .db .get_provider_by_id(id, app_type.as_str())? - .and_then(|p| p.category) - .as_deref() - == Some("omo"); + .and_then(|p| p.category); - if is_omo { + if provider_category.as_deref() == Some("omo") { let was_current = state.db.is_omo_provider_current(app_type.as_str(), id)?; let omo_count = state .db @@ -292,6 +299,30 @@ impl ProviderService { } return Ok(()); } + + if provider_category.as_deref() == Some("omo-slim") { + let was_current = state + .db + .is_omo_slim_provider_current(app_type.as_str(), id)?; + let slim_count = state + .db + .get_all_providers(app_type.as_str())? + .values() + .filter(|p| p.category.as_deref() == Some("omo-slim")) + .count(); + + if slim_count <= 1 && was_current { + return Err(AppError::Message( + "无法删除当前启用的最后一个 OMO Slim 配置,请先停用".to_string(), + )); + } + + state.db.delete_provider(app_type.as_str(), id)?; + if was_current { + crate::services::OmoService::delete_config_file_slim()?; + } + return Ok(()); + } } // Remove from database state.db.delete_provider(app_type.as_str(), id)?; @@ -329,14 +360,12 @@ impl ProviderService { ) -> Result<(), AppError> { match app_type { AppType::OpenCode => { - let is_omo = state + let provider_category = state .db .get_provider_by_id(id, app_type.as_str())? - .and_then(|p| p.category) - .as_deref() - == Some("omo"); + .and_then(|p| p.category); - if is_omo { + if provider_category.as_deref() == Some("omo") { state.db.clear_omo_provider_current(app_type.as_str(), id)?; let still_has_current = state.db.get_current_omo_provider("opencode")?.is_some(); @@ -345,6 +374,19 @@ impl ProviderService { } else { crate::services::OmoService::delete_config_file()?; } + } else if provider_category.as_deref() == Some("omo-slim") { + state + .db + .clear_omo_slim_provider_current(app_type.as_str(), id)?; + let still_has_current = state + .db + .get_current_omo_slim_provider("opencode")? + .is_some(); + if still_has_current { + crate::services::OmoService::write_config_to_file_slim(state)?; + } else { + crate::services::OmoService::delete_config_file_slim()?; + } } else { remove_opencode_provider_from_live(id)?; } @@ -386,6 +428,13 @@ impl ProviderService { return Self::switch_normal(state, app_type, id, &providers); } + // OMO Slim providers are switched through their own exclusive path. + if matches!(app_type, AppType::OpenCode) + && _provider.category.as_deref() == Some("omo-slim") + { + return Self::switch_normal(state, app_type, id, &providers); + } + // Check if proxy takeover mode is active AND proxy server is actually running // Both conditions must be true to use hot-switch mode // Use blocking wait since this is a sync function @@ -463,6 +512,15 @@ impl ProviderService { return Ok(()); } + if matches!(app_type, AppType::OpenCode) && provider.category.as_deref() == Some("omo-slim") + { + state + .db + .set_omo_slim_provider_current(app_type.as_str(), id)?; + crate::services::OmoService::write_config_to_file_slim(state)?; + return Ok(()); + } + // Backfill: Backfill current live config to current provider // Use effective current provider (validated existence) to ensure backfill targets valid provider let current_id = crate::settings::get_effective_current_provider(&state.db, &app_type)?; diff --git a/src/App.tsx b/src/App.tsx index 9bb217db8..74e5ec26a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -61,7 +61,10 @@ import { UniversalProviderPanel } from "@/components/universal"; import { McpIcon } from "@/components/BrandIcons"; import { Button } from "@/components/ui/button"; import { SessionManagerPage } from "@/components/sessions/SessionManagerPage"; -import { useDisableCurrentOmo } from "@/lib/query/omo"; +import { + useDisableCurrentOmo, + useDisableCurrentOmoSlim, +} from "@/lib/query/omo"; import WorkspaceFilesPanel from "@/components/workspace/WorkspaceFilesPanel"; import EnvPanel from "@/components/openclaw/EnvPanel"; import ToolsPanel from "@/components/openclaw/ToolsPanel"; @@ -248,6 +251,23 @@ function App() { }); }; + const disableOmoSlimMutation = useDisableCurrentOmoSlim(); + const handleDisableOmoSlim = () => { + disableOmoSlimMutation.mutate(undefined, { + onSuccess: () => { + toast.success(t("omo.disabled", { defaultValue: "OMO 已停用" })); + }, + onError: (error: Error) => { + toast.error( + t("omo.disableFailed", { + defaultValue: "停用 OMO 失败: {{error}}", + error: extractErrorMessage(error), + }), + ); + }, + }); + }; + useEffect(() => { let unsubscribe: (() => void) | undefined; @@ -746,6 +766,11 @@ function App() { onDisableOmo={ activeApp === "opencode" ? handleDisableOmo : undefined } + onDisableOmoSlim={ + activeApp === "opencode" + ? handleDisableOmoSlim + : undefined + } onDuplicate={handleDuplicateProvider} onConfigureUsage={setUsageProvider} onOpenWebsite={handleOpenWebsite} diff --git a/src/components/providers/ProviderCard.tsx b/src/components/providers/ProviderCard.tsx index 5f7a3eb91..6590cd14c 100644 --- a/src/components/providers/ProviderCard.tsx +++ b/src/components/providers/ProviderCard.tsx @@ -29,11 +29,14 @@ interface ProviderCardProps { isInConfig?: boolean; // OpenCode: 是否已添加到 opencode.json isOmo?: boolean; isLastOmo?: boolean; + isOmoSlim?: boolean; + isLastOmoSlim?: boolean; onSwitch: (provider: Provider) => void; onEdit: (provider: Provider) => void; onDelete: (provider: Provider) => void; onRemoveFromConfig?: (provider: Provider) => void; onDisableOmo?: () => void; + onDisableOmoSlim?: () => void; onConfigureUsage: (provider: Provider) => void; onOpenWebsite: (url: string) => void; onDuplicate: (provider: Provider) => void; @@ -92,11 +95,14 @@ export function ProviderCard({ isInConfig = true, isOmo = false, isLastOmo = false, + isOmoSlim = false, + isLastOmoSlim = false, onSwitch, onEdit, onDelete, onRemoveFromConfig, onDisableOmo, + onDisableOmoSlim, onConfigureUsage, onOpenWebsite, onDuplicate, @@ -117,6 +123,11 @@ export function ProviderCard({ }: ProviderCardProps) { const { t } = useTranslation(); + // OMO and OMO Slim share the same card behavior + const isAnyOmo = isOmo || isOmoSlim; + const isLastAnyOmo = isOmo ? isLastOmo : isLastOmoSlim; + const handleDisableAnyOmo = isOmoSlim ? onDisableOmoSlim : onDisableOmo; + const { data: health } = useProviderHealth(provider.id, appId); const fallbackUrlText = t("provider.notConfigured", { @@ -186,11 +197,11 @@ export function ProviderCard({ }; // 判断是否是"当前使用中"的供应商 - // - OMO 供应商:使用 isCurrent + // - OMO/OMO Slim 供应商:使用 isCurrent // - 累加模式应用(OpenCode 非 OMO / OpenClaw):不存在"当前"概念,始终返回 false // - 故障转移模式:代理实际使用的供应商(activeProviderId) // - 普通模式:isCurrent - const isActiveProvider = isOmo + const isActiveProvider = isAnyOmo ? isCurrent : appId === "opencode" || appId === "openclaw" ? false @@ -198,10 +209,10 @@ export function ProviderCard({ ? activeProviderId === provider.id : isCurrent; - const shouldUseGreen = !isOmo && isProxyTakeover && isActiveProvider; + const shouldUseGreen = !isAnyOmo && isProxyTakeover && isActiveProvider; const shouldUseBlue = - (isOmo && isActiveProvider) || - (!isOmo && !isProxyTakeover && isActiveProvider); + (isAnyOmo && isActiveProvider) || + (!isAnyOmo && !isProxyTakeover && isActiveProvider); return (
)} + {isOmoSlim && ( + + Slim + + )} + {isProxyRunning && isInFailoverQueue && health && ( onSwitch(provider)} onEdit={() => onEdit(provider)} onDuplicate={() => onDuplicate(provider)} @@ -385,7 +402,7 @@ export function ProviderCard({ ? () => onRemoveFromConfig(provider) : undefined } - onDisableOmo={onDisableOmo} + onDisableOmo={handleDisableAnyOmo} onOpenTerminal={ onOpenTerminal ? () => onOpenTerminal(provider) : undefined } diff --git a/src/components/providers/ProviderList.tsx b/src/components/providers/ProviderList.tsx index 58111cba1..a44c27428 100644 --- a/src/components/providers/ProviderList.tsx +++ b/src/components/providers/ProviderList.tsx @@ -33,7 +33,12 @@ import { useAddToFailoverQueue, useRemoveFromFailoverQueue, } from "@/lib/query/failover"; -import { useCurrentOmoProviderId, useOmoProviderCount } from "@/lib/query/omo"; +import { + useCurrentOmoProviderId, + useOmoProviderCount, + useCurrentOmoSlimProviderId, + useOmoSlimProviderCount, +} from "@/lib/query/omo"; import { useCallback } from "react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -47,6 +52,7 @@ interface ProviderListProps { onDelete: (provider: Provider) => void; onRemoveFromConfig?: (provider: Provider) => void; onDisableOmo?: () => void; + onDisableOmoSlim?: () => void; onDuplicate: (provider: Provider) => void; onConfigureUsage?: (provider: Provider) => void; onOpenWebsite: (url: string) => void; @@ -68,6 +74,7 @@ export function ProviderList({ onDelete, onRemoveFromConfig, onDisableOmo, + onDisableOmoSlim, onDuplicate, onConfigureUsage, onOpenWebsite, @@ -135,6 +142,8 @@ export function ProviderList({ const isOpenCode = appId === "opencode"; const { data: currentOmoId } = useCurrentOmoProviderId(isOpenCode); const { data: omoProviderCount } = useOmoProviderCount(isOpenCode); + const { data: currentOmoSlimId } = useCurrentOmoSlimProviderId(isOpenCode); + const { data: omoSlimProviderCount } = useOmoSlimProviderCount(isOpenCode); const getFailoverPriority = useCallback( (providerId: string): number | undefined => { @@ -239,13 +248,20 @@ export function ProviderList({
{filteredProviders.map((provider) => { const isOmo = provider.category === "omo"; + const isOmoSlim = provider.category === "omo-slim"; const isOmoCurrent = isOmo && provider.id === (currentOmoId || ""); + const isOmoSlimCurrent = + isOmoSlim && provider.id === (currentOmoSlimId || ""); return ( void; onEdit: (provider: Provider) => void; onDelete: (provider: Provider) => void; onRemoveFromConfig?: (provider: Provider) => void; onDisableOmo?: () => void; + onDisableOmoSlim?: () => void; onDuplicate: (provider: Provider) => void; onConfigureUsage?: (provider: Provider) => void; onOpenWebsite: (url: string) => void; @@ -401,11 +427,14 @@ function SortableProviderCard({ isInConfig, isOmo, isLastOmo, + isOmoSlim, + isLastOmoSlim, onSwitch, onEdit, onDelete, onRemoveFromConfig, onDisableOmo, + onDisableOmoSlim, onDuplicate, onConfigureUsage, onOpenWebsite, @@ -445,11 +474,14 @@ function SortableProviderCard({ isInConfig={isInConfig} isOmo={isOmo} isLastOmo={isLastOmo} + isOmoSlim={isOmoSlim} + isLastOmoSlim={isLastOmoSlim} onSwitch={onSwitch} onEdit={onEdit} onDelete={onDelete} onRemoveFromConfig={onRemoveFromConfig} onDisableOmo={onDisableOmo} + onDisableOmoSlim={onDisableOmoSlim} onDuplicate={onDuplicate} onConfigureUsage={ onConfigureUsage ? (item) => onConfigureUsage(item) : () => undefined diff --git a/src/components/providers/forms/OmoCommonConfigEditor.tsx b/src/components/providers/forms/OmoCommonConfigEditor.tsx index 575d3c54b..9f0a4a3d9 100644 --- a/src/components/providers/forms/OmoCommonConfigEditor.tsx +++ b/src/components/providers/forms/OmoCommonConfigEditor.tsx @@ -23,6 +23,7 @@ interface OmoCommonConfigEditorProps { onGlobalConfigStateChange: (config: OmoGlobalConfig) => void; globalConfigRef: React.RefObject; fieldsKey: number; + isSlim?: boolean; } export function OmoCommonConfigEditor({ @@ -37,6 +38,7 @@ export function OmoCommonConfigEditor({ onGlobalConfigStateChange, globalConfigRef, fieldsKey, + isSlim = false, }: OmoCommonConfigEditorProps) { const { t } = useTranslation(); const [isDarkMode, setIsDarkMode] = useState(false); @@ -153,6 +155,7 @@ export function OmoCommonConfigEditor({ ref={globalConfigRef as React.Ref} onStateChange={onGlobalConfigStateChange} hideSaveButtons + isSlim={isSlim} />
diff --git a/src/components/providers/forms/OmoFormFields.tsx b/src/components/providers/forms/OmoFormFields.tsx index b4b859ba8..d0ffa02d2 100644 --- a/src/components/providers/forms/OmoFormFields.tsx +++ b/src/components/providers/forms/OmoFormFields.tsx @@ -41,10 +41,11 @@ import { } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; -import { useReadOmoLocalFile } from "@/lib/query/omo"; +import { useReadOmoLocalFile, useReadOmoSlimLocalFile } from "@/lib/query/omo"; import { OMO_BUILTIN_AGENTS, OMO_BUILTIN_CATEGORIES, + OMO_SLIM_BUILTIN_AGENTS, type OmoAgentDef, type OmoCategoryDef, } from "@/types/omo"; @@ -69,12 +70,13 @@ interface OmoFormFieldsProps { >; agents: Record>; onAgentsChange: (agents: Record>) => void; - categories: Record>; - onCategoriesChange: ( + categories?: Record>; + onCategoriesChange?: ( categories: Record>, ) => void; otherFieldsStr: string; onOtherFieldsStrChange: (value: string) => void; + isSlim?: boolean; } export type CustomModelItem = { @@ -121,6 +123,9 @@ function DeferredKeyInput({ } const BUILTIN_AGENT_KEYS = new Set(OMO_BUILTIN_AGENTS.map((a) => a.key)); +const BUILTIN_AGENT_KEYS_SLIM = new Set( + OMO_SLIM_BUILTIN_AGENTS.map((a) => a.key), +); const BUILTIN_CATEGORY_KEYS = new Set(OMO_BUILTIN_CATEGORIES.map((c) => c.key)); const EMPTY_VARIANT_VALUE = "__cc_switch_omo_variant_empty__"; @@ -303,13 +308,21 @@ export function OmoFormFields({ presetMetaMap: _presetMetaMap = {}, agents, onAgentsChange, - categories, + categories = {}, onCategoriesChange, otherFieldsStr, onOtherFieldsStrChange, + isSlim = false, }: OmoFormFieldsProps) { const { t } = useTranslation(); + const builtinAgentDefs = isSlim + ? OMO_SLIM_BUILTIN_AGENTS + : OMO_BUILTIN_AGENTS; + const builtinAgentKeys = isSlim + ? BUILTIN_AGENT_KEYS_SLIM + : BUILTIN_AGENT_KEYS; + const [mainAgentsOpen, setMainAgentsOpen] = useState(true); const [subAgentsOpen, setSubAgentsOpen] = useState(true); const [categoriesOpen, setCategoriesOpen] = useState(true); @@ -329,7 +342,7 @@ export function OmoFormFields({ >({}); const [customAgents, setCustomAgents] = useState(() => - collectCustomModels(agents, BUILTIN_AGENT_KEYS), + collectCustomModels(agents, builtinAgentKeys), ); const [customCategories, setCustomCategories] = useState( @@ -337,7 +350,7 @@ export function OmoFormFields({ ); useEffect(() => { - setCustomAgents(collectCustomModels(agents, BUILTIN_AGENT_KEYS)); + setCustomAgents(collectCustomModels(agents, builtinAgentKeys)); }, [agents]); useEffect(() => { @@ -349,17 +362,18 @@ export function OmoFormFields({ onAgentsChange( mergeCustomModelsIntoStore( agents, - BUILTIN_AGENT_KEYS, + builtinAgentKeys, customs, modelVariantsMap, ), ); }, - [agents, onAgentsChange, modelVariantsMap], + [agents, onAgentsChange, modelVariantsMap, builtinAgentKeys], ); const syncCustomCategories = useCallback( (customs: CustomModelItem[]) => { + if (!onCategoriesChange) return; onCategoriesChange( mergeCustomModelsIntoStore( categories, @@ -709,7 +723,7 @@ export function OmoFormFields({ } const updatedAgents = { ...agents }; - for (const agentDef of OMO_BUILTIN_AGENTS) { + for (const agentDef of builtinAgentDefs) { const recommendedValue = resolveRecommendedModel(agentDef.recommended); if (recommendedValue && !updatedAgents[agentDef.key]?.model) { updatedAgents[agentDef.key] = { @@ -720,30 +734,35 @@ export function OmoFormFields({ } onAgentsChange(updatedAgents); - const updatedCategories = { ...categories }; - for (const catDef of OMO_BUILTIN_CATEGORIES) { - const recommendedValue = resolveRecommendedModel(catDef.recommended); - if (recommendedValue && !updatedCategories[catDef.key]?.model) { - updatedCategories[catDef.key] = { - ...updatedCategories[catDef.key], - model: recommendedValue, - }; + if (!isSlim && onCategoriesChange) { + const updatedCategories = { ...categories }; + for (const catDef of OMO_BUILTIN_CATEGORIES) { + const recommendedValue = resolveRecommendedModel(catDef.recommended); + if (recommendedValue && !updatedCategories[catDef.key]?.model) { + updatedCategories[catDef.key] = { + ...updatedCategories[catDef.key], + model: recommendedValue, + }; + } } + onCategoriesChange(updatedCategories); } - onCategoriesChange(updatedCategories); }; const configuredAgentCount = Object.keys(agents).length; - const configuredCategoryCount = Object.keys(categories).length; - const mainAgents = OMO_BUILTIN_AGENTS.filter((a) => a.group === "main"); - const subAgents = OMO_BUILTIN_AGENTS.filter((a) => a.group === "sub"); + const configuredCategoryCount = isSlim ? 0 : Object.keys(categories).length; + const mainAgents = builtinAgentDefs.filter((a) => a.group === "main"); + const subAgents = builtinAgentDefs.filter((a) => a.group === "sub"); const readLocalFile = useReadOmoLocalFile(); + const readSlimLocalFile = useReadOmoSlimLocalFile(); const [localFilePath, setLocalFilePath] = useState(null); const handleImportFromLocal = useCallback(async () => { try { - const data = await readLocalFile.mutateAsync(); + const data = isSlim + ? await readSlimLocalFile.mutateAsync() + : await readLocalFile.mutateAsync(); const importedAgents = (data.agents as Record> | undefined) || {}; @@ -753,16 +772,20 @@ export function OmoFormFields({ | undefined) || {}; onAgentsChange(importedAgents); - onCategoriesChange(importedCategories); + if (!isSlim && onCategoriesChange) { + onCategoriesChange(importedCategories); + } onOtherFieldsStrChange( data.otherFields ? JSON.stringify(data.otherFields, null, 2) : "", ); setAgentAdvancedDrafts({}); setCategoryAdvancedDrafts({}); - setCustomAgents(collectCustomModels(importedAgents, BUILTIN_AGENT_KEYS)); - setCustomCategories( - collectCustomModels(importedCategories, BUILTIN_CATEGORY_KEYS), - ); + setCustomAgents(collectCustomModels(importedAgents, builtinAgentKeys)); + if (!isSlim) { + setCustomCategories( + collectCustomModels(importedCategories, BUILTIN_CATEGORY_KEYS), + ); + } setLocalFilePath(data.filePath); toast.success( t("omo.importLocalReplaceSuccess", { @@ -792,7 +815,7 @@ export function OmoFormFields({ ) => { const isAgent = scope === "agent"; const store = isAgent ? agents : categories; - const setter = isAgent ? onAgentsChange : onCategoriesChange; + const setter = isAgent ? onAgentsChange : onCategoriesChange!; const drafts = isAgent ? agentAdvancedDrafts : categoryAdvancedDrafts; const expanded = isAgent ? expandedAgents : expandedCategories; @@ -866,7 +889,7 @@ export function OmoFormFields({ ) => { const isAgent = scope === "agent"; const store = isAgent ? agents : categories; - const setter = isAgent ? onAgentsChange : onCategoriesChange; + const setter = isAgent ? onAgentsChange : onCategoriesChange!; const drafts = isAgent ? agentAdvancedDrafts : categoryAdvancedDrafts; const expanded = isAgent ? expandedAgents : expandedCategories; const customs = isAgent ? customAgents : customCategories; @@ -1153,30 +1176,31 @@ export function OmoFormFields({ ), })} - {renderModelSection({ - title: t("omo.categories", { defaultValue: "Categories" }), - isOpen: categoriesOpen, - onToggle: () => setCategoriesOpen(!categoriesOpen), - badge: `${OMO_BUILTIN_CATEGORIES.length + customCategories.length}`, - action: renderCustomAddButton(() => addCustomModel("category")), - children: ( - <> - {OMO_BUILTIN_CATEGORIES.map(renderCategoryRow)} - {customCategories.length > 0 && ( - <> - {renderCustomDivider( - t("omo.customCategories", { - defaultValue: "Custom Categories", - }), - )} - {customCategories.map((c, i) => - renderCustomModelRow("category", c, i), - )} - - )} - - ), - })} + {!isSlim && + renderModelSection({ + title: t("omo.categories", { defaultValue: "Categories" }), + isOpen: categoriesOpen, + onToggle: () => setCategoriesOpen(!categoriesOpen), + badge: `${OMO_BUILTIN_CATEGORIES.length + customCategories.length}`, + action: renderCustomAddButton(() => addCustomModel("category")), + children: ( + <> + {OMO_BUILTIN_CATEGORIES.map(renderCategoryRow)} + {customCategories.length > 0 && ( + <> + {renderCustomDivider( + t("omo.customCategories", { + defaultValue: "Custom Categories", + }), + )} + {customCategories.map((c, i) => + renderCustomModelRow("category", c, i), + )} + + )} + + ), + })} {renderModelSection({ title: t("omo.otherFieldsJson", { diff --git a/src/components/providers/forms/OmoGlobalConfigFields.tsx b/src/components/providers/forms/OmoGlobalConfigFields.tsx index 5601ebbc9..3e8e1ab9d 100644 --- a/src/components/providers/forms/OmoGlobalConfigFields.tsx +++ b/src/components/providers/forms/OmoGlobalConfigFields.tsx @@ -40,11 +40,18 @@ import { OMO_BACKGROUND_TASK_PLACEHOLDER, OMO_BROWSER_AUTOMATION_PLACEHOLDER, OMO_CLAUDE_CODE_PLACEHOLDER, + OMO_SLIM_DISABLEABLE_AGENTS, + OMO_SLIM_DISABLEABLE_MCPS, + OMO_SLIM_DISABLEABLE_HOOKS, + OMO_SLIM_DEFAULT_SCHEMA_URL, } from "@/types/omo"; import { useOmoGlobalConfig, useSaveOmoGlobalConfig, useReadOmoLocalFile, + useOmoSlimGlobalConfig, + useSaveOmoSlimGlobalConfig, + useReadOmoSlimLocalFile, } from "@/lib/query/omo"; interface PresetOption { @@ -61,6 +68,7 @@ export interface OmoGlobalConfigFieldsRef { interface OmoGlobalConfigFieldsProps { onStateChange?: (config: OmoGlobalConfig) => void; hideSaveButtons?: boolean; + isSlim?: boolean; } type OmoAdvancedFieldKey = @@ -114,6 +122,11 @@ const OMO_ADVANCED_JSON_FIELDS: ReadonlyArray<{ }, ]; +const OMO_SLIM_ADVANCED_KEYS: ReadonlySet = new Set([ + "lspStr", + "experimentalStr", +]); + function TagListEditor({ label, values, @@ -310,12 +323,25 @@ function JsonTextareaField({ export const OmoGlobalConfigFields = forwardRef< OmoGlobalConfigFieldsRef, OmoGlobalConfigFieldsProps ->(function OmoGlobalConfigFields({ onStateChange, hideSaveButtons }, ref) { +>(function OmoGlobalConfigFields( + { onStateChange, hideSaveButtons, isSlim = false }, + ref, +) { const { t } = useTranslation(); - const { data: config } = useOmoGlobalConfig(); - const saveMutation = useSaveOmoGlobalConfig(); + const { data: standardConfig } = useOmoGlobalConfig(!isSlim); + const { data: slimConfig } = useOmoSlimGlobalConfig(isSlim); + const config = isSlim ? slimConfig : standardConfig; + const standardSaveMutation = useSaveOmoGlobalConfig(); + const slimSaveMutation = useSaveOmoSlimGlobalConfig(); + const saveMutation = isSlim ? slimSaveMutation : standardSaveMutation; + const standardReadLocal = useReadOmoLocalFile(); + const slimReadLocal = useReadOmoSlimLocalFile(); - const [schemaUrl, setSchemaUrl] = useState(OMO_DEFAULT_SCHEMA_URL); + const defaultSchemaUrl = isSlim + ? OMO_SLIM_DEFAULT_SCHEMA_URL + : OMO_DEFAULT_SCHEMA_URL; + + const [schemaUrl, setSchemaUrl] = useState(defaultSchemaUrl); const [sisyphusAgentStr, setSisyphusAgentStr] = useState(""); const [disabledAgents, setDisabledAgents] = useState([]); const [disabledMcps, setDisabledMcps] = useState([]); @@ -330,7 +356,7 @@ export const OmoGlobalConfigFields = forwardRef< const [loaded, setLoaded] = useState(false); const applyGlobalState = useCallback((global: OmoGlobalConfig) => { - setSchemaUrl(global.schemaUrl || OMO_DEFAULT_SCHEMA_URL); + setSchemaUrl(global.schemaUrl || defaultSchemaUrl); setSisyphusAgentStr( global.sisyphusAgent ? JSON.stringify(global.sisyphusAgent, null, 2) : "", ); @@ -545,7 +571,7 @@ export const OmoGlobalConfigFields = forwardRef< placeholder: t("omo.disabledAgentsPlaceholder", { defaultValue: "Disabled Agents", }), - presets: OMO_DISABLEABLE_AGENTS, + presets: isSlim ? OMO_SLIM_DISABLEABLE_AGENTS : OMO_DISABLEABLE_AGENTS, }, { key: "mcps", @@ -555,7 +581,7 @@ export const OmoGlobalConfigFields = forwardRef< placeholder: t("omo.disabledMcpsPlaceholder", { defaultValue: "Disabled MCPs", }), - presets: OMO_DISABLEABLE_MCPS, + presets: isSlim ? OMO_SLIM_DISABLEABLE_MCPS : OMO_DISABLEABLE_MCPS, }, { key: "hooks", @@ -565,21 +591,25 @@ export const OmoGlobalConfigFields = forwardRef< placeholder: t("omo.disabledHooksPlaceholder", { defaultValue: "Disabled Hooks", }), - presets: OMO_DISABLEABLE_HOOKS, + presets: isSlim ? OMO_SLIM_DISABLEABLE_HOOKS : OMO_DISABLEABLE_HOOKS, }, - { - key: "skills", - label: t("omo.disabledSkills", { defaultValue: "Skills" }), - values: disabledSkills, - onChange: setDisabledSkills, - placeholder: t("omo.disabledSkillsPlaceholder", { - defaultValue: "Disabled Skills", - }), - presets: OMO_DISABLEABLE_SKILLS, - }, - ] as const; + ...(!isSlim + ? [ + { + key: "skills" as const, + label: t("omo.disabledSkills", { defaultValue: "Skills" }), + values: disabledSkills, + onChange: setDisabledSkills, + placeholder: t("omo.disabledSkillsPlaceholder", { + defaultValue: "Disabled Skills", + }), + presets: OMO_DISABLEABLE_SKILLS, + }, + ] + : []), + ]; - const readLocalFile = useReadOmoLocalFile(); + const readLocalFile = isSlim ? slimReadLocal : standardReadLocal; const handleImportGlobalFromLocal = useCallback(async () => { try { @@ -668,25 +698,27 @@ export const OmoGlobalConfigFields = forwardRef< setSchemaUrl(e.target.value)} - placeholder={OMO_DEFAULT_SCHEMA_URL} + placeholder={defaultSchemaUrl} className="text-sm h-8" />
-
- -