From 0802aa6d80a75d7c76ad7b14e026542a245ec0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Tue, 14 Jul 2026 08:54:31 -0700 Subject: [PATCH] feat(openclaw): support 2026.7.1 stable --- CHANGELOG.md | 3 +- CONTRIBUTING.md | 2 +- README.md | 4 +- docs/linux-deploy.md | 2 +- openclaw-version-policy.json | 4 +- scripts/dev-api.js | 28 +++++++-- scripts/linux-deploy.sh | 56 +++++++++++------ src-tauri/src/commands/config.rs | 66 +++++++++++++++++---- src/lib/feature-catalog.js | 4 +- tests/kernel.test.js | 4 +- tests/node-runtime-detection-policy.test.js | 31 +++++++--- tests/openclaw-2026-7-1-compat.test.js | 37 ++++++++++++ 12 files changed, 188 insertions(+), 53 deletions(-) create mode 100644 tests/openclaw-2026-7-1-compat.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f7bcd48..4c59b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,8 @@ - **Hermes 0.18.0 资源兼容** — 保留 Dashboard 兼容兜底,但新版已内置 `dashboard_auth` 与 `web_dist` 时会自动使用上游真实资源 - **Hermes Provider Registry 对齐** — 新增 Google Vertex AI provider 入口,移除上游已撤下的 `google-gemini-cli` OAuth 入口,并在安装向导展示外部凭据 / SDK 类 provider,避免误引导用户填写不存在的静态 API Key - **Hermes Provider 补齐** — 跟进上游 `novita` 与 `stepfun` provider,桌面端和 Web/headless 模式的安装向导保持一致 -- **OpenClaw 推荐稳定版更新** — 推荐稳定版切换到官方 `2026.6.11` / 汉化版 `2026.6.11-zh.1`,并保留新版 `tui`、`tools.toolSearch`、`secrets` 与 `auth.profiles` 等高级配置字段,避免校准修复误清理 +- **OpenClaw 2026.7.1 稳定版适配** — 推荐稳定版切换到官方 `2026.7.1` / 汉化修复版 `2026.7.1-zh.2`;桌面端、Web/headless、Linux 部署与 standalone 安装统一使用同一稳定基线 +- **OpenClaw 7.1 Node.js 门槛对齐** — 优先读取安装包 `engines.node`,缺少包元数据时按 `>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0` 兜底,避免误放行 Node.js 23 或过旧的 24.x ### 改进 (Improvements) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cac6a5..171bca0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ | 依赖 | 最低版本 | 说明 | |------|----------|------| -| Node.js | 18+ | 前端构建与 Web 后端;运行 OpenClaw Gateway 时按当前 OpenClaw 的 `engines.node` 检测,OpenClaw 2026.6.5+ 通常需要 22.19.0+ | +| Node.js | 18+ | 前端构建与 Web 后端;运行 OpenClaw Gateway 时按当前 OpenClaw 的 `engines.node` 检测,OpenClaw 2026.7.1 要求 `>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0` | | Rust | stable | Tauri 后端编译 | | Tauri CLI | v2 | `cargo install tauri-cli --version "^2"` | diff --git a/README.md b/README.md index 3539d6c..9ac0e57 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ ClawPanel 提供**纯 Web 版部署模式**(零 GUI 依赖),天然兼容 A - **Orange Pi / 树莓派 / RK3588** 等 ARM64 板子 — `npm run serve` 即可运行 - **Docker ARM64 镜像** — `docker run ghcr.io/qingchencloud/openclaw:latest` 开箱即用 - **Armbian / Debian / Ubuntu Server** — 一键部署脚本自动检测架构 -- 无需 Rust / Tauri / 图形界面;ClawPanel Web 后端需要 **Node.js 18+**,运行 OpenClaw Gateway 时会按当前 OpenClaw 的 `engines.node` 自动检测(OpenClaw 2026.6.5+ 通常需要 **Node.js 22.19.0+**) +- 无需 Rust / Tauri / 图形界面;ClawPanel Web 后端需要 **Node.js 18+**,运行 OpenClaw Gateway 时会按当前 OpenClaw 的 `engines.node` 自动检测(OpenClaw 2026.7.1 要求 `>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0`) > 📖 详见 [Armbian 部署指南](docs/armbian-deploy.md) | [Web 版开发说明](#web-开发版无需-rusttauri) @@ -757,7 +757,7 @@ Web 版适用于 Linux 服务器(无桌面环境),通过浏览器远程管 ### 环境要求 -- **Node.js** >= 18(ClawPanel Web 后端);运行 OpenClaw Gateway 会按当前 OpenClaw 要求自动检测,OpenClaw 2026.6.5+ 通常需要 **22.19.0+** +- **Node.js** >= 18(ClawPanel Web 后端);运行 OpenClaw Gateway 会按当前 OpenClaw 要求自动检测,OpenClaw 2026.7.1 要求 `>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0` - **Git**(用于 OpenClaw 依赖安装) - **端口** 1420(ClawPanel)+ 18789(Gateway) diff --git a/docs/linux-deploy.md b/docs/linux-deploy.md index 0e7f499..df65a12 100644 --- a/docs/linux-deploy.md +++ b/docs/linux-deploy.md @@ -49,7 +49,7 @@ | 依赖 | 最低版本 | 说明 | |------|----------|------| -| Node.js | 18+ | ClawPanel Web 后端;运行 OpenClaw Gateway 时按当前 OpenClaw `engines.node` 检测,OpenClaw 2026.6.5+ 通常需要 22.19.0+ | +| Node.js | 18+ | ClawPanel Web 后端;运行 OpenClaw Gateway 时按当前 OpenClaw `engines.node` 检测,OpenClaw 2026.7.1 要求 `>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0` | | npm | 随 Node.js | 包管理器 | | Git | 任意 | 克隆仓库 | | OpenClaw | 最新 | ClawPanel 管理的对象 | diff --git a/openclaw-version-policy.json b/openclaw-version-policy.json index 576addd..b82c1be 100644 --- a/openclaw-version-policy.json +++ b/openclaw-version-policy.json @@ -9,10 +9,10 @@ }, "default": { "official": { - "recommended": "2026.6.11" + "recommended": "2026.7.1" }, "chinese": { - "recommended": "2026.6.11-zh.1" + "recommended": "2026.7.1-zh.2" } }, "panels": { diff --git a/scripts/dev-api.js b/scripts/dev-api.js index b33712d..4e68929 100644 --- a/scripts/dev-api.js +++ b/scripts/dev-api.js @@ -334,8 +334,10 @@ const PANEL_VERSION = (() => { })() const SITE_BASE_URL = 'https://claw.qt.cool' const VERSION_POLICY_PATH = path.join(__dev_dirname, '..', 'openclaw-version-policy.json') -const OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR = '2026.6.5' -const OPENCLAW_NODE_REQUIREMENT_FOR_NEWER_RUNTIME = '>=22.19.0' +const OPENCLAW_NODE_22_19_VERSION_FLOOR = '2026.6.5' +const OPENCLAW_NODE_22_19_REQUIREMENT = '>=22.19.0' +const OPENCLAW_NODE_7_1_VERSION_FLOOR = '2026.7.1' +const OPENCLAW_NODE_7_1_REQUIREMENT = '>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0' function ensureArrayContains(value, required) { const current = Array.isArray(value) @@ -746,9 +748,15 @@ function openclawNodeRequirement() { } } if (!installedVersion && cliPath) installedVersion = readVersionFromInstallation(cliPath) - return installedVersion && versionGe(baseVersion(installedVersion), OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR) - ? OPENCLAW_NODE_REQUIREMENT_FOR_NEWER_RUNTIME - : null + return fallbackOpenclawNodeRequirement(installedVersion) +} + +function fallbackOpenclawNodeRequirement(installedVersion) { + if (!installedVersion) return null + const version = baseVersion(installedVersion) + if (versionGe(version, OPENCLAW_NODE_7_1_VERSION_FLOOR)) return OPENCLAW_NODE_7_1_REQUIREMENT + if (versionGe(version, OPENCLAW_NODE_22_19_VERSION_FLOOR)) return OPENCLAW_NODE_22_19_REQUIREMENT + return null } function parseNodeVersionTriplet(value) { @@ -772,10 +780,18 @@ function nodeVersionSatisfiesClause(version, clause) { const min = parseNodeVersionTriplet(raw.slice(2)) return !!min && compareVersionTriplet(version, min) >= 0 } + if (raw.startsWith('<=')) { + const max = parseNodeVersionTriplet(raw.slice(2)) + return !!max && compareVersionTriplet(version, max) <= 0 + } if (raw.startsWith('>')) { const min = parseNodeVersionTriplet(raw.slice(1)) return !!min && compareVersionTriplet(version, min) > 0 } + if (raw.startsWith('<')) { + const max = parseNodeVersionTriplet(raw.slice(1)) + return !!max && compareVersionTriplet(version, max) < 0 + } if (raw.startsWith('^')) { const min = parseNodeVersionTriplet(raw.slice(1)) if (!min) return false @@ -17665,7 +17681,7 @@ async function _apiMiddleware(req, res, next) { } // 导出供 serve.js 独立部署使用 -export { _initApi, _apiMiddleware } +export { _initApi, _apiMiddleware, nodeVersionSatisfiesRequirement } export function devApiPlugin() { let _inited = false diff --git a/scripts/linux-deploy.sh b/scripts/linux-deploy.sh index 14e9c21..f8d3194 100644 --- a/scripts/linux-deploy.sh +++ b/scripts/linux-deploy.sh @@ -12,10 +12,13 @@ REPO_URL="https://github.com/qingchencloud/clawpanel.git" REPO_URL_GITEE="https://gitee.com/QtCodeCreators/clawpanel.git" NPM_REGISTRY="https://registry.npmmirror.com" PANEL_NODE_MIN_VERSION="18.0.0" -OPENCLAW_RECOMMENDED_VERSION="2026.5.18-zh.1" -OPENCLAW_NODE_RUNTIME_FLOOR_VERSION="2026.6.5" -OPENCLAW_NEW_NODE_MIN_VERSION="22.19.0" +OPENCLAW_RECOMMENDED_VERSION="2026.7.1-zh.2" +OPENCLAW_NODE_22_19_FLOOR_VERSION="2026.6.5" +OPENCLAW_NODE_7_1_FLOOR_VERSION="2026.7.1" +OPENCLAW_NODE_22_19_REQUIREMENT=">=22.19.0" +OPENCLAW_7_1_NODE_REQUIREMENT=">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0" NODE_MIN_VERSION="$PANEL_NODE_MIN_VERSION" +NODE_REQUIREMENT=">=${PANEL_NODE_MIN_VERSION}" # 检测权限模式 if [ "$(id -u)" = "0" ]; then @@ -95,13 +98,24 @@ version_ge() { [ "$actual_patch" -ge "$min_patch" ] } -node_version_ge_min() { - version_ge "$1" "$NODE_MIN_VERSION" +node_version_satisfies_7_1() { + local actual="${1#v}" + local major + actual=$(printf '%s' "$actual" | grep -Eo '[0-9]+(\.[0-9]+){0,2}' | head -1 || true) + major=${actual%%.*} + case "$major" in + 22) version_ge "$actual" "22.22.3" && ! version_ge "$actual" "23.0.0" ;; + 24) version_ge "$actual" "24.15.0" && ! version_ge "$actual" "25.0.0" ;; + *) [ "$major" -ge 25 ] 2>/dev/null && version_ge "$actual" "25.9.0" ;; + esac } -openclaw_version_needs_new_node() { - local base_version="${1%%-*}" - [ -n "$base_version" ] && version_ge "$base_version" "$OPENCLAW_NODE_RUNTIME_FLOOR_VERSION" +node_version_satisfies_requirement() { + if [ "$NODE_REQUIREMENT" = "$OPENCLAW_7_1_NODE_REQUIREMENT" ]; then + node_version_satisfies_7_1 "$1" + else + version_ge "$1" "$NODE_MIN_VERSION" + fi } # 安装 Node.js @@ -109,15 +123,15 @@ install_node() { if command -v node &> /dev/null; then local node_version node_version=$(node -v) - if node_version_ge_min "$node_version"; then + if node_version_satisfies_requirement "$node_version"; then echo "✅ Node.js $(node -v) 已安装" return 0 else - echo "⚠️ Node.js $(node -v) 版本过低,需要 >=${NODE_MIN_VERSION}" + echo "⚠️ Node.js $(node -v) 不满足要求 ${NODE_REQUIREMENT}" fi fi - echo "📦 安装 Node.js LTS(要求 >=${NODE_MIN_VERSION})..." + echo "📦 安装 Node.js LTS(要求 ${NODE_REQUIREMENT})..." case "$OS" in ubuntu|debian|linuxmint|pop) curl -fsSL https://deb.nodesource.com/setup_22.x | run_pkg_cmd bash - @@ -139,9 +153,9 @@ install_node() { exit 1 ;; esac - if ! node_version_ge_min "$(node -v)"; then - echo "❌ Node.js $(node -v) 仍低于 OpenClaw 要求 >=${NODE_MIN_VERSION}" - echo " 请手动安装 Node.js ${NODE_MIN_VERSION} 或更高版本后重试" + if ! node_version_satisfies_requirement "$(node -v)"; then + echo "❌ Node.js $(node -v) 仍不满足 OpenClaw 要求 ${NODE_REQUIREMENT}" + echo " 请手动安装满足 ${NODE_REQUIREMENT} 的 Node.js 后重试" exit 1 fi echo "✅ Node.js $(node -v) 安装完成" @@ -149,9 +163,16 @@ install_node() { ensure_node_for_openclaw_version() { local openclaw_version="$1" - if openclaw_version_needs_new_node "$openclaw_version"; then - NODE_MIN_VERSION="$OPENCLAW_NEW_NODE_MIN_VERSION" - echo "ℹ️ OpenClaw ${openclaw_version} 需要 Node.js >=${NODE_MIN_VERSION}" + local base_version="${openclaw_version%%-*}" + if [ -n "$base_version" ] && version_ge "$base_version" "$OPENCLAW_NODE_7_1_FLOOR_VERSION"; then + NODE_MIN_VERSION="22.22.3" + NODE_REQUIREMENT="$OPENCLAW_7_1_NODE_REQUIREMENT" + echo "ℹ️ OpenClaw ${openclaw_version} 需要 Node.js ${NODE_REQUIREMENT}" + install_node + elif [ -n "$base_version" ] && version_ge "$base_version" "$OPENCLAW_NODE_22_19_FLOOR_VERSION"; then + NODE_MIN_VERSION="22.19.0" + NODE_REQUIREMENT="$OPENCLAW_NODE_22_19_REQUIREMENT" + echo "ℹ️ OpenClaw ${openclaw_version} 需要 Node.js ${NODE_REQUIREMENT}" install_node fi } @@ -222,6 +243,7 @@ detect_openclaw_source() { # 安装 OpenClaw install_openclaw() { + ensure_node_for_openclaw_version "$OPENCLAW_RECOMMENDED_VERSION" local oc_path=$(find_openclaw) local oc_ver="" if [ -n "$oc_path" ]; then diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index b957e75..3997d41 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -167,11 +167,21 @@ fn node_version_satisfies_clause(version: [u32; 3], clause: &str) -> bool { .map(|min| cmp_version_triplet(version, min).is_ge()) .unwrap_or(false); } + if let Some(raw) = clause.strip_prefix("<=") { + return parse_node_version_triplet(raw) + .map(|max| cmp_version_triplet(version, max).is_le()) + .unwrap_or(false); + } if let Some(raw) = clause.strip_prefix('>') { return parse_node_version_triplet(raw) .map(|min| cmp_version_triplet(version, min).is_gt()) .unwrap_or(false); } + if let Some(raw) = clause.strip_prefix('<') { + return parse_node_version_triplet(raw) + .map(|max| cmp_version_triplet(version, max).is_lt()) + .unwrap_or(false); + } if let Some(raw) = clause.strip_prefix('^') { let Some(min) = parse_node_version_triplet(raw) else { return false; @@ -210,11 +220,20 @@ fn read_package_json_field(path: &std::path::Path, pointer: &str) -> Option bool { - parse_version(&base_version(version)) >= parse_version(OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR) +fn fallback_openclaw_node_requirement(version: &str) -> Option<&'static str> { + let version = parse_version(&base_version(version)); + if version >= parse_version(OPENCLAW_NODE_7_1_VERSION_FLOOR) { + return Some(OPENCLAW_NODE_7_1_REQUIREMENT); + } + if version >= parse_version(OPENCLAW_NODE_22_19_VERSION_FLOOR) { + return Some(OPENCLAW_NODE_22_19_REQUIREMENT); + } + None } fn cli_source_prefers_zh_package(cli_source: &str) -> bool { @@ -271,8 +290,9 @@ pub(crate) fn openclaw_node_requirement() -> Option { .and_then(|pkg| read_package_json_field(pkg, "/version")) .or_else(|| read_version_from_installation(cli_path_ref)); installed_version - .filter(|version| openclaw_version_requires_node_22_19(version)) - .map(|_| OPENCLAW_NODE_REQUIREMENT_FOR_NEWER_RUNTIME.to_string()) + .as_deref() + .and_then(fallback_openclaw_node_requirement) + .map(str::to_string) } fn standalone_bundled_node_bin(cli_path: &str) -> Option { @@ -7707,9 +7727,9 @@ pub fn invalidate_path_cache() -> Result<(), String> { mod write_openclaw_config_merge_tests { use super::apply_reset_inheritance; use super::calibration_richness_score; + use super::fallback_openclaw_node_requirement; use super::merge_configs_preserving_fields; use super::node_version_satisfies_requirement; - use super::openclaw_version_requires_node_22_19; use super::path_without_curdir_string; use super::promote_nested_standalone_dir; #[cfg(target_os = "windows")] @@ -7978,11 +7998,33 @@ mod write_openclaw_config_merge_tests { } #[test] - fn openclaw_node_requirement_floor_starts_at_2026_6_5() { - assert!(!openclaw_version_requires_node_22_19("2026.6.4")); - assert!(openclaw_version_requires_node_22_19("2026.6.5")); - assert!(openclaw_version_requires_node_22_19("2026.6.5-zh.1")); - assert!(openclaw_version_requires_node_22_19("2026.7.1")); + fn openclaw_node_requirement_fallback_tracks_runtime_floors() { + assert_eq!(fallback_openclaw_node_requirement("2026.6.4"), None); + assert_eq!( + fallback_openclaw_node_requirement("2026.6.5-zh.1"), + Some(">=22.19.0") + ); + assert_eq!( + fallback_openclaw_node_requirement("2026.7.1"), + Some(">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0") + ); + assert_eq!( + fallback_openclaw_node_requirement("2026.7.1-zh.1"), + Some(">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0") + ); + } + + #[test] + fn openclaw_2026_7_1_node_range_rejects_unsupported_gaps() { + let requirement = ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0"; + assert!(!node_version_satisfies_requirement("v22.22.2", requirement)); + assert!(node_version_satisfies_requirement("v22.22.3", requirement)); + assert!(!node_version_satisfies_requirement("v23.11.1", requirement)); + assert!(!node_version_satisfies_requirement("v24.14.9", requirement)); + assert!(node_version_satisfies_requirement("v24.15.0", requirement)); + assert!(!node_version_satisfies_requirement("v25.8.9", requirement)); + assert!(node_version_satisfies_requirement("v25.9.0", requirement)); + assert!(node_version_satisfies_requirement("v33.0.0", requirement)); } #[test] diff --git a/src/lib/feature-catalog.js b/src/lib/feature-catalog.js index e03f49d..290ba22 100644 --- a/src/lib/feature-catalog.js +++ b/src/lib/feature-catalog.js @@ -196,8 +196,8 @@ export const KERNEL_TARGET = { // 不要与 dev-api.js 中设备签名 payload 字符串前缀 `v3|deviceId|...` 混淆—— // 后者是 **device signature payload 字符串格式版本**,两者完全独立、互不相关。 // 即使在 v4 握手协议下,签名 payload 字符串仍以 `v3|` 开头(这是 payload schema 版本)。 - official: '2026.5.19', - chinese: '2026.5.18-zh.1', + official: '2026.7.1', + chinese: '2026.7.1-zh.2', }, hermes: { default: '0.13.x', diff --git a/tests/kernel.test.js b/tests/kernel.test.js index 450cef5..e703a16 100644 --- a/tests/kernel.test.js +++ b/tests/kernel.test.js @@ -162,8 +162,8 @@ test('buildSnapshot edge case: version slightly below 5.6 feature requirement', test('buildSnapshot.isLatest works against KERNEL_TARGET', () => { const atOfficial = buildSnapshot('openclaw', KERNEL_TARGET.openclaw.official) const atChinese = buildSnapshot('openclaw', KERNEL_TARGET.openclaw.chinese) - const aboveTarget = buildSnapshot('openclaw', '2026.6.0') - const belowTarget = buildSnapshot('openclaw', '2026.5.18') + const aboveTarget = buildSnapshot('openclaw', '2026.7.2') + const belowTarget = buildSnapshot('openclaw', '2026.6.11') assert.equal(atOfficial.isLatest, true) assert.equal(atChinese.isLatest, true) assert.equal(aboveTarget.isLatest, true) diff --git a/tests/node-runtime-detection-policy.test.js b/tests/node-runtime-detection-policy.test.js index c6aabfd..6b6b4c1 100644 --- a/tests/node-runtime-detection-policy.test.js +++ b/tests/node-runtime-detection-policy.test.js @@ -1,6 +1,7 @@ import test from 'node:test' import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' +import { nodeVersionSatisfiesRequirement } from '../scripts/dev-api.js' const devApi = readFileSync(new URL('../scripts/dev-api.js', import.meta.url), 'utf8') const rustConfig = readFileSync(new URL('../src-tauri/src/commands/config.rs', import.meta.url), 'utf8') @@ -27,13 +28,29 @@ test('desktop check_node prefers standalone bundled Node before PATH lookup', () assert.match(rustConfig, /"standalone-bundled"/) }) -test('Node 22.19 fallback is gated by OpenClaw 2026.6.5 or newer', () => { - assert.match(devApi, /OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR = '2026\.6\.5'/) - assert.match(devApi, /OPENCLAW_NODE_REQUIREMENT_FOR_NEWER_RUNTIME = '>=22\.19\.0'/) - assert.match(devApi, /versionGe\(baseVersion\(installedVersion\), OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR\)/) +test('Node fallback follows the OpenClaw 6.5 and 7.1 runtime floors', () => { + assert.match(devApi, /OPENCLAW_NODE_22_19_VERSION_FLOOR = '2026\.6\.5'/) + assert.match(devApi, /OPENCLAW_NODE_22_19_REQUIREMENT = '>=22\.19\.0'/) + assert.match(devApi, /OPENCLAW_NODE_7_1_VERSION_FLOOR = '2026\.7\.1'/) + assert.match(devApi, /OPENCLAW_NODE_7_1_REQUIREMENT = '>=22\.22\.3 <23 \|\| >=24\.15\.0 <25 \|\| >=25\.9\.0'/) + assert.match(devApi, /fallbackOpenclawNodeRequirement\(installedVersion\)/) assert.doesNotMatch(devApi, /DEFAULT_OPENCLAW_NODE_REQUIREMENT/) - assert.match(rustConfig, /OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR: &str = "2026\.6\.5"/) - assert.match(rustConfig, /OPENCLAW_NODE_REQUIREMENT_FOR_NEWER_RUNTIME: &str = ">=22\.19\.0"/) - assert.match(rustConfig, /openclaw_version_requires_node_22_19/) + assert.match(rustConfig, /OPENCLAW_NODE_22_19_VERSION_FLOOR: &str = "2026\.6\.5"/) + assert.match(rustConfig, /OPENCLAW_NODE_22_19_REQUIREMENT: &str = ">=22\.19\.0"/) + assert.match(rustConfig, /OPENCLAW_NODE_7_1_VERSION_FLOOR: &str = "2026\.7\.1"/) + assert.match(rustConfig, /OPENCLAW_NODE_7_1_REQUIREMENT: &str =\s*">=22\.22\.3 <23 \|\| >=24\.15\.0 <25 \|\| >=25\.9\.0"/) + assert.match(rustConfig, /fallback_openclaw_node_requirement/) +}) + +test('Web Node range parser accepts OpenClaw 2026.7.1 supported versions only', () => { + const requirement = '>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0' + assert.equal(nodeVersionSatisfiesRequirement('v22.22.2', requirement), false) + assert.equal(nodeVersionSatisfiesRequirement('v22.22.3', requirement), true) + assert.equal(nodeVersionSatisfiesRequirement('v23.11.1', requirement), false) + assert.equal(nodeVersionSatisfiesRequirement('v24.14.9', requirement), false) + assert.equal(nodeVersionSatisfiesRequirement('v24.15.0', requirement), true) + assert.equal(nodeVersionSatisfiesRequirement('v25.8.9', requirement), false) + assert.equal(nodeVersionSatisfiesRequirement('v25.9.0', requirement), true) + assert.equal(nodeVersionSatisfiesRequirement('v33.0.0', requirement), true) }) diff --git a/tests/openclaw-2026-7-1-compat.test.js b/tests/openclaw-2026-7-1-compat.test.js new file mode 100644 index 0000000..cdc5c3a --- /dev/null +++ b/tests/openclaw-2026-7-1-compat.test.js @@ -0,0 +1,37 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' + +const policy = JSON.parse(readFileSync(new URL('../openclaw-version-policy.json', import.meta.url), 'utf8')) +const featureCatalog = readFileSync(new URL('../src/lib/feature-catalog.js', import.meta.url), 'utf8') +const linuxDeploy = readFileSync(new URL('../scripts/linux-deploy.sh', import.meta.url), 'utf8') +const webBackend = readFileSync(new URL('../scripts/dev-api.js', import.meta.url), 'utf8') +const desktopDevice = readFileSync(new URL('../src-tauri/src/commands/device.rs', import.meta.url), 'utf8') + +test('ClawPanel recommends the matching official and Chinese 2026.7.1 stable builds', () => { + assert.equal(policy.default.official.recommended, '2026.7.1') + assert.equal(policy.default.chinese.recommended, '2026.7.1-zh.2') + assert.match(featureCatalog, /official: '2026\.7\.1'/) + assert.match(featureCatalog, /chinese: '2026\.7\.1-zh\.2'/) +}) + +test('Linux deployment installs the Chinese 2026.7.1 stable build', () => { + assert.match(linuxDeploy, /OPENCLAW_RECOMMENDED_VERSION="2026\.7\.1-zh\.2"/) + assert.match(linuxDeploy, /\[ "\$major" -ge 25 \]/) + assert.match( + linuxDeploy, + /OPENCLAW_7_1_NODE_REQUIREMENT=">=22\.22\.3 <23 \|\| >=24\.15\.0 <25 \|\| >=25\.9\.0"/, + ) + + const installStart = linuxDeploy.indexOf('install_openclaw() {') + const npmInstall = linuxDeploy.indexOf('npm install -g "$openclaw_spec"', installStart) + const runtimeGuard = linuxDeploy.indexOf('ensure_node_for_openclaw_version "$OPENCLAW_RECOMMENDED_VERSION"', installStart) + assert.ok(installStart >= 0 && runtimeGuard > installStart, 'OpenClaw install must preflight its target runtime') + assert.ok(npmInstall > runtimeGuard, 'Node runtime preflight must happen before npm installs OpenClaw') +}) + +test('Gateway connect frames retain a range that overlaps OpenClaw 2026.7.1 protocol v4', () => { + assert.match(webBackend, /minProtocol: 3, maxProtocol: 4/) + assert.match(desktopDevice, /"minProtocol": 3/) + assert.match(desktopDevice, /"maxProtocol": 4/) +})