fix: #145 仪表盘版本缓存 + #144 macOS手动安装检测 + #146 更新提示持久化 + #148 AI助手Web模式CORS

- dashboard.js: 版本/状态信息持久化缓存,实例切换时自动清空 (#145)
- service.rs: macOS is_cli_installed 改为真实路径探测,无plist时返回默认Gateway条目 (#144)
- utils.rs: 新增 common_non_windows_cli_candidates,resolve_openclaw_cli_path 优先检测 standalone/手动安装路径 (#144)
- config.rs: macOS get_local_version 增加 resolve_cli + canonicalize,detect_installed_source 增加 CLI 分类 + standalone 检测 (#144)
- dev-api.js: macOS CLI/版本/来源检测补齐 Intel Homebrew + standalone + findOpenclawBin (#144)
- main.js: 更新 banner dismiss 从 sessionStorage 改 localStorage (#146)
- assistant.js: Web 模式 AI 测试走后端代理 api.testModel 绕过 CORS (#148)
This commit is contained in:
晴天
2026-03-26 02:02:19 +08:00
parent 038e9c01bc
commit 7de40624f7
7 changed files with 159 additions and 16 deletions

View File

@@ -1158,10 +1158,19 @@ pub fn write_mcp_config(config: Value) -> Result<(), String> {
/// macOS: 优先从 npm 包的 package.json 读取含完整后缀fallback 到 CLI
/// Windows/Linux: 优先读文件系统fallback 到 CLI
async fn get_local_version() -> Option<String> {
// macOS: 通过 symlink 找到包目录,读 package.json 的 version
#[cfg(target_os = "macos")]
{
// 兼容 ARM (/opt/homebrew) 和 Intel (/usr/local) 两种 Homebrew 安装路径
if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() {
let resolved = std::fs::canonicalize(&cli_path)
.ok()
.unwrap_or_else(|| PathBuf::from(&cli_path));
if let Some(ver) = read_version_from_installation(&resolved)
.or_else(|| read_version_from_installation(std::path::Path::new(&cli_path)))
{
return Some(ver);
}
}
for brew_prefix in &["/opt/homebrew/bin", "/usr/local/bin"] {
let openclaw_path = format!("{}/openclaw", brew_prefix);
if let Ok(target) = fs::read_link(&openclaw_path) {
@@ -1182,10 +1191,9 @@ async fn get_local_version() -> Option<String> {
}
}
}
// Windows: 先查 standalone 安装,再查 npm 全局目录
#[cfg(target_os = "windows")]
{
// 检查所有 standalone 安装目录
for sa_dir in all_standalone_dirs() {
let version_file = sa_dir.join("VERSION");
if let Ok(content) = fs::read_to_string(&version_file) {
@@ -1212,7 +1220,7 @@ async fn get_local_version() -> Option<String> {
}
}
}
// npm 全局目录 — 根据 CLI 来源决定检查顺序,避免读到非活跃包的版本
if let Ok(appdata) = std::env::var("APPDATA") {
let cli_is_zh = crate::utils::resolve_openclaw_cli_path()
.map(|p| crate::utils::classify_cli_source(&p) == "npm-zh")
@@ -1239,7 +1247,8 @@ async fn get_local_version() -> Option<String> {
}
}
}
// 所有平台通用 fallback: CLI 输出(异步)
// 所有平台通用 fallback: CLI 输出
// Windows: 先确认 openclaw 不是第三方程序(如 CherryStudio
#[cfg(target_os = "windows")]
{
@@ -1259,6 +1268,7 @@ async fn get_local_version() -> Option<String> {
}
}
}
use crate::utils::openclaw_command_async;
let output = openclaw_command_async()
.arg("--version")
@@ -1296,6 +1306,18 @@ fn detect_installed_source() -> String {
// macOS: 检查 openclaw bin 的 symlink 指向
#[cfg(target_os = "macos")]
{
if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() {
let resolved = std::fs::canonicalize(&cli_path)
.ok()
.unwrap_or_else(|| PathBuf::from(&cli_path));
let source = crate::utils::classify_cli_source(&resolved.to_string_lossy());
if source == "npm-zh" || source == "standalone" {
return "chinese".into();
}
if source == "npm-official" || source == "npm-global" {
return "official".into();
}
}
// 兼容 ARM (/opt/homebrew) 和 Intel (/usr/local) 两种 Homebrew 路径
for brew_prefix in &["/opt/homebrew/bin/openclaw", "/usr/local/bin/openclaw"] {
if let Ok(target) = std::fs::read_link(brew_prefix) {
@@ -1305,6 +1327,11 @@ fn detect_installed_source() -> String {
return "official".into();
}
}
for sa_dir in all_standalone_dirs() {
if sa_dir.join("openclaw").exists() || sa_dir.join("VERSION").exists() {
return "chinese".into();
}
}
"official".into()
}
// Windows: 优先通过 CLI 路径判断fallback 到文件系统检测

View File

@@ -322,13 +322,26 @@ pub fn guardian_status() -> Result<GuardianStatus, String> {
#[cfg(target_os = "macos")]
mod platform {
use std::fs;
use std::path::PathBuf;
use std::process::Command;
const OPENCLAW_PREFIXES: &[&str] = &["ai.openclaw."];
/// macOS 上 CLI 是否安装(检查 plist 是否存在即可)
fn common_cli_candidates() -> Vec<PathBuf> {
let mut candidates = Vec::new();
if let Some(home) = dirs::home_dir() {
candidates.push(home.join(".openclaw-bin").join("openclaw"));
}
candidates.push(PathBuf::from("/opt/openclaw/openclaw"));
candidates.push(PathBuf::from("/opt/homebrew/bin/openclaw"));
candidates.push(PathBuf::from("/usr/local/bin/openclaw"));
candidates
}
/// macOS 上 CLI 是否安装(兼容手动安装 / standalone / Homebrew
pub fn is_cli_installed() -> bool {
true // macOS 通过 plist 扫描,不依赖 CLI 检测
crate::utils::resolve_openclaw_cli_path().is_some()
|| common_cli_candidates().into_iter().any(|p| p.exists())
}
pub fn current_uid() -> Result<u32, String> {
@@ -361,6 +374,9 @@ mod platform {
}
}
labels.sort();
if labels.is_empty() {
labels.push("ai.openclaw.gateway".to_string());
}
labels
}

View File

@@ -35,6 +35,20 @@ fn find_openclaw_cmd() -> Option<std::path::PathBuf> {
None
}
#[cfg(not(target_os = "windows"))]
fn common_non_windows_cli_candidates() -> Vec<std::path::PathBuf> {
let mut candidates = Vec::new();
if let Some(home) = dirs::home_dir() {
candidates.push(home.join(".openclaw-bin").join("openclaw"));
candidates.push(home.join(".local").join("bin").join("openclaw"));
}
candidates.push(std::path::PathBuf::from("/opt/openclaw/openclaw"));
candidates.push(std::path::PathBuf::from("/opt/homebrew/bin/openclaw"));
candidates.push(std::path::PathBuf::from("/usr/local/bin/openclaw"));
candidates.push(std::path::PathBuf::from("/usr/bin/openclaw"));
candidates
}
/// 解析当前实际使用的 openclaw CLI 完整路径(跨平台)
pub fn resolve_openclaw_cli_path() -> Option<String> {
// 优先使用用户绑定的路径
@@ -54,6 +68,11 @@ pub fn resolve_openclaw_cli_path() -> Option<String> {
}
#[cfg(not(target_os = "windows"))]
{
for candidate in common_non_windows_cli_candidates() {
if candidate.exists() {
return Some(candidate.to_string_lossy().to_string());
}
}
let path = crate::commands::enhanced_path();
let sep = ':';
for dir in path.split(sep) {