diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index 2420d1c..2c9e4f7 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -486,17 +486,28 @@ fn npm_command_elevated() -> Command { /// 解决 Windows 上 EEXIST(文件已存在)和文件被占用的问题 fn pre_install_cleanup() { /// 带超时执行命令(spawn + try_wait),防止任何子进程无限阻塞 - fn run_with_timeout(mut child: std::process::Child, timeout_secs: u64) -> Option { + fn run_with_timeout( + mut child: std::process::Child, + timeout_secs: u64, + ) -> Option { let deadline = std::time::Instant::now() + std::time::Duration::from_secs(timeout_secs); loop { match child.try_wait() { Ok(Some(status)) => { - let stdout = child.stdout.take().map(|mut s| { - let mut buf = Vec::new(); - let _ = std::io::Read::read_to_end(&mut s, &mut buf); - buf - }).unwrap_or_default(); - return Some(std::process::Output { status, stdout, stderr: Vec::new() }); + let stdout = child + .stdout + .take() + .map(|mut s| { + let mut buf = Vec::new(); + let _ = std::io::Read::read_to_end(&mut s, &mut buf); + buf + }) + .unwrap_or_default(); + return Some(std::process::Output { + status, + stdout, + stderr: Vec::new(), + }); } Ok(None) => { if std::time::Instant::now() >= deadline { @@ -546,7 +557,10 @@ fn pre_install_cleanup() { // 同时杀死 standalone 目录下的 node.exe 进程(每个目录 10s 超时) for sa_dir in all_standalone_dirs() { if sa_dir.exists() { - let dir_lower = sa_dir.to_string_lossy().to_lowercase().replace('\\', "\\\\"); + let dir_lower = sa_dir + .to_string_lossy() + .to_lowercase() + .replace('\\', "\\\\"); let ps_script = format!( "Get-Process -Name node -ErrorAction SilentlyContinue | Where-Object {{ $_.Path -and $_.Path.ToLower().Contains('{}') }} | Select-Object -ExpandProperty Id", dir_lower @@ -561,7 +575,9 @@ fn pre_install_cleanup() { let text = String::from_utf8_lossy(&output.stdout); for line in text.lines() { if let Ok(_pid) = line.trim().parse::() { - let _ = Command::new("taskkill").args(["/F", "/PID", line.trim()]).output(); + let _ = Command::new("taskkill") + .args(["/F", "/PID", line.trim()]) + .output(); } } } @@ -2971,9 +2987,7 @@ async fn try_standalone_install( // 兼容两种 latest.json 格式: // 新格式(CI 生成): { "editions": { "zh": { "version": "...", "base_url": "..." } } } // 旧格式(兼容): { "version": "...", "base_url": "..." } - let edition_obj = manifest - .get("editions") - .and_then(|e| e.get("zh")); + let edition_obj = manifest.get("editions").and_then(|e| e.get("zh")); let (remote_version, manifest_base_url, archive_prefix) = if let Some(ed) = edition_obj { let ver = ed .get("version") @@ -3059,7 +3073,10 @@ async fn try_standalone_install( let dl_mb = downloaded as f64 / 1_048_576.0; let total_mb = total_bytes as f64 / 1_048_576.0; let real_pct = (downloaded as f64 / total_bytes as f64 * 100.0) as u32; - let _ = app.emit("upgrade-log", format!("下载中 {real_pct}% ({dl_mb:.0}/{total_mb:.0}MB)")); + let _ = app.emit( + "upgrade-log", + format!("下载中 {real_pct}% ({dl_mb:.0}/{total_mb:.0}MB)"), + ); } last_progress = pct; let _ = app.emit("upgrade-progress", pct.min(70)); @@ -3581,7 +3598,9 @@ async fn upgrade_openclaw_inner( let _ = app.emit("upgrade-progress", 100); super::refresh_enhanced_path(); crate::commands::service::invalidate_cli_detection_cache(); - let msg = format!("✅ standalone (GitHub) 安装完成,当前版本: {installed_ver}"); + let msg = format!( + "✅ standalone (GitHub) 安装完成,当前版本: {installed_ver}" + ); let _ = app.emit("upgrade-log", &msg); return Ok(msg); } @@ -3593,7 +3612,9 @@ async fn upgrade_openclaw_inner( ); let _ = app.emit("upgrade-progress", 5); } else { - return Err(format!("standalone 安装失败: CDN={cdn_reason}, GitHub={gh_reason}")); + return Err(format!( + "standalone 安装失败: CDN={cdn_reason}, GitHub={gh_reason}" + )); } } } @@ -3872,7 +3893,10 @@ async fn upgrade_openclaw_inner( // 使用 PowerShell Get-Process(兼容 Windows 11,wmic 已废弃) #[cfg(target_os = "windows")] { - let dir_lower = sa_dir.to_string_lossy().to_lowercase().replace('\\', "\\\\"); + let dir_lower = sa_dir + .to_string_lossy() + .to_lowercase() + .replace('\\', "\\\\"); let ps_script = format!( "Get-Process -Name node -ErrorAction SilentlyContinue | Where-Object {{ $_.Path -and $_.Path.ToLower().Contains('{}') }} | Select-Object -ExpandProperty Id", dir_lower @@ -3884,8 +3908,11 @@ async fn upgrade_openclaw_inner( let text = String::from_utf8_lossy(&output.stdout); for line in text.lines() { if let Ok(pid) = line.trim().parse::() { - let _ = app.emit("upgrade-log", format!("终止占用进程 PID {pid}...")); - let _ = Command::new("taskkill").args(["/F", "/PID", &pid.to_string()]).output(); + let _ = + app.emit("upgrade-log", format!("终止占用进程 PID {pid}...")); + let _ = Command::new("taskkill") + .args(["/F", "/PID", &pid.to_string()]) + .output(); } } } @@ -3902,7 +3929,10 @@ async fn upgrade_openclaw_inner( if let Err(e) = std::fs::remove_dir_all(&sa_dir) { let _ = app.emit( "upgrade-log", - format!("⚠️ 清理 standalone 残留失败: {e}(可手动删除 {})", sa_dir.display()), + format!( + "⚠️ 清理 standalone 残留失败: {e}(可手动删除 {})", + sa_dir.display() + ), ); } else { let _ = app.emit("upgrade-log", "standalone 残留已清理(重试成功)✓"); @@ -4033,7 +4063,10 @@ async fn uninstall_openclaw_inner( // 使用 PowerShell Get-Process(兼容 Windows 11,wmic 已废弃) #[cfg(target_os = "windows")] { - let dir_lower = sa_dir.to_string_lossy().to_lowercase().replace('\\', "\\\\"); + let dir_lower = sa_dir + .to_string_lossy() + .to_lowercase() + .replace('\\', "\\\\"); let ps_script = format!( "Get-Process -Name node -ErrorAction SilentlyContinue | Where-Object {{ $_.Path -and $_.Path.ToLower().Contains('{}') }} | Select-Object -ExpandProperty Id", dir_lower @@ -4046,7 +4079,9 @@ async fn uninstall_openclaw_inner( for line in text.lines() { if let Ok(pid) = line.trim().parse::() { let _ = app.emit("upgrade-log", format!("终止占用进程 PID {pid}...")); - let _ = Command::new("taskkill").args(["/F", "/PID", &pid.to_string()]).output(); + let _ = Command::new("taskkill") + .args(["/F", "/PID", &pid.to_string()]) + .output(); } } } @@ -4066,7 +4101,10 @@ async fn uninstall_openclaw_inner( if let Err(e) = std::fs::remove_dir_all(sa_dir) { let _ = app.emit( "upgrade-log", - format!("⚠️ 清理 standalone 失败: {e}(可手动删除 {})", sa_dir.display()), + format!( + "⚠️ 清理 standalone 失败: {e}(可手动删除 {})", + sa_dir.display() + ), ); } else { let _ = app.emit("upgrade-log", "standalone 安装已清理(重试成功)✓"); @@ -4903,14 +4941,13 @@ async fn reload_gateway_internal(app: Option<&tauri::AppHandle>) -> Result Ok(msg), - Err(_) => { - crate::commands::service::restart_service( - app.cloned().ok_or_else(|| "缺少 AppHandle,无法回退到 Gateway 进程重启".to_string())?, - "ai.openclaw.gateway".into(), - ) - .await - .map(|_| "Gateway 已重启".to_string()) - } + Err(_) => crate::commands::service::restart_service( + app.cloned() + .ok_or_else(|| "缺少 AppHandle,无法回退到 Gateway 进程重启".to_string())?, + "ai.openclaw.gateway".into(), + ) + .await + .map(|_| "Gateway 已重启".to_string()), } } } diff --git a/src-tauri/src/commands/diagnose.rs b/src-tauri/src/commands/diagnose.rs index 3452619..02a9e98 100644 --- a/src-tauri/src/commands/diagnose.rs +++ b/src-tauri/src/commands/diagnose.rs @@ -59,9 +59,19 @@ fn collect_env() -> DiagnoseEnv { if let Ok(val) = serde_json::from_str::(&content) { let auth = val.get("gateway").and_then(|g| g.get("auth")); if let Some(auth) = auth { - if auth.get("token").and_then(|t| t.as_str()).map(|s| !s.is_empty()).unwrap_or(false) { + if auth + .get("token") + .and_then(|t| t.as_str()) + .map(|s| !s.is_empty()) + .unwrap_or(false) + { "token".to_string() - } else if auth.get("password").and_then(|p| p.as_str()).map(|s| !s.is_empty()).unwrap_or(false) { + } else if auth + .get("password") + .and_then(|p| p.as_str()) + .map(|s| !s.is_empty()) + .unwrap_or(false) + { "password".to_string() } else { "none".to_string() @@ -88,7 +98,11 @@ fn collect_env() -> DiagnoseEnv { let err_log_path = openclaw_dir.join("logs").join("gateway.err.log"); let err_log_excerpt = if let Ok(bytes) = std::fs::read(&err_log_path) { let max = 2048; - let tail = if bytes.len() > max { &bytes[bytes.len() - max..] } else { &bytes[..] }; + let tail = if bytes.len() > max { + &bytes[bytes.len() - max..] + } else { + &bytes[..] + }; String::from_utf8_lossy(tail).to_string() } else { String::new() @@ -123,18 +137,16 @@ fn check_config() -> DiagnoseStep { return finish_step("config", false, "openclaw.json 不存在", t); } match std::fs::read_to_string(&config_path) { - Ok(content) => { - match serde_json::from_str::(&content) { - Ok(val) => { - if val.get("gateway").is_some() { - finish_step("config", true, "配置文件有效,含 gateway 配置", t) - } else { - finish_step("config", false, "配置文件缺少 gateway 段", t) - } + Ok(content) => match serde_json::from_str::(&content) { + Ok(val) => { + if val.get("gateway").is_some() { + finish_step("config", true, "配置文件有效,含 gateway 配置", t) + } else { + finish_step("config", false, "配置文件缺少 gateway 段", t) } - Err(e) => finish_step("config", false, &format!("JSON 解析失败: {e}"), t), } - } + Err(e) => finish_step("config", false, &format!("JSON 解析失败: {e}"), t), + }, Err(e) => finish_step("config", false, &format!("读取失败: {e}"), t), } } @@ -159,7 +171,12 @@ fn check_device_key() -> DiagnoseStep { Err(e) => finish_step("device_key", false, &format!("读取失败: {e}"), t), } } else { - finish_step("device_key", false, "设备密钥不存在(将在首次连接时自动生成)", t) + finish_step( + "device_key", + false, + "设备密钥不存在(将在首次连接时自动生成)", + t, + ) } } @@ -178,15 +195,32 @@ fn check_allowed_origins() -> DiagnoseStep { match origins { Some(arr) if !arr.is_empty() => { let list: Vec<&str> = arr.iter().filter_map(|v| v.as_str()).collect(); - let has_tauri = list.iter().any(|o| o.contains("tauri://") || o.contains("https://tauri.localhost")); + let has_tauri = list.iter().any(|o| { + o.contains("tauri://") || o.contains("https://tauri.localhost") + }); if has_tauri { - finish_step("allowed_origins", true, &format!("allowedOrigins 包含 Tauri origin: {:?}", list), t) + finish_step( + "allowed_origins", + true, + &format!("allowedOrigins 包含 Tauri origin: {:?}", list), + t, + ) } else { - finish_step("allowed_origins", false, &format!("allowedOrigins 缺少 Tauri origin: {:?}", list), t) + finish_step( + "allowed_origins", + false, + &format!("allowedOrigins 缺少 Tauri origin: {:?}", list), + t, + ) } } Some(_) => finish_step("allowed_origins", false, "allowedOrigins 为空数组", t), - None => finish_step("allowed_origins", false, "未配置 allowedOrigins(autoPair 会自动修复)", t), + None => finish_step( + "allowed_origins", + false, + "未配置 allowedOrigins(autoPair 会自动修复)", + t, + ), } } else { finish_step("allowed_origins", false, "配置文件解析失败", t) @@ -208,21 +242,43 @@ async fn check_http_health(port: u16) -> DiagnoseStep { Ok(resp) => { let status = resp.status(); if status.is_success() { - finish_step("http_health", true, &format!("HTTP /health 返回 {status}"), t) + finish_step( + "http_health", + true, + &format!("HTTP /health 返回 {status}"), + t, + ) } else { - finish_step("http_health", false, &format!("HTTP /health 返回 {status}"), t) + finish_step( + "http_health", + false, + &format!("HTTP /health 返回 {status}"), + t, + ) } } - Err(e) => finish_step("http_health", false, &format!("HTTP /health 请求失败: {e}"), t), + Err(e) => finish_step( + "http_health", + false, + &format!("HTTP /health 请求失败: {e}"), + t, + ), }, - Err(e) => finish_step("http_health", false, &format!("HTTP client 创建失败: {e}"), t), + Err(e) => finish_step( + "http_health", + false, + &format!("HTTP client 创建失败: {e}"), + t, + ), } } /// 检查 Gateway 错误日志 fn check_error_log() -> DiagnoseStep { let t = step_timer(); - let log_path = crate::commands::openclaw_dir().join("logs").join("gateway.err.log"); + let log_path = crate::commands::openclaw_dir() + .join("logs") + .join("gateway.err.log"); if !log_path.exists() { return finish_step("err_log", true, "无错误日志(正常)", t); } @@ -234,13 +290,29 @@ fn check_error_log() -> DiagnoseStep { } else { // 读最后 1KB 看有没有关键错误 let content = std::fs::read(&log_path).unwrap_or_default(); - let tail = if content.len() > 1024 { &content[content.len() - 1024..] } else { &content[..] }; - let text = String::from_utf8_lossy(tail).to_lowercase(); - let has_fatal = text.contains("fatal") || text.contains("eaddrinuse") || text.contains("config invalid"); - if has_fatal { - finish_step("err_log", false, &format!("错误日志含关键错误 ({size} bytes)"), t) + let tail = if content.len() > 1024 { + &content[content.len() - 1024..] } else { - finish_step("err_log", true, &format!("错误日志存在但无致命错误 ({size} bytes)"), t) + &content[..] + }; + let text = String::from_utf8_lossy(tail).to_lowercase(); + let has_fatal = text.contains("fatal") + || text.contains("eaddrinuse") + || text.contains("config invalid"); + if has_fatal { + finish_step( + "err_log", + false, + &format!("错误日志含关键错误 ({size} bytes)"), + t, + ) + } else { + finish_step( + "err_log", + true, + &format!("错误日志存在但无致命错误 ({size} bytes)"), + t, + ) } } } @@ -274,7 +346,11 @@ pub async fn diagnose_gateway_connection() -> DiagnoseResult { steps.push(check_error_log()); let overall_ok = steps.iter().all(|s| s.ok); - let failed: Vec<&str> = steps.iter().filter(|s| !s.ok).map(|s| s.name.as_str()).collect(); + let failed: Vec<&str> = steps + .iter() + .filter(|s| !s.ok) + .map(|s| s.name.as_str()) + .collect(); let summary = if overall_ok { "所有检查项通过".to_string() } else { diff --git a/src-tauri/src/commands/messaging.rs b/src-tauri/src/commands/messaging.rs index 2d25607..eca4c55 100644 --- a/src-tauri/src/commands/messaging.rs +++ b/src-tauri/src/commands/messaging.rs @@ -2122,13 +2122,19 @@ pub async fn list_all_plugins() -> Result { if let Ok(rd) = std::fs::read_dir(&ext_dir) { for entry in rd.flatten() { let name = entry.file_name().to_string_lossy().to_string(); - if name.starts_with('.') { continue; } + if name.starts_with('.') { + continue; + } let p = entry.path(); - if !p.is_dir() { continue; } + if !p.is_dir() { + continue; + } let has_marker = p.join("package.json").is_file() || p.join("plugin.ts").is_file() || p.join("index.js").is_file(); - if !has_marker { continue; } + if !has_marker { + continue; + } let plugin_id = name.clone(); seen.insert(plugin_id.clone()); @@ -2150,7 +2156,10 @@ pub async fn list_all_plugins() -> Result { let description = std::fs::read_to_string(p.join("package.json")) .ok() .and_then(|s| serde_json::from_str::(&s).ok()) - .and_then(|v| v.get("description").and_then(|v| v.as_str().map(String::from))); + .and_then(|v| { + v.get("description") + .and_then(|v| v.as_str().map(String::from)) + }); plugins.push(json!({ "id": plugin_id, @@ -2168,9 +2177,14 @@ pub async fn list_all_plugins() -> Result { // Also include entries from config that might not be in extensions dir (built-in) for (pid, entry_val) in &entries { - if seen.contains(pid.as_str()) { continue; } + if seen.contains(pid.as_str()) { + continue; + } seen.insert(pid.clone()); - let enabled = entry_val.get("enabled").and_then(|v| v.as_bool()).unwrap_or(false); + let enabled = entry_val + .get("enabled") + .and_then(|v| v.as_bool()) + .unwrap_or(false); let allowed = allow_arr.iter().any(|v| v.as_str() == Some(pid.as_str())); let builtin = is_plugin_builtin(pid); plugins.push(json!({ @@ -3275,11 +3289,9 @@ pub async fn install_qqbot_plugin( ); let app2 = app.clone(); tauri::async_runtime::spawn(async move { - let _ = crate::commands::service::restart_service( - app2, - "ai.openclaw.gateway".into(), - ) - .await; + let _ = + crate::commands::service::restart_service(app2, "ai.openclaw.gateway".into()) + .await; }); Ok("安装成功".into()) } diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 9179edb..a52159a 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -36,10 +36,7 @@ fn default_openclaw_dir() -> PathBuf { fn panel_path_key(path: &std::path::Path) -> String { #[cfg(target_os = "windows")] { - return path - .to_string_lossy() - .replace('/', "\\") - .to_lowercase(); + return path.to_string_lossy().replace('/', "\\").to_lowercase(); } #[cfg(not(target_os = "windows"))] { @@ -66,21 +63,24 @@ fn panel_config_candidate_paths() -> Vec { if !trimmed.is_empty() { push_unique_panel_config_path( &mut paths, - PathBuf::from(trimmed).join(".openclaw").join("clawpanel.json"), + PathBuf::from(trimmed) + .join(".openclaw") + .join("clawpanel.json"), ); } } - if let (Ok(home_drive), Ok(home_path)) = ( - std::env::var("HOMEDRIVE"), - std::env::var("HOMEPATH"), - ) { + if let (Ok(home_drive), Ok(home_path)) = + (std::env::var("HOMEDRIVE"), std::env::var("HOMEPATH")) + { let combined = format!("{}{}", home_drive.trim(), home_path.trim()); let trimmed = combined.trim(); if !trimmed.is_empty() { push_unique_panel_config_path( &mut paths, - PathBuf::from(trimmed).join(".openclaw").join("clawpanel.json"), + PathBuf::from(trimmed) + .join(".openclaw") + .join("clawpanel.json"), ); } } diff --git a/src-tauri/src/commands/service.rs b/src-tauri/src/commands/service.rs index 3cdf63e..d623d4e 100644 --- a/src-tauri/src/commands/service.rs +++ b/src-tauri/src/commands/service.rs @@ -281,8 +281,8 @@ fn looks_like_gateway_config_mismatch(reason: &str) -> bool { /// 当 `openclaw doctor --fix` 无法修复时作为二级回退 fn try_direct_config_strip() -> Result { let config_path = crate::commands::openclaw_dir().join("openclaw.json"); - let raw = std::fs::read_to_string(&config_path) - .map_err(|e| format!("读取配置文件失败: {e}"))?; + let raw = + std::fs::read_to_string(&config_path).map_err(|e| format!("读取配置文件失败: {e}"))?; let mut doc: serde_json::Value = serde_json::from_str(&raw).map_err(|e| format!("解析配置文件失败: {e}"))?; @@ -346,10 +346,9 @@ fn try_direct_config_strip() -> Result { } if changed { - let formatted = serde_json::to_string_pretty(&doc) - .map_err(|e| format!("序列化配置失败: {e}"))?; - std::fs::write(&config_path, formatted) - .map_err(|e| format!("写入配置文件失败: {e}"))?; + let formatted = + serde_json::to_string_pretty(&doc).map_err(|e| format!("序列化配置失败: {e}"))?; + std::fs::write(&config_path, formatted).map_err(|e| format!("写入配置文件失败: {e}"))?; guardian_log("直接修复: 已写回 openclaw.json"); } @@ -458,7 +457,10 @@ async fn try_auto_fix_gateway_config( "auto_fix_failure", "已尝试自动执行 openclaw doctor --fix,但修复超时 (30s)", ); - Err("检测到 Gateway 配置异常,已尝试自动执行 openclaw doctor --fix,但修复超时 (30s)".into()) + Err( + "检测到 Gateway 配置异常,已尝试自动执行 openclaw doctor --fix,但修复超时 (30s)" + .into(), + ) } } } @@ -897,11 +899,17 @@ mod platform { Err(_) => return (false, None), }; // 两次尝试:第一次 1 秒,失败后短暂等待再用 2 秒重试,避免瞬态超时误判 - let connected = std::net::TcpStream::connect_timeout(&socket_addr, std::time::Duration::from_secs(1)).is_ok() - || { - std::thread::sleep(std::time::Duration::from_millis(300)); - std::net::TcpStream::connect_timeout(&socket_addr, std::time::Duration::from_secs(2)).is_ok() - }; + let connected = + std::net::TcpStream::connect_timeout(&socket_addr, std::time::Duration::from_secs(1)) + .is_ok() + || { + std::thread::sleep(std::time::Duration::from_millis(300)); + std::net::TcpStream::connect_timeout( + &socket_addr, + std::time::Duration::from_secs(2), + ) + .is_ok() + }; if connected { let pid = get_pid_by_lsof(port); (true, pid) @@ -1158,13 +1166,11 @@ mod platform { use std::io::{Read, Write as IoWrite}; use std::net::TcpStream; let addr = format!("127.0.0.1:{port}"); - let mut stream = match TcpStream::connect_timeout( - &addr.parse().unwrap(), - Duration::from_secs(3), - ) { - Ok(s) => s, - Err(_) => return false, - }; + let mut stream = + match TcpStream::connect_timeout(&addr.parse().unwrap(), Duration::from_secs(3)) { + Ok(s) => s, + Err(_) => return false, + }; let _ = stream.set_read_timeout(Some(Duration::from_secs(3))); let _ = stream.set_write_timeout(Some(Duration::from_secs(2))); let req = format!("GET /health HTTP/1.0\r\nHost: 127.0.0.1:{port}\r\n\r\n"); @@ -1227,7 +1233,8 @@ mod platform { if let Some(cmdline) = read_process_command_line(pid) { let cmdline_lower = cmdline.to_lowercase(); - let is_gateway = cmdline_lower.contains("openclaw") && cmdline_lower.contains("gateway"); + let is_gateway = + cmdline_lower.contains("openclaw") && cmdline_lower.contains("gateway"); let our_pid = *LAST_KNOWN_GATEWAY_PID.lock().unwrap(); if is_gateway { @@ -1500,8 +1507,8 @@ mod platform { Err(_) => return (false, None), }; // 两次尝试:第一次 1 秒,失败后短暂等待再用 2 秒重试,避免瞬态超时误判 - let connected = std::net::TcpStream::connect_timeout(&socket_addr, Duration::from_secs(1)).is_ok() - || { + let connected = + std::net::TcpStream::connect_timeout(&socket_addr, Duration::from_secs(1)).is_ok() || { std::thread::sleep(Duration::from_millis(300)); std::net::TcpStream::connect_timeout(&socket_addr, Duration::from_secs(2)).is_ok() }; diff --git a/src-tauri/src/commands/skills.rs b/src-tauri/src/commands/skills.rs index 21c4ffb..58a80ef 100644 --- a/src-tauri/src/commands/skills.rs +++ b/src-tauri/src/commands/skills.rs @@ -16,7 +16,8 @@ pub async fn skills_list(agent_id: Option) -> Result { #[tauri::command] pub async fn skills_info(name: String, agent_id: Option) -> Result { let agent_ws = resolve_agent_skills_dir(agent_id.as_deref()); - scan_custom_skill_detail(&name, agent_ws.as_deref()).ok_or_else(|| format!("Skill「{name}」不存在")) + scan_custom_skill_detail(&name, agent_ws.as_deref()) + .ok_or_else(|| format!("Skill「{name}」不存在")) } /// 检查 Skills 依赖状态(纯本地扫描) @@ -150,8 +151,8 @@ pub async fn skills_uninstall(name: String, agent_id: Option) -> Result< return Err("无效的 Skill 名称".to_string()); } let agent_ws = resolve_agent_skills_dir(agent_id.as_deref()); - let skills_dir = - resolve_custom_skill_dir_with_agent(&name, agent_ws.as_deref()).ok_or_else(|| format!("Skill「{name}」不存在"))?; + let skills_dir = resolve_custom_skill_dir_with_agent(&name, agent_ws.as_deref()) + .ok_or_else(|| format!("Skill「{name}」不存在"))?; if !skills_dir.exists() { return Err(format!("Skill「{name}」不存在")); } @@ -166,8 +167,8 @@ pub async fn skills_validate(name: String) -> Result { return Err("无效的 Skill 名称".to_string()); } - let skill_dir = - resolve_custom_skill_dir_with_agent(&name, None).ok_or_else(|| format!("Skill「{name}」不存在"))?; + let skill_dir = resolve_custom_skill_dir_with_agent(&name, None) + .ok_or_else(|| format!("Skill「{name}」不存在"))?; if !skill_dir.exists() { return Err(format!("Skill「{name}」不存在")); } @@ -457,7 +458,9 @@ fn clean_cli_output(text: &str) -> String { /// 根据 agentId 解析该 Agent 的 workspace/skills 目录 /// 如果 agentId 为 None 或 "main",返回 None(使用默认的 ~/.openclaw/skills) fn resolve_agent_skills_dir(agent_id: Option<&str>) -> Option { - let id = agent_id.map(|s| s.trim()).filter(|s| !s.is_empty() && *s != "main")?; + let id = agent_id + .map(|s| s.trim()) + .filter(|s| !s.is_empty() && *s != "main")?; // 读取 openclaw.json 获取 agent workspace let config = super::config::load_openclaw_json().ok()?; let workspace = config @@ -485,7 +488,9 @@ fn resolve_agent_skills_dir(agent_id: Option<&str>) -> Option) -> Vec<(std::path::PathBuf, &'static str)> { +fn custom_skill_roots_for_agent( + agent_skills_dir: Option<&std::path::Path>, +) -> Vec<(std::path::PathBuf, &'static str)> { let mut roots = Vec::new(); // 如果指定了 agent 的 skills 目录,优先放在第一位 @@ -532,14 +537,20 @@ fn custom_skill_roots_for_agent(agent_skills_dir: Option<&std::path::Path>) -> V roots } -fn resolve_custom_skill_dir_with_agent(name: &str, agent_skills_dir: Option<&std::path::Path>) -> Option { +fn resolve_custom_skill_dir_with_agent( + name: &str, + agent_skills_dir: Option<&std::path::Path>, +) -> Option { custom_skill_roots_for_agent(agent_skills_dir) .into_iter() .map(|(root, _)| root.join(name)) .find(|path| path.exists()) } -fn scan_custom_skill_detail(name: &str, agent_skills_dir: Option<&std::path::Path>) -> Option { +fn scan_custom_skill_detail( + name: &str, + agent_skills_dir: Option<&std::path::Path>, +) -> Option { for (root, source_label) in custom_skill_roots_for_agent(agent_skills_dir) { let skill_path = root.join(name); if !skill_path.exists() { @@ -593,7 +604,9 @@ fn scan_custom_skill_detail(name: &str, agent_skills_dir: Option<&std::path::Pat None } -fn scan_local_skill_entries_for_agent(agent_skills_dir: Option<&std::path::Path>) -> Result, String> { +fn scan_local_skill_entries_for_agent( + agent_skills_dir: Option<&std::path::Path>, +) -> Result, String> { let mut skills = Vec::new(); for (skills_dir, source_label) in custom_skill_roots_for_agent(agent_skills_dir) { @@ -663,7 +676,10 @@ fn scan_local_skill_entries() -> Result, String> { } /// CLI 不可用或当前结果不可用时的兜底:扫描本地自定义 Skills 目录 -fn scan_local_skills(cli_diagnostic: Option, agent_skills_dir: Option<&std::path::Path>) -> Result { +fn scan_local_skills( + cli_diagnostic: Option, + agent_skills_dir: Option<&std::path::Path>, +) -> Result { let roots = custom_skill_roots_for_agent(agent_skills_dir); let scanned_roots: Vec = roots .iter() diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index 83494ac..fa7d860 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -62,11 +62,8 @@ fn handle_menu_event(app: &AppHandle, id: &str) { "gateway_start" => { let app2 = app.clone(); tauri::async_runtime::spawn(async move { - let _ = crate::commands::service::start_service( - app2, - "ai.openclaw.gateway".into(), - ) - .await; + let _ = crate::commands::service::start_service(app2, "ai.openclaw.gateway".into()) + .await; }); } "gateway_stop" => { @@ -77,11 +74,9 @@ fn handle_menu_event(app: &AppHandle, id: &str) { "gateway_restart" => { let app2 = app.clone(); tauri::async_runtime::spawn(async move { - let _ = crate::commands::service::restart_service( - app2, - "ai.openclaw.gateway".into(), - ) - .await; + let _ = + crate::commands::service::restart_service(app2, "ai.openclaw.gateway".into()) + .await; }); } "quit" => {