Clawdbot(Moltbot)源码部署实战
一、背景
最近技术圈关注较多的是 Clawdbot(后更名为 Moltbot),支持 WhatsApp、Telegram 等通道,可私有化部署。本文记录从源码拉取到环境搭建的完整过程及踩坑经验。
二、环境准备
Windows 环境下推荐优先使用 WSL2(Ubuntu/Debian 镜像)进行部署,原生 CMD 易报依赖错误。
- 启用 WSL2:在 Windows 功能中勾选'适用于 Linux 的 Windows 子系统'和'虚拟机平台',重启后安装 Ubuntu 22.04。
- 配置 WSL2:打开终端更新系统源(建议换阿里源),执行
sudo apt update && sudo apt upgrade -y。 - 安装 Git:执行
sudo apt install git,验证git --version。
三、Node.js 安装与依赖
仓库文档要求 Node.js ≥ 22,建议使用 nvm 管理环境并切换至最新版本。
注意:版本低于 22 时
pnpm install可能报错。
1. 拉取源码 & 装依赖
git clone https://github.com/openclaw/openclaw.git
cd openclaw
pnpm install
常见问题:安装时 node-llama-cpp 的 postinstall 脚本可能失败(错误码 3221225477)。该依赖仅用于本地嵌入,若使用远程模型(如 OpenAI)可忽略。解决方案是注释掉 package.json 中的 node-llama-cpp 依赖后重新运行 pnpm install。
2. 构建项目
先构建 UI:
pnpm ui:build
再构建主项目:
pnpm build
若在原生 Windows 上构建需 Bash 环境。检查是否有 Git Bash 可用,或在项目 /script 目录下创建 Node.js 版本的构建脚本 bundle-a2ui.mjs:
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
import { promisify } from "node:util";
const spawnAsync = promisify(spawn);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT_DIR = path.resolve(__dirname, "..");
const HASH_FILE = path.join(ROOT_DIR, "src/canvas-host/a2ui/.bundle.hash");
const OUTPUT_FILE = path.join(ROOT_DIR, "src/canvas-host/a2ui/a2ui.bundle.js");
const A2UI_RENDERER_DIR = path.join(ROOT_DIR, "vendor/a2ui/renderers/lit");
const A2UI_APP_DIR = path.join(ROOT_DIR, "apps/shared/OpenClawKit/Tools/CanvasA2UI");
async function checkDirExists(dir) {
try {
const stat = await fs.stat(dir);
return stat.isDirectory();
} catch {
return false;
}
}
async function walk(entryPath, files = []) {
const st = await fs.stat(entryPath);
if (st.isDirectory()) {
const entries = await fs.readdir(entryPath);
for (const entry of entries) {
await walk(path.join(entryPath, entry), files);
}
return files;
}
files.push(entryPath);
return files;
}
function normalize(p) {
return p.split(path.sep).join("/");
}
async function computeHash() {
const inputPaths = [
path.join(ROOT_DIR, "package.json"),
path.join(ROOT_DIR, "pnpm-lock.yaml"),
A2UI_RENDERER_DIR,
A2UI_APP_DIR,
];
const files = [];
for (const inputPath of inputPaths) {
try {
const stat = await fs.stat(inputPath);
if (stat.isDirectory() || stat.isFile()) {
await walk(inputPath, files);
}
} catch {
// Path doesn't exist, skip
}
}
files.sort((a, b) => normalize(a).localeCompare(normalize(b)));
const hash = createHash("sha256");
for (const filePath of files) {
const rel = normalize(path.relative(ROOT_DIR, filePath));
hash.update(rel);
hash.update("\0");
const content = await fs.readFile(filePath);
hash.update(content);
hash.update("\0");
}
return hash.digest("hex");
}
async function runCommand(command, args, options = {}) {
return new Promise((resolve, reject) => {
const proc = spawn(command, args, {
...options,
stdio: "inherit",
shell: false,
});
proc.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Command failed with exit code ${code}`));
}
});
proc.on("error", reject);
});
}
async function main() {
try {
if (!(await checkDirExists(A2UI_RENDERER_DIR)) || !(await checkDirExists(A2UI_APP_DIR))) {
console.log("A2UI sources missing; keeping prebuilt bundle.");
process.exit(0);
}
const currentHash = await computeHash();
let shouldBuild = true;
try {
const previousHash = await fs.readFile(HASH_FILE, "utf-8");
const outputExists = await fs.access(OUTPUT_FILE).then(() => true).catch(() => false);
if (previousHash.trim() === currentHash && outputExists) {
console.log("A2UI bundle up to date; skipping.");
shouldBuild = false;
}
} catch {
// Hash file doesn't exist, need to build
}
if (shouldBuild) {
console.log("Building A2UI bundle...");
await runCommand("pnpm", ["-s", "exec", "tsc", "-p", path.join(A2UI_RENDERER_DIR, "tsconfig.json")]);
await runCommand("pnpm", ["-s", "exec", "rolldown", "-c", path.join(A2UI_APP_DIR, "rolldown.config.mjs")]);
await fs.writeFile(HASH_FILE, currentHash, "utf-8");
console.log("A2UI bundle built successfully.");
}
} catch (error) {
console.error("A2UI bundling failed. Re-run with: pnpm canvas:a2ui:bundle");
console.error("If this persists, verify pnpm deps and try again.");
console.error(error);
process.exit(1);
}
}
main();
重新构建项目:pnpm build。
四、配置 OpenClaw
直接运行向导:pnpm openclaw onboard --install-daemon。
- 快速配置:按提示选择 yes 和 quickstart。
- 配置模型:选择智谱 AI 等支持的模型。
- 配置 API Key:输入对应模型的 API Key。
- 配置 Channel:支持接入不同 App,国内微信暂不支持,可跳过并使用 WebChat 测试。
- 配置 Skills:可选内置技能,按空格选中,回车保存。部分技能需额外申请 Key。
- 配置 Hooks:暂时可跳过。
启动 Gateway Service 后,UI 应用也会自动启动。
五、问题排查
若发送消息无响应且控制台无报错,可能是模型限额导致。修改 openclaw.json 配置文件,将模型改为 Flash 版本或其他可用模型。
注意:修改后需重启 Gateway Service 生效。
pnpm openclaw gateway restart --allow-unconfigured
可使用 openclaw status 查看网关状态。
六、常用命令
- 查看状态:
openclaw status - 发送测试消息:
openclaw message send --to +86xxxxxxxxx --message "测试 Clawdbot" - 重置会话:
openclaw agent --reset - 检查问题:
openclaw doctor - 更新版本:
openclaw update --channel stable
七、WebChat 测试
通过原生 UI WebChat 进行测试,验证工具调用能力。
- 读取文件能力:测试 Agent 调用工具读取系统文件。
- Computer Use 能力:测试 Agent 直接向系统写入文件。
可见其调用工具及 Computer Use 能力较强。


