feat(openclaw): support 2026.7.1 stable

This commit is contained in:
晴天
2026-07-14 08:54:31 -07:00
parent 8cd8d2a2ad
commit 0802aa6d80
12 changed files with 188 additions and 53 deletions

View File

@@ -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)

View File

@@ -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"` |

View File

@@ -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** >= 18ClawPanel Web 后端);运行 OpenClaw Gateway 会按当前 OpenClaw 要求自动检测OpenClaw 2026.6.5+ 通常需要 **22.19.0+**
- **Node.js** >= 18ClawPanel Web 后端);运行 OpenClaw Gateway 会按当前 OpenClaw 要求自动检测OpenClaw 2026.7.1 要求 `>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0`
- **Git**(用于 OpenClaw 依赖安装)
- **端口** 1420ClawPanel+ 18789Gateway

View File

@@ -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 管理的对象 |

View File

@@ -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": {

View File

@@ -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

View File

@@ -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

View File

@@ -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<Stri
.filter(|v| !v.is_empty())
}
const OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR: &str = "2026.6.5";
const OPENCLAW_NODE_REQUIREMENT_FOR_NEWER_RUNTIME: &str = ">=22.19.0";
const OPENCLAW_NODE_22_19_VERSION_FLOOR: &str = "2026.6.5";
const OPENCLAW_NODE_22_19_REQUIREMENT: &str = ">=22.19.0";
const OPENCLAW_NODE_7_1_VERSION_FLOOR: &str = "2026.7.1";
const OPENCLAW_NODE_7_1_REQUIREMENT: &str = ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0";
fn openclaw_version_requires_node_22_19(version: &str) -> 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<String> {
.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<PathBuf> {
@@ -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]

View File

@@ -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',

View File

@@ -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)

View File

@@ -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)
})

View File

@@ -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/)
})