fix(openclaw): allow upgrading kernel to latest patch

OpenClaw Chinese edition has advanced to 2026.5.12-zh.2 while the panel still recommended 2026.5.12-zh.1 and treated same-base zh patch versions as equivalent.

This updates the recommended Chinese kernel target and makes the version comparison detect suffix-level upgrades such as zh.1 -> zh.2 when both sides expose suffixes. It also adds explicit latest-upstream upgrade actions on the Services and About version cards so users can upgrade to the latest detected upstream version without going through the manual version picker.

## Changes
- update OpenClaw Chinese recommended target to 2026.5.12-zh.2
- detect same-base suffixed patch upgrades in version info
- add Services page "upgrade to latest" action and confirmation copy
- add About page latest-upstream action

## Verification
- npm run build
- cargo check
This commit is contained in:
晴天
2026-05-15 19:08:42 +08:00
parent 7f078a3c49
commit dcafd29e51
7 changed files with 50 additions and 11 deletions

View File

@@ -192,7 +192,7 @@ export const KERNEL_TARGET = {
// 内核协议在 5.12 升级到 v4MIN_CLIENT_PROTOCOL_VERSION=4新增增量 chat delta payloads
// 面板通过 connect frame `[minProtocol=3, maxProtocol=4]` 同时兼容新旧内核
official: '2026.5.12',
chinese: '2026.5.12-zh.1',
chinese: '2026.5.12-zh.2',
},
hermes: {
default: '0.13.x',

View File

@@ -42,6 +42,7 @@ export default {
switchToRecommended: _('切换到推荐版', 'Switch to recommended', '切換到推薦版'),
isRecommended: _('已是推荐稳定版', 'Already on recommended stable', '已是推薦穩定版'),
latestUpstream: _('最新上游: {ver}', 'Latest upstream: {ver}'),
switchToLatest: _('升级到最新版', 'Upgrade to latest', '升級到最新版'),
switchVersion: _('切换版本', 'Switch Version', '切換版本', 'バージョン切替', '버전 전환'),
installOpenclaw: _('安装 OpenClaw', 'Install OpenClaw', '安裝 OpenClaw', 'OpenClaw をインストール', 'OpenClaw 설치'),
uninstall: _('卸载', 'Uninstall', '卸載', 'アンインストール', '제거'),
@@ -50,6 +51,7 @@ export default {
configNotFound: _('未找到配置文件', 'Config file not found', '未找到設定檔案', '設定ファイルが見つかりません', '설정 파일을 찾을 수 없음'),
rollbackToRecommendedStable: _('回退到推荐稳定版', 'Rollback to recommended stable', '回退到推薦穩定版'),
switchToRecommendedStable: _('切换到推荐稳定版', 'Switch to recommended stable', '切換到推薦穩定版'),
switchToLatestVersion: _('升级到最新上游版', 'Upgrade to latest upstream', '升級到最新上游版'),
confirmUninstall: _('确定要卸载 OpenClaw 吗?\n\n这将停止 Gateway 服务并卸载 npm 全局包。\n配置文件~/.openclaw/)默认保留,可稍后手动删除。', 'Are you sure you want to uninstall OpenClaw?\n\nThis will stop the Gateway service and uninstall the npm global package.\nConfig files (~/.openclaw/) are kept by default and can be deleted manually later.', '確定要卸載 OpenClaw 吗?\n\n这將停止 Gateway 服務並卸載 npm 全域包。\n設定檔案~/.openclaw/)預設保留,可稍后手動刪除。'),
uninstallTitle: _('卸载 OpenClaw', 'Uninstall OpenClaw', '卸載 OpenClaw'),
uninstallStarting: _('开始卸载 OpenClaw...', 'Starting OpenClaw uninstall...', '開始卸載 OpenClaw...'),

View File

@@ -117,10 +117,11 @@ export default {
currentImageVer: _('已是当前镜像版本', 'Already on current image version', '已是目前鏡像版本'),
rollbackToRecommended: _('回退到推荐版', 'Rollback to recommended', '回退到推薦版'),
switchToRecommended: _('切换到推荐版', 'Switch to recommended', '切換到推薦版'),
upgradeToLatest: _('升级到最新版', 'Upgrade to latest', '升級到最新版'),
versionLoadFailed: _('版本信息加载失败', 'Failed to load version info', '版本資訊載入失敗'),
serviceLoadFailed: _('加载服务列表失败', 'Failed to load service list', '載入服務列表失敗'),
policyAhead: _('检测到当前本地版本 {ver} 高于面板推荐稳定版 {recommended},继续使用可能存在兼容或稳定性风险,建议尽快回退到推荐版。', 'Local version {ver} is ahead of panel recommended stable {recommended}. Continued use may have compatibility or stability risks. Consider rolling back.', '檢測到目前本地版本 {ver} 高於面板推薦穩定版 {recommended},繼續使用可能存在相容或穩定性風險,建議尽快回退到推薦版。'),
policyDefault: _('默认建议当前面板已验证的推荐稳定版。如需尝试其它版本或最新特性,请到「关于」页手动切换版本并自行验证兼容性;若希望面板优先适配最新版,欢迎提交 issue。', 'Only the panel-verified recommended stable version is suggested by default. To try other versions or latest features, manually switch in the About page and verify compatibility yourself. To request newer version support, file an issue.', '預設建議目前面板已驗證的推薦穩定版。如需尝試其它版本或最新特性,請到「關於」頁手動切換版本並自行驗證相容性;若希望面板優先適配最新版,欢迎提交 issue。'),
policyDefault: _('默认建议当前面板已验证的推荐稳定版;如果检测到最新上游,也可以点击「升级到最新版」自行验证兼容性。', 'The panel recommends the verified stable version by default. If the latest upstream is detected, you can also click "Upgrade to latest" and verify compatibility yourself.', '預設建議目前面板已驗證的推薦穩定版;如果檢測到最新上游,也可以點擊「升級到最新版」自行驗證相容性。'),
configEditor: _('配置文件编辑', 'Config Editor', '設定檔案編輯'),
configEditorHint: _('直接编辑 openclaw.json 主配置文件。保存前会自动创建备份,修改后可能需要重启 Gateway 生效。', 'Edit the openclaw.json config file directly. A backup is auto-created before saving. Changes may require a Gateway restart.', '直接編輯 openclaw.json 主設定檔案。儲存前會自動建立備份,修改后可能需要重啟 Gateway 生效。'),
configCalibration: _('配置校准', 'Config Calibration', '設定校準'),
@@ -165,8 +166,9 @@ export default {
backupDeleted: _('备份已删除', 'Backup deleted', '備份已刪除'),
backupLoadFailed: _('加载备份列表失败', 'Failed to load backup list', '載入備份列表失敗'),
upgradeTitle: _('升级 / 切换版本', 'Upgrade / Switch Version', '升級 / 切換版本'),
upgradeConfirm: _('确定要将 OpenClaw 切换到当前面板推荐的稳定{source}{version}吗?\n切换过程中 Gateway 会短暂中断。\n如果你想尝试最新版请到「关于」页手动切换版本并自测兼容性。', 'Switch OpenClaw to the panel-recommended stable {source}{version}?\nGateway will be briefly interrupted.\nTo try the latest version, manually switch in the About page.', '確定要將 OpenClaw 切換到目前面板推薦的穩定{source}{version}吗?\n切換過程中 Gateway 會短暂中斷。\n如果你想尝試最新版請到「關於」頁手動切換版本並自測相容性。'),
switchSourceConfirm: _('确定要切换到{target}{version}吗?\n这会安装对应的 npm 包,配置数据不受影响。\n如需尝试最新版请到「关于」页手动切换版本。', 'Switch to {target}{version}?\nThis installs the corresponding npm package. Config data is not affected.\nTo try the latest, manually switch in the About page.', '確定要切換到{target}{version}\n这會安裝对應的 npm 包,設定資料不受影響。\n如需尝試最新版請到「關於」頁手動切換版本。'),
upgradeConfirm: _('确定要将 OpenClaw 切换到当前面板推荐的稳定{source}{version}吗?\n切换过程中 Gateway 会短暂中断。', 'Switch OpenClaw to the panel-recommended stable {source}{version}?\nGateway will be briefly interrupted.', '確定要將 OpenClaw 切換到目前面板推薦的穩定{source}{version}吗?\n切換過程中 Gateway 會短暂中斷。'),
upgradeLatestConfirm: _('确定要将 OpenClaw 升级到最新上游{source}{version}吗?\n最新版可能包含尚未完全验证的接口或配置变化升级过程中 Gateway 会短暂中断。', 'Upgrade OpenClaw to the latest upstream {source}{version}?\nThe latest version may include API or config changes that are not fully verified yet. Gateway will be briefly interrupted.', '確定要將 OpenClaw 升級到最新上游{source}{version}\n最新版可能包含尚未完全驗證的介面或設定變化,升級過程中 Gateway 會短暫中斷。'),
switchSourceConfirm: _('确定要切换到{target}{version}吗?\n这会安装对应的 npm 包,配置数据不受影响。', 'Switch to {target}{version}?\nThis installs the corresponding npm package. Config data is not affected.', '確定要切換到{target}{version}吗?\n这會安裝对應的 npm 包,設定資料不受影響。'),
taskStarted: _('后台任务已启动,请等待完成...', 'Background task started, please wait...', '後台任務已啟動,請等待完成...'),
webModeNoLog: _('Web 模式:升级过程日志不可用,请等待完成...', 'Web mode: Upgrade logs unavailable, please wait...', 'Web 模式:升級過程日誌不可用,請等待完成...'),
taskDone: _('操作完成', 'Operation complete'),

View File

@@ -315,7 +315,8 @@ async function loadData(page) {
<button class="btn btn-primary btn-sm" id="btn-apply-recommended" style="${btnSm}">${t('about.switchToRecommended')}</button>`
: `<span style="color:var(--success)">${t('about.isRecommended')}</span>`)
: ''}
${version.latest_update_available && version.latest ? `<span style="color:var(--text-tertiary)">${t('about.latestUpstream', { ver: version.latest })}</span>` : ''}
${version.latest_update_available && version.latest ? `<span style="color:var(--text-tertiary)">${t('about.latestUpstream', { ver: version.latest })}</span>
<button class="btn btn-primary btn-sm" id="btn-apply-latest" style="${btnSm}">${t('about.switchToLatest')}</button>` : ''}
<button class="btn btn-${isInstalled ? 'secondary' : 'primary'} btn-sm" id="btn-version-mgmt" style="${btnSm}">
${isInstalled ? t('about.switchVersion') : t('about.installOpenclaw')}
</button>
@@ -336,6 +337,10 @@ async function loadData(page) {
if (applyRecommendedBtn && version.recommended) {
applyRecommendedBtn.onclick = () => doInstall(page, aheadOfRecommended ? t('about.rollbackToRecommendedStable') : t('about.switchToRecommendedStable'), version.source, version.recommended)
}
const applyLatestBtn = cards.querySelector('#btn-apply-latest')
if (applyLatestBtn && version.latest) {
applyLatestBtn.onclick = () => doInstall(page, t('about.switchToLatestVersion'), version.source, version.latest)
}
// 版本管理 / 安装
const versionMgmtBtn = cards.querySelector('#btn-version-mgmt')

View File

@@ -101,6 +101,7 @@ async function loadVersion(page) {
const hasRecommended = !!info.recommended
const aheadOfRecommended = !!info.current && hasRecommended && !!info.ahead_of_recommended
const driftFromRecommended = !!info.current && hasRecommended && !info.is_recommended && !aheadOfRecommended
const canUpgradeLatest = !!info.latest_update_available && !!info.latest
const isChinese = detectedSource === 'chinese'
const sourceTag = isChinese ? t('services.chineseEdition') : t('services.officialEdition')
const switchLabel = isChinese ? t('services.switchToOfficial') : t('services.switchToChinese')
@@ -118,8 +119,8 @@ async function loadVersion(page) {
<span class="stat-card-label">${t('services.currentVersion')} · <span style="color:var(--accent)">${t('services.dockerDeploy')}</span></span>
</div>
<div class="stat-card-value">${ver}</div>
<div class="stat-card-meta">${info.latest_update_available ? t('services.latestUpstream', { version: info.latest }) + '' + t('services.pullNewImage') + '' : t('services.currentImageVer')}</div>
${info.latest_update_available ? `<div style="margin-top:var(--space-sm)">
<div class="stat-card-meta">${canUpgradeLatest ? t('services.latestUpstream', { version: info.latest }) + '' + t('services.pullNewImage') + '' : t('services.currentImageVer')}</div>
${canUpgradeLatest ? `<div style="margin-top:var(--space-sm)">
<code style="font-size:var(--font-size-xs);background:var(--bg-tertiary);padding:4px 8px;border-radius:4px;user-select:all">${escapeHtml(`docker pull ${dockerImage}:latest`)}</code>
</div>` : ''}
</div>
@@ -137,10 +138,11 @@ async function loadVersion(page) {
${hasRecommended
? (aheadOfRecommended ? t('services.aheadOfRecommended', { version: info.recommended }) : driftFromRecommended ? t('services.recommendedStable', { version: info.recommended }) : t('services.alignedRecommended', { version: info.recommended }))
: t('services.noRecommended')}
${info.latest_update_available && info.latest ? ' · ' + t('services.latestUpstream', { version: info.latest }) : ''}
${canUpgradeLatest ? ' · ' + t('services.latestUpstream', { version: info.latest }) : ''}
</div>
<div style="display:flex;gap:var(--space-sm);margin-top:var(--space-sm);flex-wrap:wrap">
${aheadOfRecommended ? `<button class="btn btn-primary btn-sm" data-action="upgrade">${t('services.rollbackToRecommended')}</button>` : driftFromRecommended ? `<button class="btn btn-primary btn-sm" data-action="upgrade">${t('services.switchToRecommended')}</button>` : ''}
${canUpgradeLatest ? `<button class="btn btn-primary btn-sm" data-action="upgrade-latest" data-version="${escapeHtml(info.latest)}">${t('services.upgradeToLatest')}</button>` : ''}
<button class="btn btn-secondary btn-sm" data-action="switch-source" data-source="${switchTarget}">${switchLabel}</button>
</div>
<div style="margin-top:8px;font-size:var(--font-size-xs);color:var(--text-tertiary);line-height:1.6">
@@ -594,6 +596,9 @@ function bindEvents(page) {
case 'upgrade':
await handleUpgrade(btn, page)
break
case 'upgrade-latest':
await handleUpgradeLatest(btn, page)
break
case 'switch-source':
await handleSwitchSource(btn.dataset.source, page)
break
@@ -978,6 +983,15 @@ async function handleUpgrade(btn, page) {
await doUpgradeWithModal(detectedSource, page, recommended || null)
}
async function handleUpgradeLatest(btn, page) {
const sourceLabel = detectedSource === 'official' ? t('services.officialEdition') : t('services.chineseEdition')
const latest = btn.dataset.version || lastVersionInfo?.latest
if (!latest) return
const yes = await showConfirm(t('services.upgradeLatestConfirm', { source: sourceLabel, version: `${latest}` }))
if (!yes) return
await doUpgradeWithModal(detectedSource, page, latest)
}
async function handleSwitchSource(target, page) {
const targetLabel = target === 'official' ? t('services.officialEdition') : t('services.chineseEdition')
const recommended = target === 'official'