Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
. .\scripts\windows\node-runtime.ps1
$node = & node --version
if ($LASTEXITCODE -ne 0 -or -not (Test-SupportedNodeVersion $node)) {
throw "Node $node is unsupported by OpenClaw 2026.9.3 (need >=24.16.0 <25 || >=26.1.0; install Node 26)"
throw "Node $node is unsupported by OpenClaw 2026.10.2 (need >=24.16.0 <25 || >=26.1.0; install Node 26)"
}
Write-Host "node $node"

Expand Down Expand Up @@ -146,7 +146,7 @@ jobs:
. .\scripts\windows\node-runtime.ps1
$node = & node --version
if ($LASTEXITCODE -ne 0 -or -not (Test-SupportedNodeVersion $node)) {
throw "Node $node is unsupported by OpenClaw 2026.9.3 (need >=24.16.0 <25 || >=26.1.0; install Node 26)"
throw "Node $node is unsupported by OpenClaw 2026.10.2 (need >=24.16.0 <25 || >=26.1.0; install Node 26)"
}
Write-Host "node $node"
$sdks = & dotnet --list-sdks
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
. .\scripts\windows\node-runtime.ps1
$node = & node --version
if ($LASTEXITCODE -ne 0 -or -not (Test-SupportedNodeVersion $node)) {
throw "Node $node is unsupported by OpenClaw 2026.9.3 (need >=24.16.0 <25 || >=26.1.0; install Node 26)"
throw "Node $node is unsupported by OpenClaw 2026.10.2 (need >=24.16.0 <25 || >=26.1.0; install Node 26)"
}
Write-Host "node $node"
$sdks = & dotnet --list-sdks
Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ skills/**/_meta.json
# Studio backend test fixtures (if any get added later)
desktop/studio-backend/data/

# Mirrored third-party plugin source; runtime comes from the pinned release archive.
# Mirrored third-party plugin source with targeted MicroClaw SDK compatibility patches.
plugins/openclaw-weixin/

# Top-level legal / generated text
Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ cd desktop
npm run test # run all main-process tests (vitest, node env)
npm run test:watch # watch mode
npm run test:coverage # with coverage
npm run test:weixin-runtime # offline staging + emitted Weixin compatibility tests

cd desktop/renderer
npm run test # run all renderer tests (vitest, jsdom env)
Expand Down Expand Up @@ -137,7 +138,8 @@ Custom skills installed to `~/.openclaw/skills/`. Each skill has `SKILL.md` (met

- `.env` (gitignored): `MODEL_API_KEY`, `MODEL_BASE_URL`, `MODEL_NAME`, `BRAVE_API_KEY`
- `openclaw.json`: Auto-generated by installer — gateway port, skill allowlists, model config
- OpenClaw target: **2026.9.3**. Node.js engine range: `>=24.16.0 <25 || >=26.1.0` (Node 22 and 25 unsupported); Node **26** is recommended and the download fallback is **26.1.0**.
- OpenClaw's default `stable` channel is pinned in `deployer/openclaw_version.py`; keep the legacy dependency helper, CI gates, and runtime tests aligned when upgrading it.
- OpenClaw target: **2026.10.2**. Node.js engine range: `>=24.16.0 <25 || >=26.1.0` (Node 22 and 25 unsupported); Node **26** is recommended and the download fallback is **26.1.0**.
- Node.js installed via the official signed `.msi` to `%ProgramFiles%\nodejs\` (per-machine, UAC-elevated; override with `OPENCLAW_NODE_DIR`); an existing supported system Node at a standard path is reused as-is; OpenClaw state dir at `%APPDATA%/openclaw`

## Key Constants (desktop/src/constants.ts)
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
### Development Setup

1. Clone the repository
2. Install Node.js 26.1+ (recommended), or 24.16+ on the 24.x line. OpenClaw 2026.9.3 requires `>=24.16.0 <25 || >=26.1.0`; Node 22 and 25 are unsupported.
2. Install Node.js 26.1+ (recommended), or 24.16+ on the 24.x line. OpenClaw 2026.10.2 requires `>=24.16.0 <25 || >=26.1.0`; Node 22 and 25 are unsupported.
3. Install dependencies:
```bash
cd desktop && npm install
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ MicroClaw targets **Windows 10/11**. For most users, the only thing you need to

Build the installer from source, then run it:

> **Build prerequisites** (only needed to run `build.ps1`): **Node.js 26.1+ (recommended)** or **24.16+ on the 24.x line**, and **Python 3.10+**. OpenClaw **2026.9.3** requires `>=24.16.0 <25 || >=26.1.0`; Node 22 and 25 are unsupported. Install the Python build dependencies with `pip install -r requirements.txt` (this includes PyInstaller, used to package the installer exe). End users running the packaged `MicroClawInstaller.exe` do **not** need Python.
> **Build prerequisites** (only needed to run `build.ps1`): **Node.js 26.1+ (recommended)** or **24.16+ on the 24.x line**, and **Python 3.10+**. OpenClaw **2026.10.2** requires `>=24.16.0 <25 || >=26.1.0`; Node 22 and 25 are unsupported. Install the Python build dependencies with `pip install -r requirements.txt` (this includes PyInstaller, used to package the installer exe). End users running the packaged `MicroClawInstaller.exe` do **not** need Python.

```powershell
.\build.ps1 # produces dist/MicroClawInstaller/MicroClawInstaller.exe
Expand All @@ -125,13 +125,15 @@ The installer handles the Windows-side setup in a single run:

- Git for Windows (PortableGit → `~/.openclaw-git`)
- Node.js 26 via the official signed `.msi` (per-machine install to `%ProgramFiles%\nodejs\`, UAC-elevated; an existing supported system Node at that path is reused as-is; supported range: `>=24.16.0 <25 || >=26.1.0`, download fallback: `26.1.0`)
- OpenClaw Gateway (`npm install -g openclaw`)
- OpenClaw Gateway, pinned to **2026.10.2** on the default `stable` channel (`npm install -g openclaw@2026.10.2`)
- Configures the npm registry mirror and V8 compile cache
- Installs the MicroClaw desktop client, managed skills, AppContainer sandbox, WeChat plugin
- Installs the MicroClaw desktop client, managed skills, and Windows sandbox components; the WeChat plugin is not bundled or automatically installed
- Adds Windows Defender exclusions, creates desktop shortcuts (including one-click Uninstall)

After install, launch **MicroClaw** from the Start menu or the desktop shortcut. The desktop app auto-starts the Gateway.

To upgrade an existing installation, rerun the MicroClaw installer. It preserves the transactional backup, validation, and rollback flow; the legacy dependency helper refuses in-place OpenClaw upgrades. Old Node 22/23/25 configurations are advanced to a supported line. Upgrade rollback keeps the updated, pre-existing Node directory instead of deleting it and any restored OpenClaw package it contains. `stable` is an exact version pin, not npm's moving `latest` tag.

### 2. Add your model credentials

On first launch, the desktop app opens a SetupWizard only when model configuration is missing. The required user input is your model connection:
Expand Down
8 changes: 5 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ MicroClaw 仅支持 **Windows 10/11**。对大多数用户来说,真正需要

从源码构建安装器后运行:

> **构建前置依赖**(仅运行 `build.ps1` 时需要):**Node.js 26.1+(推荐)** 或 **24.x 系列的 24.16+**,以及 **Python 3.10+**。OpenClaw **2026.9.3** 要求 `>=24.16.0 <25 || >=26.1.0`,不再支持 Node 22 和 25。使用 `pip install -r requirements.txt` 安装 Python 构建依赖(其中包含用于打包安装器 exe 的 PyInstaller)。终端用户运行已打包的 `MicroClawInstaller.exe` **无需** 安装 Python。
> **构建前置依赖**(仅运行 `build.ps1` 时需要):**Node.js 26.1+(推荐)** 或 **24.x 系列的 24.16+**,以及 **Python 3.10+**。OpenClaw **2026.10.2** 要求 `>=24.16.0 <25 || >=26.1.0`,不再支持 Node 22 和 25。使用 `pip install -r requirements.txt` 安装 Python 构建依赖(其中包含用于打包安装器 exe 的 PyInstaller)。终端用户运行已打包的 `MicroClawInstaller.exe` **无需** 安装 Python。

```powershell
.\build.ps1 # 生成 dist/MicroClawInstaller/MicroClawInstaller.exe
Expand All @@ -71,13 +71,15 @@ MicroClaw 仅支持 **Windows 10/11**。对大多数用户来说,真正需要

- Git for Windows(PortableGit → `~/.openclaw-git`)
- Node.js 26,通过官方签名 `.msi` 以 per-machine 方式安装到 `%ProgramFiles%\nodejs\`(UAC 提权;若该路径已存在受支持的系统 Node 则直接复用;支持范围为 `>=24.16.0 <25 || >=26.1.0`,下载回退版本为 `26.1.0`)
- OpenClaw Gateway(`npm install -g openclaw`)
- OpenClaw Gateway,默认 `stable` 通道固定为 **2026.10.2**(`npm install -g openclaw@2026.10.2`)
- 配置 npm 镜像源与 V8 编译缓存
- 安装 MicroClaw 桌面客户端、托管技能、AppContainer 沙箱、微信插件
- 安装 MicroClaw 桌面客户端、托管技能及 Windows 沙箱组件;微信插件不随安装包捆绑,也不会自动安装
- 添加 Windows Defender 排除项,创建桌面快捷方式(含一键卸载)

安装完成后,从开始菜单或桌面快捷方式启动 **MicroClaw**。桌面应用会自动拉起 Gateway。

升级已有安装时,请重新运行 MicroClaw 安装器,继续使用事务备份、验证和失败回滚流程;旧版依赖脚本会拒绝直接覆盖升级 OpenClaw。旧配置中的 Node 22/23/25 会迁移到受支持的版本线。升级回滚会保留更新后的既有 Node 目录,避免连同已恢复的 OpenClaw 包一起删除。`stable` 是精确版本锁定,不会随 npm 的 `latest` 自动变化。

### 2. 只补上你的模型配置

首次启动时,只有在模型尚未配置的情况下才会进入 SetupWizard。这里真正需要用户填写的内容只有:
Expand Down
2 changes: 1 addition & 1 deletion build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ if (-not $nodeFound) {
Write-Host " ERROR: supported node.exe not found in any of:" -ForegroundColor Red
foreach ($candidate in $nodeCandidates) { Write-Host " - $candidate" -ForegroundColor Red }
Write-Host " - PATH (Get-Command node.exe)" -ForegroundColor Red
Write-Host " OpenClaw 2026.9.3 requires Node.js >=24.16.0 <25 || >=26.1.0." -ForegroundColor Red
Write-Host " OpenClaw 2026.10.2 requires Node.js >=24.16.0 <25 || >=26.1.0." -ForegroundColor Red
Write-Host " Install Node.js 26 (https://nodejs.org/) and re-run build.ps1." -ForegroundColor Red
exit 1
}
Expand Down
4 changes: 3 additions & 1 deletion deployer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from pathlib import Path
from typing import Any

from deployer.openclaw_version import NODE_DEFAULT_MAJOR


def _get_app_dir() -> Path:
"""Return the directory where the exe (or script) lives."""
Expand Down Expand Up @@ -57,7 +59,7 @@ def _load_dotenv() -> None:

DEFAULT_CONFIG: dict[str, Any] = {
"node": {
"version": "26",
"version": NODE_DEFAULT_MAJOR,
},
"openclaw": {
"install_method": "npm", # npm | source
Expand Down
3 changes: 2 additions & 1 deletion deployer/openclaw_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import re

OPENCLAW_TARGET_VERSION = "2026.9.3"
OPENCLAW_TARGET_VERSION = "2026.10.2"
NODE_DEFAULT_MAJOR = "26"
NODE_ENGINE_RANGE = ">=24.16.0 <25 || >=26.1.0"
NODE_FALLBACK_VERSION = "26.1.0"

Expand Down
35 changes: 30 additions & 5 deletions deployer/windows_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
prune_previous_committed_backups,
)
from deployer.openclaw_version import (
NODE_DEFAULT_MAJOR,
NODE_ENGINE_RANGE,
NODE_FALLBACK_VERSION,
OPENCLAW_TARGET_VERSION,
Expand Down Expand Up @@ -404,7 +405,7 @@ class WindowsSetup:
def __init__(self, config, logger: DeployerLogger):
self.cfg = config
self.log = logger
self.node_version = config.get("node.version", "26")
self.node_version = config.get("node.version", NODE_DEFAULT_MAJOR)
# Re-read env var at construction time (UI may have set it)
self.node_dir = Path(os.environ.get("OPENCLAW_NODE_DIR", str(DEFAULT_NODE_DIR)))
self._node_bin: Path | None = None
Expand Down Expand Up @@ -1213,6 +1214,7 @@ def install_node_windows(self) -> bool:
"""
self._select_download_mirror()
self.log.step(f"Installing Node.js on Windows ({self._mirror_name})…")
existing_node_directory = self.node_dir.exists()

version = self._resolve_target_node_version()
if not _VERSION_RE.fullmatch(version) or not is_supported_node_version(version):
Expand Down Expand Up @@ -1319,12 +1321,18 @@ def install_node_windows(self) -> bool:
ver = self._get_node_version(str(node_exe))
if not ver or not is_supported_node_version(ver):
self.log.error(
f"Node.js installed but verification failed; need {NODE_ENGINE_RANGE}"
f"Node.js installed but verification failed: {ver!r} (need {NODE_ENGINE_RANGE})"
)
return False

self.log.success(f"Node.js {ver} installed to {self.node_dir}")

# Removing an upgraded prerequisite would also delete the restored
# OpenClaw package when npm's prefix shares this directory.
if existing_node_directory:
self.log.info("Preserving the existing Node.js directory during upgrade rollback.")
return True

# Record that MicroClaw owns this Node install so the uninstaller
# knows it's safe to remove. Without this marker, uninstall
# preserves any Node.js it finds (assumes user-installed).
Expand Down Expand Up @@ -3584,10 +3592,25 @@ def _find_bundled_weixin_plugin(self) -> Path | None:
candidates.append(Path(sys.executable).parent / "plugins" / "openclaw-weixin")
# Project root (development scenario)
project_root = Path(__file__).resolve().parent.parent
candidates.append(project_root / "plugins" / "openclaw-weixin")
candidates.append(project_root / "dist" / "openclaw-weixin")
for p in candidates:
if p.is_dir() and (p / "package.json").exists():
return p
try:
package = json.loads((p / "package.json").read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
self.log.warn(f"Could not read bundled Weixin metadata at {p}: {error}")
continue
compatibility = package.get("microclaw") if isinstance(package, dict) else None
if (
isinstance(compatibility, dict)
and compatibility.get("sdkCompatibility") == OPENCLAW_TARGET_VERSION
and (p / "dist" / "index.js").is_file()
):
return p
self.log.warn(
f"Weixin bundle at {p} is not staged for OpenClaw {OPENCLAW_TARGET_VERSION}; "
"rebuild the installer with build.ps1."
)
return None

@staticmethod
Expand Down Expand Up @@ -4018,7 +4041,9 @@ def install_weixin_plugin(self) -> bool:

plugin_dir = self._find_bundled_weixin_plugin()
if not plugin_dir:
self.log.error("未找到插件安装包")
self.log.error(
"未找到插件安装包;从源码运行时请先运行 build.ps1 生成 dist/openclaw-weixin"
)
return False

state_dir = Path.home() / ".openclaw"
Expand Down
3 changes: 2 additions & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"dist:msix": "npm run prepare-resources && npm run build && node scripts/build-msix.mjs",
"prepare-windows-node-resources": "node scripts/prepare-windows-node-resources.mjs --arch=x64",
"prepare-windows-node-resources:arm64": "node scripts/prepare-windows-node-resources.mjs --arch=arm64",
"test": "vitest run",
"test": "npm run test:weixin-runtime && vitest run",
"test:weixin-runtime": "node --test scripts/stage-weixin-runtime.test.mjs",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"lint": "eslint .",
Expand Down
77 changes: 77 additions & 0 deletions desktop/scripts/stage-weixin-runtime.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import ts from "typescript";

export function stageWeixinRuntime(sourceDir, stageDir) {
const sourcePackage = JSON.parse(fs.readFileSync(path.join(sourceDir, "package.json"), "utf8"));
const stagedPackage = JSON.parse(fs.readFileSync(path.join(stageDir, "package.json"), "utf8"));
if (sourcePackage.version !== stagedPackage.version) {
throw new Error("Weixin source and vendored runtime versions do not match");
}
if (path.resolve(sourceDir) === path.resolve(stageDir)) {
throw new Error("Weixin staging must not overwrite the source directory");
}

const files = ["index.ts"];
function visit(relativeDir) {
for (const entry of fs.readdirSync(path.join(sourceDir, relativeDir), {
withFileTypes: true,
})) {
const relative = path.join(relativeDir, entry.name);
if (entry.isDirectory()) visit(relative);
else if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts")) {
files.push(relative);
}
}
}
visit("src");

let compiledCount = 0;
for (const relative of files) {
const source = fs.readFileSync(path.join(sourceDir, relative), "utf8");
const stagedSource = path.join(stageDir, relative);
fs.mkdirSync(path.dirname(stagedSource), { recursive: true });
fs.writeFileSync(stagedSource, source);
if (relative.endsWith(".d.ts")) continue;

const compiled = ts.transpileModule(source, {
fileName: relative,
reportDiagnostics: true,
compilerOptions: {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.ESNext,
},
});
const errors = (compiled.diagnostics ?? []).filter(
(diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error,
);
if (errors.length > 0) {
throw new Error(
`${relative}: ${errors.map((error) => ts.flattenDiagnosticMessageText(error.messageText, "\n")).join("\n")}`,
);
}
const stagedRuntime = path.join(stageDir, "dist", relative.replace(/\.ts$/, ".js"));
fs.mkdirSync(path.dirname(stagedRuntime), { recursive: true });
fs.writeFileSync(stagedRuntime, compiled.outputText);
compiledCount++;
}
for (const name of ["package.json", "README.md", "README.zh_CN.md"]) {
fs.copyFileSync(path.join(sourceDir, name), path.join(stageDir, name));
}
return compiledCount;
}

if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const stageDir = process.argv[2];
if (!stageDir) throw new Error("Usage: node stage-weixin-runtime.mjs <staging-directory>");
const sourceDir = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"..",
"..",
"plugins",
"openclaw-weixin",
);
const count = stageWeixinRuntime(sourceDir, path.resolve(stageDir));
console.log(`Staged ${count} Weixin modules with the MicroClaw SDK compatibility changes.`);
}
Loading
Loading