mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-02 22:30:36 +08:00
feat: 飞书官方插件迁移 + 配对审批 + Gateway防卡死 + 微信升级修复 + 更新检测修复
- 飞书渠道从 @openclaw/feishu 迁移到 @larksuite/openclaw-lark 官方插件 - 保存飞书配置时自动禁用旧 feishu 插件,防止新旧插件冲突 - 所有主要渠道(飞书/Telegram/Discord/Slack)启用配对审批UI - gateway_command 增加20s超时,超时后force-kill+fresh start - 全平台启动前端口占用检查,防止Guardian无限拉起 - Linux gateway_command 补齐 Duration 导入和 cleanup_zombie 实现 - Guardian自动守护在Tauri桌面端也启用,轮询间隔30s→15s - 微信渠道:升级操作不再弹出扫码二维码,按钮文案区分安装/升级 - 版本更新检测:CI不再将minAppVersion写死为当前版本 - 部署脚本增强OpenClaw检测,支持已安装的官方版 - 日间/夜间模式圆形扩散切换动画(View Transitions API) - API错误信息完整展示(429限流等),URL自动转可点击链接 - 第三方API接入引导优化:移除内置密钥,引导式流程 - 修复全平台 Clippy 警告(strip_prefix/dead_code/unnecessary_unwrap等) - Rust代码格式化修复(cargo fmt) - toast组件支持HTML内容渲染 - Rust后端test_model返回详细错误信息
This commit is contained in:
@@ -3,6 +3,12 @@ use std::path::PathBuf;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
|
||||
/// 缓存 gateway 端口,避免频繁读文件(5秒有效期)
|
||||
static GATEWAY_PORT_CACHE: std::sync::LazyLock<std::sync::Mutex<(u16, std::time::Instant)>> =
|
||||
std::sync::LazyLock::new(|| {
|
||||
std::sync::Mutex::new((18789, std::time::Instant::now() - Duration::from_secs(60)))
|
||||
});
|
||||
|
||||
pub mod agent;
|
||||
pub mod assistant;
|
||||
pub mod config;
|
||||
@@ -39,6 +45,41 @@ pub fn openclaw_dir() -> PathBuf {
|
||||
default_openclaw_dir()
|
||||
}
|
||||
|
||||
/// Gateway 监听端口:读取 `openclaw.json` 的 `gateway.port`,缺省 **18789**。
|
||||
/// 与面板「Gateway 配置」、服务状态检测(netstat / TCP / launchctl 兜底)共用同一来源,
|
||||
/// 并尊重 `clawpanel.json` 中的 `openclawDir` 自定义配置目录。
|
||||
pub fn gateway_listen_port() -> u16 {
|
||||
// 5秒内返回缓存值,避免服务状态检测时频繁读文件
|
||||
if let Ok(cache) = GATEWAY_PORT_CACHE.lock() {
|
||||
if cache.1.elapsed() < Duration::from_secs(5) {
|
||||
return cache.0;
|
||||
}
|
||||
}
|
||||
let port = read_gateway_port_from_config();
|
||||
if let Ok(mut cache) = GATEWAY_PORT_CACHE.lock() {
|
||||
*cache = (port, std::time::Instant::now());
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
fn read_gateway_port_from_config() -> u16 {
|
||||
let config_path = openclaw_dir().join("openclaw.json");
|
||||
if let Ok(content) = std::fs::read_to_string(&config_path) {
|
||||
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
if let Some(port) = val
|
||||
.get("gateway")
|
||||
.and_then(|g| g.get("port"))
|
||||
.and_then(|p| p.as_u64())
|
||||
{
|
||||
if port > 0 && port < 65536 {
|
||||
return port as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18789
|
||||
}
|
||||
|
||||
fn panel_config_path() -> PathBuf {
|
||||
// ClawPanel 自身配置始终在默认目录,不随 openclawDir 变化
|
||||
default_openclaw_dir().join("clawpanel.json")
|
||||
@@ -341,16 +382,28 @@ fn build_enhanced_path() -> String {
|
||||
let mut extra: Vec<String> = vec![];
|
||||
|
||||
// 1. NVM_SYMLINK(nvm-windows 活跃版本符号链接,如 D:\nodejs)—— 最高优先级
|
||||
// 增强:尝试解析符号链接目标
|
||||
if let Ok(nvm_symlink) = std::env::var("NVM_SYMLINK") {
|
||||
let symlink_path = std::path::Path::new(&nvm_symlink);
|
||||
if symlink_path.is_dir() {
|
||||
extra.push(nvm_symlink.clone());
|
||||
}
|
||||
// 如果是符号链接,尝试读取其实际指向的目标
|
||||
#[cfg(target_os = "windows")]
|
||||
if symlink_path.is_symlink() {
|
||||
if let Ok(target) = std::fs::read_link(symlink_path) {
|
||||
if target.is_dir() {
|
||||
extra.push(target.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. NVM_HOME(用户自定义 nvm 安装目录)
|
||||
if let Ok(nvm_home) = std::env::var("NVM_HOME") {
|
||||
let nvm_path = std::path::Path::new(&nvm_home);
|
||||
if nvm_path.is_dir() {
|
||||
// 扫描所有已安装的版本目录
|
||||
if let Ok(entries) = std::fs::read_dir(nvm_path) {
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
@@ -359,13 +412,34 @@ fn build_enhanced_path() -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 尝试从 settings.json 读取当前激活版本
|
||||
let settings_path = nvm_path.join("settings.json");
|
||||
if settings_path.exists() {
|
||||
if let Ok(content) = std::fs::read_to_string(&settings_path) {
|
||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
// settings.json 中有 "path" 字段指向当前版本
|
||||
if let Some(current_version) = json.get("path").and_then(|v| v.as_str())
|
||||
{
|
||||
let version_path = nvm_path.join(current_version);
|
||||
if version_path.is_dir() {
|
||||
// 将当前激活版本移到更高优先级
|
||||
let version_bin = version_path.to_string_lossy().to_string();
|
||||
if !extra.contains(&version_bin) {
|
||||
extra.insert(0, version_bin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. %APPDATA%\nvm(nvm-windows 默认安装目录)
|
||||
if !appdata.is_empty() {
|
||||
extra.push(format!(r"{}\nvm", appdata));
|
||||
let nvm_dir = std::path::Path::new(&appdata).join("nvm");
|
||||
if nvm_dir.is_dir() {
|
||||
// 扫描所有已安装的版本
|
||||
if let Ok(entries) = std::fs::read_dir(&nvm_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
@@ -374,10 +448,35 @@ fn build_enhanced_path() -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 尝试从 settings.json 读取当前激活版本
|
||||
let settings_path = nvm_dir.join("settings.json");
|
||||
if settings_path.exists() {
|
||||
if let Ok(content) = std::fs::read_to_string(&settings_path) {
|
||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
if let Some(current_version) = json.get("path").and_then(|v| v.as_str())
|
||||
{
|
||||
let version_path = nvm_dir.join(current_version);
|
||||
if version_path.is_dir() {
|
||||
let version_bin = version_path.to_string_lossy().to_string();
|
||||
if !extra.contains(&version_bin) {
|
||||
extra.insert(0, version_bin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. volta
|
||||
extra.push(format!(r"{}\.volta\bin", home.display()));
|
||||
// volta 的活跃版本
|
||||
let volta_bin = std::path::Path::new(&home).join(".volta/bin");
|
||||
if volta_bin.is_dir() && !extra.contains(&volta_bin.to_string_lossy().to_string()) {
|
||||
extra.insert(0, volta_bin.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
// 5. fnm
|
||||
if !localappdata.is_empty() {
|
||||
extra.push(format!(r"{}\fnm_multishells", localappdata));
|
||||
@@ -388,30 +487,53 @@ fn build_enhanced_path() -> String {
|
||||
.unwrap_or_else(|| std::path::Path::new(&appdata).join("fnm"));
|
||||
let fnm_versions = fnm_base.join("node-versions");
|
||||
if fnm_versions.is_dir() {
|
||||
// 尝试找到 fnm 的当前活跃版本
|
||||
let fnm_current = fnm_base.join("current");
|
||||
if fnm_current.is_dir() {
|
||||
let current_inst = fnm_current.join("installation");
|
||||
if current_inst.is_dir()
|
||||
&& current_inst.join("node.exe").exists()
|
||||
&& !extra.contains(¤t_inst.to_string_lossy().to_string())
|
||||
{
|
||||
extra.insert(0, current_inst.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
// 扫描所有版本
|
||||
if let Ok(entries) = std::fs::read_dir(&fnm_versions) {
|
||||
for entry in entries.flatten() {
|
||||
let inst = entry.path().join("installation");
|
||||
if inst.is_dir() && inst.join("node.exe").exists() {
|
||||
extra.push(inst.to_string_lossy().to_string());
|
||||
let inst_str = inst.to_string_lossy().to_string();
|
||||
if !extra.contains(&inst_str) {
|
||||
extra.push(inst_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. npm 全局(openclaw.cmd 通常在这里)
|
||||
if !appdata.is_empty() {
|
||||
extra.push(format!(r"{}\npm", appdata));
|
||||
}
|
||||
|
||||
// 7. 系统默认 Node.js 安装路径(优先级最低)
|
||||
extra.push(format!(r"{}\nodejs", pf));
|
||||
extra.push(format!(r"{}\nodejs", pf86));
|
||||
if !localappdata.is_empty() {
|
||||
extra.push(format!(r"{}\Programs\nodejs", localappdata));
|
||||
}
|
||||
|
||||
// 8. 扫描常见盘符下的 Node 安装(用户可能装在 D:\、F:\ 等)
|
||||
for drive in &["C", "D", "E", "F"] {
|
||||
extra.push(format!(r"{}:\nodejs", drive));
|
||||
extra.push(format!(r"{}:\Node", drive));
|
||||
extra.push(format!(r"{}:\Program Files\nodejs", drive));
|
||||
// 常见 AI/Dev 工具目录
|
||||
extra.push(format!(r"{}:\AI\Node", drive));
|
||||
extra.push(format!(r"{}:\AI\nodejs", drive));
|
||||
extra.push(format!(r"{}:\Dev\nodejs", drive));
|
||||
extra.push(format!(r"{}:\Tools\nodejs", drive));
|
||||
}
|
||||
|
||||
let mut parts: Vec<&str> = vec![];
|
||||
@@ -419,9 +541,10 @@ fn build_enhanced_path() -> String {
|
||||
if let Some(ref cp) = custom_path {
|
||||
parts.push(cp.as_str());
|
||||
}
|
||||
// 然后是默认扫描到的路径
|
||||
// 然后是默认扫描到的路径(去重)
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for p in &extra {
|
||||
if std::path::Path::new(p).exists() {
|
||||
if std::path::Path::new(p).exists() && seen.insert(p.clone()) {
|
||||
parts.push(p.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user