fix: 重构版本源检测逻辑 + standalone 目录集中化 + Linux 平台检测补全 (#161)

* fix: Windows 版本检测错误——优先从活跃 CLI 读取版本,修复源判断和包检查顺序

- get_local_version() Windows 块新增活跃 CLI 优先检测(与 macOS 一致),
  避免残留的 standalone 汉化版目录被优先扫描到
- detect_installed_source() 修复 standalone 被错误映射为 official(应为 chinese)
- read_version_from_installation() 根据 classify_cli_source 动态决定
  package.json 检查顺序,避免硬编码汉化版优先导致官方版用户看到错误版本

🤖 Generated with [Qoder][https://qoder.com]

* fix: Windows 版本检测忽略残留文件,仅当 CLI 二进制存在时才读取版本

standalone 目录和 npm 全局目录中可能存在卸载后的残留 node_modules/
package.json,导致面板误判 OpenClaw 已安装并显示错误版本号。
现在在读取版本前先检查 openclaw.cmd 是否实际存在。

🤖 Generated with [Qoder][https://qoder.com]

* fix: 重构版本源检测逻辑,修复跨源切换后显示旧源的问题

- 新增 detect_source_from_cmd_shim() 通过读取 Windows .cmd shim 内容判断
  npm 包归属,替代不可靠的文件系统残留目录扫描
- 重写 detect_installed_source() Windows 检测块,优先使用 shim 内容信号
- upgrade_openclaw_inner() 跨源切换时清理 standalone 安装目录
- get_local_version() npm 段改用 shim 内容判断活跃包
- build_enhanced_path() 三平台添加 standalone 安装目录,避免 dashboard 超时
- 所有平台 fallback 从 "official" 改为 "unknown",支持非面板安装场景
- 前端 dashboard/about 支持 official/chinese/unknown 三源显示
- 新增 unknownSource i18n key(中/英/繁/日/韩)

🤖 Generated with [Qoder][https://qoder.com]

* fix: 移除 config.rs 末尾多余的闭合括号,修复编译错误

🤖 Generated with [Qoder][https://qoder.com]

* fix: 移除 config.rs 末尾重复的 configure_git_https 和 invalidate_path_cache 定义

🤖 Generated with [Qoder][https://qoder.com]

* refactor: standalone 目录集中化、unsafe set_var 适配、Linux 平台检测补全

- 提升 all_standalone_dirs() / standalone_install_dir() 为 pub(crate),
  作为全局唯一的 standalone 路径来源
- build_enhanced_path() 三平台块改为调用 config::all_standalone_dirs()
- service.rs 三平台 candidate 函数改为调用 super::config::all_standalone_dirs()
- utils.rs common_non_windows_cli_candidates() 改为调用集中函数
- std::env::set_var 包裹 unsafe 块,附 SAFETY 注释,适配 Rust 1.83+
- 补全 Linux detect_installed_source(): CLI 路径分类 -> symlink -> standalone -> npm list
- 补全 Linux get_local_version(): 活跃 CLI -> standalone VERSION -> symlink package.json

🤖 Generated with [Qoder][https://qoder.com]

* fix: service.rs 模块路径修正 super::config -> crate::commands::config

mod platform 内部 super 指向 service 模块而非 commands,
需要完整路径 crate::commands::config 才能访问 all_standalone_dirs()

🤖 Generated with [Qoder][https://qoder.com]

---------

Co-authored-by: SEVENTEEN-TAN <SEVENTEEN-TAN@users.noreply.github.com>
This commit is contained in:
SEVENTEEN-TAN
2026-03-30 22:50:11 +08:00
committed by GitHub
parent 61bfd56865
commit 87d7c227d8
8 changed files with 253 additions and 81 deletions

View File

@@ -83,7 +83,7 @@ async function loadData(page) {
checkHotUpdate(cards, panelVersion)
const isInstalled = !!version.current
const sourceLabel = version.source === 'official' ? t('about.official') : t('about.chinese')
const sourceLabel = version.source === 'official' ? t('about.official') : version.source === 'chinese' ? t('about.chinese') : t('about.unknownSource')
const btnSm = 'padding:2px 8px;font-size:var(--font-size-xs)'
const hasRecommended = !!version.recommended
const aheadOfRecommended = isInstalled && hasRecommended && !!version.ahead_of_recommended
@@ -250,18 +250,18 @@ async function showVersionPicker(page, currentVersion) {
if (!targetVer || targetVer === '') { hintEl.textContent = ''; confirmBtn.disabled = true; return }
const targetTag = select.selectedIndex === 0 ? t('about.tagRecommended') : t('about.tagNeedTest')
const sameSource = targetSource === (currentVersion.source === 'official' ? 'official' : 'chinese')
const sameSource = targetSource === currentVersion.source
if (!isInstalled) {
confirmBtn.textContent = t('about.btnInstall')
hintEl.textContent = t('about.hintInstall', { source: targetSource === 'official' ? t('about.official') : t('about.chinese'), ver: targetVer, tag: targetTag })
hintEl.textContent = t('about.hintInstall', { source: targetSource === 'official' ? t('about.official') : targetSource === 'chinese' ? t('about.chinese') : t('about.unknownSource'), ver: targetVer, tag: targetTag })
confirmBtn.disabled = false
return
}
if (!sameSource) {
confirmBtn.textContent = t('about.btnSwitch')
hintEl.innerHTML = `${t('about.hintCurrent')}: <strong>${currentVersion.source === 'official' ? t('about.official') : t('about.chinese')} ${currentVersion.current}</strong> → <strong>${targetSource === 'official' ? t('about.official') : t('about.chinese')} ${targetVer}</strong>${targetTag}`
hintEl.innerHTML = `${t('about.hintCurrent')}: <strong>${currentVersion.source === 'official' ? t('about.official') : currentVersion.source === 'chinese' ? t('about.chinese') : t('about.unknownSource')} ${currentVersion.current}</strong> → <strong>${targetSource === 'official' ? t('about.official') : targetSource === 'chinese' ? t('about.chinese') : t('about.unknownSource')} ${targetVer}</strong>${targetTag}`
confirmBtn.disabled = false
return
}
@@ -310,7 +310,7 @@ async function showVersionPicker(page, currentVersion) {
const versions = showNightly ? allVersions : (stable.length > 0 ? stable : allVersions)
const nightlyCount = allVersions.length - stable.length
select.innerHTML = versions.map((v, idx) => {
const isCurrent = isInstalled && v === currentVersion.current && source === (currentVersion.source === 'official' ? 'official' : 'chinese')
const isCurrent = isInstalled && v === currentVersion.current && source === currentVersion.source
return `<option value="${v}">${v}${idx === 0 ? ` (${t('about.recommended')})` : ''}${isCurrent ? ` (${t('about.current')})` : ''}</option>`
}).join('')
// nightly 切换提示

View File

@@ -186,7 +186,7 @@ function renderStatCards(page, services, version, agents, config) {
</div>
<div class="stat-card">
<div class="stat-card-header">
<span class="stat-card-label">${t('dashboard.versionLabel')} · ${version.source === 'official' ? t('dashboard.versionOfficial') : t('dashboard.versionChinese')}</span>
<span class="stat-card-label">${t('dashboard.versionLabel')} · ${version.source === 'official' ? t('dashboard.versionOfficial') : version.source === 'chinese' ? t('dashboard.versionChinese') : t('dashboard.versionUnknownSource')}</span>
</div>
<div class="stat-card-value">${version.current || t('common.unknown')}</div>
<div class="stat-card-meta">${versionMeta}</div>