create-openclaw-bot 5.1.7 → 5.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/CHANGELOG.vi.md +8 -0
- package/README.md +3 -3
- package/README.vi.md +3 -3
- package/cli.js +89 -30
- package/package.json +1 -1
- package/setup.js +5 -2
- package/tests/smoke-cli-logic.mjs +9 -1
- package/tmp_diff.patch +0 -363
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# Changelog (English)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [5.1.8] — 2026-04-07
|
|
5
|
+
|
|
6
|
+
### 🌟 Dashboard VPS Connectivity & Token Login Fix
|
|
7
|
+
|
|
8
|
+
- **Fix `requireDeviceIdentity` Error on VPS**: OpenClaw's WebCrypto E2E identity check inherently demands a secure browser context (HTTPS or localhost). For raw IPv4 VPS deployments, the `crypto.subtle` browser limitation causes WebSocket `code=1008` rejection upon token login. The setup tool now seamlessly injects `requireDeviceIdentity: false` into the `gateway.controlUi` configuration, granting you flawless remote login capabilities over standard HTTP networks.
|
|
9
|
+
- **Dynamic Terminal URLs**: The programmatic CLI will now intelligently scan and log your external, reachable IPv4 addresses in the console output alongside the local endpoints. This eliminates confusion and guarantees that the automatically generated tokenized dashboard links are ready for immediate copy-pasting.
|
|
10
|
+
|
|
11
|
+
|
|
4
12
|
## [5.1.7] — 2026-04-07
|
|
5
13
|
|
|
6
14
|
### 🌟 Fix Control UI CORS & Native 9Router Path Resolution
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# Changelog (Tiếng Việt)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [5.1.8] — 2026-04-07
|
|
5
|
+
|
|
6
|
+
### 🌟 Sửa lỗi Đăng nhập Token (1008) & Cải tiến IP hiển thị trên VPS
|
|
7
|
+
|
|
8
|
+
- **Tắt `requireDeviceIdentity` để vượt tường WebCrypto**: Do cơ chế bảo mật mới của Control UI bắt buộc trình duyệt phải dùng môi trường HTTPS (hoặc localhost) thì mới cấp quyền khởi tạo key mã hóa thiết bị E2E. Nếu dùng IP thường (HTTP) thì Dashboard sẽ báo lỗi đỏ `code=1008`. Bản setup mới nhất đã tự động chích cờ `requireDeviceIdentity: false` để tắt cơ chế ép buộc này đi, giúp bạn vào thẳng Dashboard bằng IP của VPS.
|
|
9
|
+
- **Hiển thị Link Public ở Terminal**: Cấu trúc báo cáo của PM2 đã được viết lại để tự động tìm và sinh ra các đường dẫn kèm IPv4 Public (thay vì chỉ in mỗi `localhost`). Giờ đây bạn chỉ việc soi console và bấm/copy thẳng link vào trình duyệt mà không cần phải tự chế nữa.
|
|
10
|
+
|
|
11
|
+
|
|
4
12
|
## [5.1.7] — 2026-04-07
|
|
5
13
|
|
|
6
14
|
### 🌟 Sửa lỗi CORS Control UI & Đường dẫn 9Router Native
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# 🦞 OpenClaw Setup
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.8-0EA5E9?style=for-the-badge" alt="Version 5.1.8" /></a>
|
|
7
7
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
8
8
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
9
9
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
@@ -24,7 +24,7 @@ An interactive **CLI tool** and **Setup Wizard** to deploy your own free AI Bot
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## 🆕 What's new in v5.1.
|
|
27
|
+
## 🆕 What's new in v5.1.8
|
|
28
28
|
|
|
29
29
|
- 💻 **OS-First Setup** — Step 1 is now choosing your OS (Windows, macOS, Ubuntu, VPS). All scripts, configs, and instructions are generated to match.
|
|
30
30
|
- 🧠 **Gemma 4 — 4 sizes** — `gemma4:e2b` (~4 GB), `gemma4:e4b` (~8 GB), `gemma4:26b` (~18 GB), `gemma4:31b` (~24 GB). Auto-pulled on first launch.
|
|
@@ -112,7 +112,7 @@ Run in your terminal → follow the interactive prompts → startup script is ge
|
|
|
112
112
|
2. Open this repo as your workspace
|
|
113
113
|
3. Paste into chat:
|
|
114
114
|
```
|
|
115
|
-
Read SETUP.md and set up OpenClaw v5.1.
|
|
115
|
+
Read SETUP.md and set up OpenClaw v5.1.8 for me.
|
|
116
116
|
My bot token is X. Use 9Router (no API key).
|
|
117
117
|
My project folder: <YOUR_PATH>
|
|
118
118
|
```
|
package/README.vi.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# 🦞 OpenClaw Setup
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.8-0EA5E9?style=for-the-badge" alt="Version 5.1.8" /></a>
|
|
7
7
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
8
8
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
9
9
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
@@ -24,7 +24,7 @@ Công cụ **CLI tương tác** và **Setup Wizard** để tự triển khai Bot
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## 🆕 Có gì mới trong v5.1.
|
|
27
|
+
## 🆕 Có gì mới trong v5.1.8
|
|
28
28
|
|
|
29
29
|
- 💻 **OS-First Setup** — Bước đầu tiên bây giờ là chọn hệ điều hành của bạn (Windows, macOS, Ubuntu, VPS). Toàn bộ script, cấu hình và hướng dẫn được tạo ra phù hợp với lựa chọn đó.
|
|
30
30
|
- 🧠 **Gemma 4 — 4 kích thước** — `gemma4:e2b` (~4 GB), `gemma4:e4b` (~8 GB), `gemma4:26b` (~18 GB), `gemma4:31b` (~24 GB). Tự pull về khi bot khởi động lần đầu.
|
|
@@ -112,7 +112,7 @@ Chạy lệnh trên trong Terminal → làm theo các prompt tương tác → sc
|
|
|
112
112
|
2. Mở repo này làm workspace
|
|
113
113
|
3. Paste vào chat:
|
|
114
114
|
```
|
|
115
|
-
Read SETUP.md and set up OpenClaw v5.1.
|
|
115
|
+
Read SETUP.md and set up OpenClaw v5.1.8 for me.
|
|
116
116
|
My bot token is X. Use 9Router (no API key).
|
|
117
117
|
My project folder: <THƯ_MỤC_CỦA_BẠN>
|
|
118
118
|
```
|
package/cli.js
CHANGED
|
@@ -184,6 +184,45 @@ function getGatewayAllowedOrigins(port) {
|
|
|
184
184
|
return Array.from(origins);
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
function getReachableDashboardHosts(port) {
|
|
188
|
+
const normalizedPort = Number(port) || 18791;
|
|
189
|
+
const hosts = [];
|
|
190
|
+
const pushHost = (host) => {
|
|
191
|
+
if (!host) return;
|
|
192
|
+
const url = `http://${host}:${normalizedPort}`;
|
|
193
|
+
if (!hosts.includes(url)) {
|
|
194
|
+
hosts.push(url);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
pushHost('127.0.0.1');
|
|
199
|
+
pushHost('localhost');
|
|
200
|
+
|
|
201
|
+
for (const entries of Object.values(os.networkInterfaces() || {})) {
|
|
202
|
+
for (const entry of entries || []) {
|
|
203
|
+
if (!entry || entry.internal || entry.family !== 'IPv4' || !entry.address) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
pushHost(entry.address);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return hosts;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function rewriteDashboardUrlHost(urlText, fallbackPort, targetBaseUrl) {
|
|
214
|
+
try {
|
|
215
|
+
const target = new URL(targetBaseUrl);
|
|
216
|
+
const parsed = new URL(urlText);
|
|
217
|
+
parsed.protocol = target.protocol;
|
|
218
|
+
parsed.hostname = target.hostname;
|
|
219
|
+
parsed.port = target.port || String(fallbackPort || '');
|
|
220
|
+
return parsed.toString();
|
|
221
|
+
} catch {
|
|
222
|
+
return `${targetBaseUrl}${String(urlText || '').startsWith('/') ? '' : '/'}${String(urlText || '')}`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
187
226
|
async function waitFor9RouterApiReady({ port = 20128, timeoutMs = 15000 } = {}) {
|
|
188
227
|
const deadline = Date.now() + timeoutMs;
|
|
189
228
|
const candidates = [
|
|
@@ -441,27 +480,45 @@ function getTokenizedDashboardUrl(projectDir) {
|
|
|
441
480
|
}
|
|
442
481
|
}
|
|
443
482
|
|
|
444
|
-
function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gatewayPort = 18791 }) {
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (
|
|
451
|
-
console.log(chalk.
|
|
452
|
-
? ` →
|
|
453
|
-
: ` →
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
483
|
+
function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gatewayPort = 18791 }) {
|
|
484
|
+
const gatewayUrls = getReachableDashboardHosts(gatewayPort);
|
|
485
|
+
const dashboardUrl = gatewayUrls[0] || `http://127.0.0.1:${gatewayPort}`;
|
|
486
|
+
const tokenizedUrl = getTokenizedDashboardUrl(projectDir);
|
|
487
|
+
|
|
488
|
+
console.log(chalk.yellow(`\n🧭 ${isVi ? 'Dashboard OpenClaw:' : 'OpenClaw Dashboard:'} ${dashboardUrl}`));
|
|
489
|
+
if (gatewayUrls.length > 1) {
|
|
490
|
+
console.log(chalk.gray(isVi
|
|
491
|
+
? ` → Link khac co the mo duoc: ${gatewayUrls.slice(1).join(' , ')}`
|
|
492
|
+
: ` → Other reachable URLs: ${gatewayUrls.slice(1).join(' , ')}`));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (tokenizedUrl) {
|
|
496
|
+
const tokenizedUrls = gatewayUrls.map((baseUrl) => rewriteDashboardUrlHost(tokenizedUrl, gatewayPort, baseUrl));
|
|
497
|
+
console.log(chalk.green(isVi
|
|
498
|
+
? ` → Mở link đã kèm token: ${tokenizedUrls[0]}`
|
|
499
|
+
: ` → Open the tokenized link directly: ${tokenizedUrls[0]}`));
|
|
500
|
+
if (tokenizedUrls.length > 1) {
|
|
501
|
+
console.log(chalk.gray(isVi
|
|
502
|
+
? ` → Ban mo tu may khac/WSL thi thu: ${tokenizedUrls.slice(1).join(' , ')}`
|
|
503
|
+
: ` → If you are opening from another machine/WSL, try: ${tokenizedUrls.slice(1).join(' , ')}`));
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
console.log(chalk.gray(isVi
|
|
507
|
+
? ' → Nếu dashboard đòi Gateway Token, chạy: openclaw dashboard'
|
|
508
|
+
: ' → If the dashboard asks for a Gateway Token, run: openclaw dashboard'));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (providerKey === '9router') {
|
|
512
|
+
const routerUrls = getReachableDashboardHosts(20128).map((baseUrl) => `${baseUrl}/dashboard`);
|
|
513
|
+
console.log(chalk.yellow(`\n🔀 ${isVi ? '9Router Dashboard:' : '9Router Dashboard:'} ${routerUrls[0] || 'http://127.0.0.1:20128/dashboard'}`));
|
|
514
|
+
if (routerUrls.length > 1) {
|
|
515
|
+
console.log(chalk.gray(isVi
|
|
516
|
+
? ` → Link khac co the mo duoc: ${routerUrls.slice(1).join(' , ')}`
|
|
517
|
+
: ` → Other reachable URLs: ${routerUrls.slice(1).join(' , ')}`));
|
|
518
|
+
}
|
|
519
|
+
console.log(chalk.gray(isVi
|
|
520
|
+
? ' → Mở dashboard 9Router → đăng nhập OAuth → kết nối provider miễn phí'
|
|
521
|
+
: ' → Open the 9Router dashboard → complete OAuth login → connect a free provider'));
|
|
465
522
|
console.log(chalk.gray(isVi
|
|
466
523
|
? ' → Sau khi login 9Router xong, bot sẽ tự dùng model smart-route qua http://localhost:20128/v1'
|
|
467
524
|
: ' → Once 9Router is logged in, the bot will use smart-route through http://localhost:20128/v1'));
|
|
@@ -1282,7 +1339,7 @@ async function main() {
|
|
|
1282
1339
|
}
|
|
1283
1340
|
|
|
1284
1341
|
|
|
1285
|
-
const patchScript = `const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\`http://\${entry.address}:18791\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
|
|
1342
|
+
const patchScript = `const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\`http://\${entry.address}:18791\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a),requireDeviceIdentity:false})});fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
|
|
1286
1343
|
const b64Patch = Buffer.from(patchScript).toString('base64');
|
|
1287
1344
|
|
|
1288
1345
|
// Browser Playwright (both desktop & server modes need chromium)
|
|
@@ -1748,6 +1805,7 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1748
1805
|
customBindHost: '0.0.0.0',
|
|
1749
1806
|
controlUi: {
|
|
1750
1807
|
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
1808
|
+
requireDeviceIdentity: false,
|
|
1751
1809
|
},
|
|
1752
1810
|
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' },
|
|
1753
1811
|
},
|
|
@@ -1979,7 +2037,7 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1979
2037
|
tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
|
|
1980
2038
|
gateway: {
|
|
1981
2039
|
port: 18791 + (isMultiBot ? bIndex : 0), mode: 'local', bind: 'custom', customBindHost: '0.0.0.0',
|
|
1982
|
-
controlUi: { allowedOrigins: getGatewayAllowedOrigins(18791 + (isMultiBot ? bIndex : 0)) },
|
|
2040
|
+
controlUi: { allowedOrigins: getGatewayAllowedOrigins(18791 + (isMultiBot ? bIndex : 0)), requireDeviceIdentity: false },
|
|
1983
2041
|
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' }
|
|
1984
2042
|
}
|
|
1985
2043
|
};
|
|
@@ -2233,13 +2291,14 @@ fi
|
|
|
2233
2291
|
if (code === 0) {
|
|
2234
2292
|
console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoàn tất! Bot đang chạy.' : 'Setup complete! Bot is running.'}`));
|
|
2235
2293
|
|
|
2236
|
-
if (providerKey === '9router') {
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2294
|
+
if (providerKey === '9router') {
|
|
2295
|
+
const routerDashboardUrl = `${getReachableDashboardHosts(20128)[0] || 'http://127.0.0.1:20128'}/dashboard`;
|
|
2296
|
+
console.log(chalk.yellow(`\n🔀 ${isVi
|
|
2297
|
+
? `9Router Dashboard: ${routerDashboardUrl}`
|
|
2298
|
+
: `9Router Dashboard: ${routerDashboardUrl}`}`));
|
|
2299
|
+
console.log(chalk.gray(isVi
|
|
2300
|
+
? ' → Mở dashboard → đăng nhập OAuth để kết nối các Provider (iFlow, Gemini CLI, Claude Code...)'
|
|
2301
|
+
: ' → Open dashboard → OAuth login to connect Providers (iFlow, Gemini CLI, Claude Code...)'));
|
|
2243
2302
|
console.log(chalk.gray(isVi
|
|
2244
2303
|
? ' → Sau khi kết nối provider, bot sẽ tự động hoạt động qua combo "smart-route"'
|
|
2245
2304
|
: ' → After connecting providers, bot works automatically via "smart-route" combo'));
|
package/package.json
CHANGED
package/setup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/* ============================================
|
|
2
2
|
OpenClaw Setup Wizard — Logic v2
|
|
3
3
|
Multi-model, Multi-plugin, Multi-channel
|
|
4
4
|
============================================ */
|
|
@@ -1597,6 +1597,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
1597
1597
|
bind: '0.0.0.0',
|
|
1598
1598
|
controlUi: {
|
|
1599
1599
|
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
1600
|
+
requireDeviceIdentity: false,
|
|
1600
1601
|
},
|
|
1601
1602
|
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
1602
1603
|
},
|
|
@@ -1832,7 +1833,7 @@ model:
|
|
|
1832
1833
|
? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 & '
|
|
1833
1834
|
: '';
|
|
1834
1835
|
// Patch config on every startup to keep gateway settings stable
|
|
1835
|
-
const patchCmd = `node -e \\"const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\\\`http://\\\${entry.address}:18791\\\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
|
|
1836
|
+
const patchCmd = `node -e \\"const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\\\`http://\\\${entry.address}:18791\\\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a),requireDeviceIdentity:false})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
|
|
1836
1837
|
// Auto-approve device pairing after gateway starts (required since v2026.3.x)
|
|
1837
1838
|
const autoApproveCmd = '(while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done) & ';
|
|
1838
1839
|
const finalCmd = `CMD sh -c "${pluginInstallCmd}${patchCmd}${browserPrefix}${autoApproveCmd}${gatewayCmd}"`;
|
|
@@ -3059,6 +3060,7 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
3059
3060
|
bind: '0.0.0.0',
|
|
3060
3061
|
controlUi: {
|
|
3061
3062
|
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
3063
|
+
requireDeviceIdentity: false,
|
|
3062
3064
|
},
|
|
3063
3065
|
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3064
3066
|
},
|
|
@@ -3175,6 +3177,7 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
3175
3177
|
bind: '0.0.0.0',
|
|
3176
3178
|
controlUi: {
|
|
3177
3179
|
allowedOrigins: getGatewayAllowedOrigins(basePort),
|
|
3180
|
+
requireDeviceIdentity: false,
|
|
3178
3181
|
},
|
|
3179
3182
|
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3180
3183
|
},
|
|
@@ -116,6 +116,14 @@ checks.push(() => expect(
|
|
|
116
116
|
'CLI must derive control UI allowed origins from localhost plus non-internal IPv4 interfaces'
|
|
117
117
|
));
|
|
118
118
|
|
|
119
|
+
checks.push(() => expect(
|
|
120
|
+
cli.includes('function getReachableDashboardHosts(port) {')
|
|
121
|
+
&& cli.includes('pushHost(\'127.0.0.1\')')
|
|
122
|
+
&& cli.includes('pushHost(\'localhost\')')
|
|
123
|
+
&& cli.includes('function rewriteDashboardUrlHost(urlText, fallbackPort, targetBaseUrl) {'),
|
|
124
|
+
'CLI must derive reachable dashboard hosts and rewrite tokenized dashboard URLs for WSL/LAN access'
|
|
125
|
+
));
|
|
126
|
+
|
|
119
127
|
checks.push(() => expect(
|
|
120
128
|
cli.includes("Removed smart-route (no active providers)")
|
|
121
129
|
&& cli.includes("if (!a.length) {")
|
|
@@ -144,7 +152,7 @@ checks.push(() => expectMatch(
|
|
|
144
152
|
|
|
145
153
|
checks.push(() => expectMatch(
|
|
146
154
|
cli,
|
|
147
|
-
/function printNativeDashboardAccessInfo\(\{ isVi, providerKey, projectDir, gatewayPort = 18791 \}\) \{[\s\S]*
|
|
155
|
+
/function printNativeDashboardAccessInfo\(\{ isVi, providerKey, projectDir, gatewayPort = 18791 \}\) \{[\s\S]*getReachableDashboardHosts\(gatewayPort\)[\s\S]*rewriteDashboardUrlHost[\s\S]*Other reachable URLs[\s\S]*getReachableDashboardHosts\(20128\)/s,
|
|
148
156
|
'Native PM2 flow must expose dashboard access info and the tokenized dashboard command'
|
|
149
157
|
));
|
|
150
158
|
|
package/tmp_diff.patch
DELETED
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
diff --git a/cli.js b/cli.js
|
|
2
|
-
index 4b32aaf..4323e60 100644
|
|
3
|
-
--- a/cli.js
|
|
4
|
-
+++ b/cli.js
|
|
5
|
-
@@ -146,34 +146,13 @@ function spawnBackgroundProcess(command, args, options = {}) {
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function resolveNative9RouterDesktopLaunch() {
|
|
9
|
-
- if (process.platform === 'win32') {
|
|
10
|
-
- const npmRoot = (() => {
|
|
11
|
-
- try {
|
|
12
|
-
- return execSync('npm root -g', {
|
|
13
|
-
- stdio: ['ignore', 'pipe', 'ignore'],
|
|
14
|
-
- encoding: 'utf8',
|
|
15
|
-
- shell: true,
|
|
16
|
-
- env: process.env
|
|
17
|
-
- }).trim();
|
|
18
|
-
- } catch {
|
|
19
|
-
- return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
|
|
20
|
-
- }
|
|
21
|
-
- })();
|
|
22
|
-
-
|
|
23
|
-
- return {
|
|
24
|
-
- command: process.execPath,
|
|
25
|
-
- args: [path.join(npmRoot, '9router', 'app', 'server.js')],
|
|
26
|
-
- env: {
|
|
27
|
-
- PORT: '20128',
|
|
28
|
-
- HOSTNAME: '0.0.0.0'
|
|
29
|
-
- }
|
|
30
|
-
- };
|
|
31
|
-
- }
|
|
32
|
-
-
|
|
33
|
-
return {
|
|
34
|
-
- command: '9router',
|
|
35
|
-
- args: ['-n', '-l', '-H', '0.0.0.0', '-p', '20128', '--skip-update'],
|
|
36
|
-
- env: {}
|
|
37
|
-
+ command: process.execPath,
|
|
38
|
-
+ args: [path.join(getGlobalNpmRoot(), '9router', 'app', 'server.js')],
|
|
39
|
-
+ env: {
|
|
40
|
-
+ PORT: '20128',
|
|
41
|
-
+ HOSTNAME: '0.0.0.0'
|
|
42
|
-
+ }
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@@ -185,6 +164,26 @@ function getNative9RouterDataDir() {
|
|
47
|
-
return path.join(os.homedir(), '.9router');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
+function getGatewayAllowedOrigins(port) {
|
|
51
|
-
+ const normalizedPort = Number(port) || 18791;
|
|
52
|
-
+ const origins = new Set([
|
|
53
|
-
+ `http://localhost:${normalizedPort}`,
|
|
54
|
-
+ `http://127.0.0.1:${normalizedPort}`,
|
|
55
|
-
+ `http://0.0.0.0:${normalizedPort}`
|
|
56
|
-
+ ]);
|
|
57
|
-
+
|
|
58
|
-
+ for (const entries of Object.values(os.networkInterfaces() || {})) {
|
|
59
|
-
+ for (const entry of entries || []) {
|
|
60
|
-
+ if (!entry || entry.internal || entry.family !== 'IPv4' || !entry.address) {
|
|
61
|
-
+ continue;
|
|
62
|
-
+ }
|
|
63
|
-
+ origins.add(`http://${entry.address}:${normalizedPort}`);
|
|
64
|
-
+ }
|
|
65
|
-
+ }
|
|
66
|
-
+
|
|
67
|
-
+ return Array.from(origins);
|
|
68
|
-
+}
|
|
69
|
-
+
|
|
70
|
-
async function waitFor9RouterApiReady({ port = 20128, timeoutMs = 15000 } = {}) {
|
|
71
|
-
const deadline = Date.now() + timeoutMs;
|
|
72
|
-
const candidates = [
|
|
73
|
-
@@ -380,6 +379,22 @@ function resolveCommandOnPath(command) {
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
+function getGlobalNpmRoot() {
|
|
78
|
-
+ try {
|
|
79
|
-
+ return execSync('npm root -g', {
|
|
80
|
-
+ stdio: ['ignore', 'pipe', 'ignore'],
|
|
81
|
-
+ encoding: 'utf8',
|
|
82
|
-
+ shell: true,
|
|
83
|
-
+ env: process.env
|
|
84
|
-
+ }).trim();
|
|
85
|
-
+ } catch {
|
|
86
|
-
+ if (process.platform === 'win32') {
|
|
87
|
-
+ return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
|
|
88
|
-
+ }
|
|
89
|
-
+ return path.join(os.homedir(), '.local', 'lib', 'node_modules');
|
|
90
|
-
+ }
|
|
91
|
-
+}
|
|
92
|
-
+
|
|
93
|
-
function indentBlock(text, spaces) {
|
|
94
|
-
const prefix = ' '.repeat(spaces);
|
|
95
|
-
return String(text)
|
|
96
|
-
@@ -625,10 +640,10 @@ function runPm2Save({ projectDir, isVi }) {
|
|
97
|
-
|
|
98
|
-
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
99
|
-
const routerAppName = `${appName}-9router`;
|
|
100
|
-
- const routerCommand = resolveCommandOnPath('9router');
|
|
101
|
-
+ const routerLaunch = resolveNative9RouterDesktopLaunch();
|
|
102
|
-
execFileSync('pm2', [
|
|
103
|
-
'start',
|
|
104
|
-
- routerCommand,
|
|
105
|
-
+ routerLaunch.command,
|
|
106
|
-
'--name',
|
|
107
|
-
routerAppName,
|
|
108
|
-
'--cwd',
|
|
109
|
-
@@ -636,17 +651,11 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
110
|
-
'--interpreter',
|
|
111
|
-
'none',
|
|
112
|
-
'--',
|
|
113
|
-
- '-n',
|
|
114
|
-
- '-l',
|
|
115
|
-
- '-H',
|
|
116
|
-
- '0.0.0.0',
|
|
117
|
-
- '-p',
|
|
118
|
-
- '20128',
|
|
119
|
-
- '--skip-update'
|
|
120
|
-
+ ...routerLaunch.args
|
|
121
|
-
], {
|
|
122
|
-
cwd: projectDir,
|
|
123
|
-
stdio: 'inherit',
|
|
124
|
-
- env: process.env
|
|
125
|
-
+ env: { ...process.env, ...routerLaunch.env }
|
|
126
|
-
});
|
|
127
|
-
if (syncScriptPath) {
|
|
128
|
-
const syncAppName = `${appName}-9router-sync`;
|
|
129
|
-
@@ -1273,7 +1282,7 @@ async function main() {
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- const patchScript = `const fs=require('fs'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0'});fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
|
|
134
|
-
+ const patchScript = `const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\`http://\${entry.address}:18791\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
|
|
135
|
-
const b64Patch = Buffer.from(patchScript).toString('base64');
|
|
136
|
-
|
|
137
|
-
// Browser Playwright (both desktop & server modes need chromium)
|
|
138
|
-
@@ -1737,6 +1746,9 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
139
|
-
mode: 'local',
|
|
140
|
-
bind: 'custom',
|
|
141
|
-
customBindHost: '0.0.0.0',
|
|
142
|
-
+ controlUi: {
|
|
143
|
-
+ allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
144
|
-
+ },
|
|
145
|
-
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' },
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
@@ -1967,6 +1979,7 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
149
|
-
tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
|
|
150
|
-
gateway: {
|
|
151
|
-
port: 18791 + (isMultiBot ? bIndex : 0), mode: 'local', bind: 'custom', customBindHost: '0.0.0.0',
|
|
152
|
-
+ controlUi: { allowedOrigins: getGatewayAllowedOrigins(18791 + (isMultiBot ? bIndex : 0)) },
|
|
153
|
-
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' }
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
diff --git a/setup.js b/setup.js
|
|
157
|
-
index 885a34a..2a33996 100644
|
|
158
|
-
--- a/setup.js
|
|
159
|
-
+++ b/setup.js
|
|
160
|
-
@@ -49,6 +49,20 @@
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
+ function getGatewayAllowedOrigins(port) {
|
|
165
|
-
+ const normalizedPort = Number(port) || 18791;
|
|
166
|
-
+ const origins = new Set([
|
|
167
|
-
+ `http://localhost:${normalizedPort}`,
|
|
168
|
-
+ `http://127.0.0.1:${normalizedPort}`,
|
|
169
|
-
+ `http://0.0.0.0:${normalizedPort}`,
|
|
170
|
-
+ ]);
|
|
171
|
-
+ const currentHost = (window.location && window.location.hostname) ? window.location.hostname.trim() : '';
|
|
172
|
-
+ if (currentHost) {
|
|
173
|
-
+ origins.add(`http://${currentHost}:${normalizedPort}`);
|
|
174
|
-
+ }
|
|
175
|
-
+ return Array.from(origins);
|
|
176
|
-
+ }
|
|
177
|
-
+
|
|
178
|
-
|
|
179
|
-
// ========== AI Providers & Models ==========
|
|
180
|
-
const PROVIDERS = {
|
|
181
|
-
@@ -1581,6 +1595,9 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
182
|
-
port: 18791,
|
|
183
|
-
mode: 'local',
|
|
184
|
-
bind: '0.0.0.0',
|
|
185
|
-
+ controlUi: {
|
|
186
|
-
+ allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
187
|
-
+ },
|
|
188
|
-
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
@@ -1815,7 +1832,7 @@ model:
|
|
192
|
-
? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 & '
|
|
193
|
-
: '';
|
|
194
|
-
// Patch config on every startup to keep gateway settings stable
|
|
195
|
-
- const patchCmd = `node -e \\"const fs=require('fs'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0'});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
|
|
196
|
-
+ const patchCmd = `node -e \\"const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\\\`http://\\\${entry.address}:18791\\\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
|
|
197
|
-
// Auto-approve device pairing after gateway starts (required since v2026.3.x)
|
|
198
|
-
const autoApproveCmd = '(while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done) & ';
|
|
199
|
-
const finalCmd = `CMD sh -c "${pluginInstallCmd}${patchCmd}${browserPrefix}${autoApproveCmd}${gatewayCmd}"`;
|
|
200
|
-
@@ -2875,7 +2892,7 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
201
|
-
arr.push('timeout /t 5 /nobreak >nul');
|
|
202
|
-
} else {
|
|
203
|
-
arr.push('npm install -g 9router');
|
|
204
|
-
- arr.push('nohup 9router -n -l -H 0.0.0.0 -p 20128 --skip-update >/tmp/9router.log 2>&1 &');
|
|
205
|
-
+ arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 node "$(npm root -g)/9router/app/server.js" >/tmp/9router.log 2>&1 &');
|
|
206
|
-
arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
|
|
207
|
-
arr.push('sleep 3');
|
|
208
|
-
}
|
|
209
|
-
@@ -3040,6 +3057,9 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
210
|
-
port: 18791,
|
|
211
|
-
mode: 'local',
|
|
212
|
-
bind: '0.0.0.0',
|
|
213
|
-
+ controlUi: {
|
|
214
|
-
+ allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
215
|
-
+ },
|
|
216
|
-
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
217
|
-
},
|
|
218
|
-
};
|
|
219
|
-
@@ -3153,6 +3173,9 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
220
|
-
port: basePort,
|
|
221
|
-
mode: 'local',
|
|
222
|
-
bind: '0.0.0.0',
|
|
223
|
-
+ controlUi: {
|
|
224
|
-
+ allowedOrigins: getGatewayAllowedOrigins(basePort),
|
|
225
|
-
+ },
|
|
226
|
-
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
@@ -3536,7 +3559,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
230
|
-
appendShWriteCommands(vps, sharedNativeFileMap());
|
|
231
|
-
vps.push('echo "--- Starting shared gateway via PM2 ---"');
|
|
232
|
-
if (is9Router) {
|
|
233
|
-
- vps.push('pm2 start --name openclaw-multibot-9router -- sh -c "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
234
|
-
+ vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-multibot-9router --interpreter "$(command -v node)"');
|
|
235
|
-
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
236
|
-
}
|
|
237
|
-
vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
|
|
238
|
-
@@ -3549,7 +3572,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
239
|
-
} else {
|
|
240
|
-
appendShWriteCommands(vps, botFiles(0));
|
|
241
|
-
if (is9Router) {
|
|
242
|
-
- vps.push('pm2 start --name openclaw-9router -- sh -c "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
243
|
-
+ vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-9router --interpreter "$(command -v node)"');
|
|
244
|
-
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
245
|
-
}
|
|
246
|
-
vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
|
|
247
|
-
diff --git a/tests/smoke-cli-logic.mjs b/tests/smoke-cli-logic.mjs
|
|
248
|
-
index 23c1982..0827726 100644
|
|
249
|
-
--- a/tests/smoke-cli-logic.mjs
|
|
250
|
-
+++ b/tests/smoke-cli-logic.mjs
|
|
251
|
-
@@ -107,6 +107,15 @@ checks.push(() => expectMatch(
|
|
252
|
-
'CLI must resolve the correct native 9Router data directory on both Windows and Unix'
|
|
253
|
-
));
|
|
254
|
-
|
|
255
|
-
+checks.push(() => expect(
|
|
256
|
-
+ cli.includes('function getGatewayAllowedOrigins(port) {')
|
|
257
|
-
+ && cli.includes('Object.values(os.networkInterfaces() || {})')
|
|
258
|
-
+ && cli.includes('`http://localhost:${normalizedPort}`')
|
|
259
|
-
+ && cli.includes('`http://127.0.0.1:${normalizedPort}`')
|
|
260
|
-
+ && cli.includes('`http://0.0.0.0:${normalizedPort}`'),
|
|
261
|
-
+ 'CLI must derive control UI allowed origins from localhost plus non-internal IPv4 interfaces'
|
|
262
|
-
+));
|
|
263
|
-
+
|
|
264
|
-
checks.push(() => expect(
|
|
265
|
-
cli.includes("Removed smart-route (no active providers)")
|
|
266
|
-
&& cli.includes("if (!a.length) {")
|
|
267
|
-
@@ -187,6 +196,18 @@ checks.push(() => expectMatch(
|
|
268
|
-
'Native 9Router config must target localhost instead of the Docker hostname'
|
|
269
|
-
));
|
|
270
|
-
|
|
271
|
-
+checks.push(() => expectMatch(
|
|
272
|
-
+ cli,
|
|
273
|
-
+ /controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791\)/s,
|
|
274
|
-
+ 'Native shared gateway config must seed control UI allowed origins'
|
|
275
|
-
+));
|
|
276
|
-
+
|
|
277
|
-
+checks.push(() => expectMatch(
|
|
278
|
-
+ cli,
|
|
279
|
-
+ /controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791 \+ \(isMultiBot \? bIndex : 0\)\)/s,
|
|
280
|
-
+ 'Native per-bot gateway config must seed control UI allowed origins for each port'
|
|
281
|
-
+));
|
|
282
|
-
+
|
|
283
|
-
checks.push(() => expectMatch(
|
|
284
|
-
cli,
|
|
285
|
-
/channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'pairing',\s*autoReply: true/s,
|
|
286
|
-
@@ -195,7 +216,7 @@ checks.push(() => expectMatch(
|
|
287
|
-
|
|
288
|
-
checks.push(() => expectMatch(
|
|
289
|
-
cli,
|
|
290
|
-
- /function startNative9RouterPm2\(\{ isVi, projectDir, appName, syncScriptPath \}\) \{[\s\S]*resolveCommandOnPath\('9router'\)[\s\S]*execFileSync\('pm2'[\s\S]*--interpreter'?,?[\s\S]*none[\s\S]*'-n'[\s\S]*'-l'[\s\S]*'--skip-update'[\s\S]*nohup "\$\{process\.execPath\}" "\$\{normalizedSyncScriptPath\}" >\/tmp\/\$\{syncAppName\}\.log 2>&1 &[\s\S]*runPm2Save\(\{ projectDir, isVi \}\)/s,
|
|
291
|
-
+ /function startNative9RouterPm2\(\{ isVi, projectDir, appName, syncScriptPath \}\) \{[\s\S]*resolveNative9RouterDesktopLaunch\(\)[\s\S]*execFileSync\('pm2'[\s\S]*routerLaunch\.command[\s\S]*--interpreter'?,?[\s\S]*none[\s\S]*routerLaunch\.args[\s\S]*routerLaunch\.env[\s\S]*nohup "\$\{process\.execPath\}" "\$\{normalizedSyncScriptPath\}" >\/tmp\/\$\{syncAppName\}\.log 2>&1 &[\s\S]*runPm2Save\(\{ projectDir, isVi \}\)/s,
|
|
292
|
-
'VPS native 9Router flow must start a standalone 9Router dashboard on port 20128 via PM2'
|
|
293
|
-
));
|
|
294
|
-
|
|
295
|
-
@@ -207,8 +228,8 @@ checks.push(() => expectMatch(
|
|
296
|
-
|
|
297
|
-
checks.push(() => expectMatch(
|
|
298
|
-
cli,
|
|
299
|
-
- /function resolveNative9RouterDesktopLaunch\(\) \{[\s\S]*process\.platform === 'win32'[\s\S]*npm root -g[\s\S]*9router', 'app', 'server\.js'[\s\S]*PORT: '20128'[\s\S]*HOSTNAME: '0\.0\.0\.0'[\s\S]*command: '9router'[\s\S]*\['-n', '-l', '-H', '0\.0\.0\.0', '-p', '20128', '--skip-update'\]/s,
|
|
300
|
-
- 'Native desktop 9Router launch must bypass the interactive CLI menu on Windows while preserving the standard CLI launch elsewhere'
|
|
301
|
-
+ /function resolveNative9RouterDesktopLaunch\(\) \{[\s\S]*command: process\.execPath[\s\S]*getGlobalNpmRoot\(\), '9router', 'app', 'server\.js'[\s\S]*PORT: '20128'[\s\S]*HOSTNAME: '0\.0\.0\.0'/s,
|
|
302
|
-
+ 'Native desktop 9Router launch must bypass the interactive CLI menu by running the 9Router server entry directly'
|
|
303
|
-
));
|
|
304
|
-
|
|
305
|
-
checks.push(() => expectMatch(
|
|
306
|
-
@@ -292,7 +313,7 @@ checks.push(() => expectMatch(
|
|
307
|
-
|
|
308
|
-
checks.push(() => expectMatch(
|
|
309
|
-
setup,
|
|
310
|
-
- /function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*start "9Router" cmd \/k "9router -n -l -H 0\.0\.0\.0 -p 20128 --skip-update"[\s\S]*nohup 9router -n -l -H 0\.0\.0\.0 -p 20128 --skip-update[\s\S]*9router-smart-route-sync\.js/s,
|
|
311
|
-
+ /function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*start "9Router" cmd \/k "9router -n -l -H 0\.0\.0\.0 -p 20128 --skip-update"[\s\S]*nohup env PORT=20128 HOSTNAME=0\.0\.0\.0 node "\$\(npm root -g\)\/9router\/app\/server\.js"[\s\S]*9router-smart-route-sync\.js/s,
|
|
312
|
-
'Native script generation must install and start a standalone 9Router dashboard on port 20128'
|
|
313
|
-
));
|
|
314
|
-
|
|
315
|
-
@@ -340,13 +361,13 @@ checks.push(() => expect(
|
|
316
|
-
|
|
317
|
-
checks.push(() => expectMatch(
|
|
318
|
-
setup,
|
|
319
|
-
- /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw-multibot/s,
|
|
320
|
-
+ /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*PORT=20128 HOSTNAME=0\.0\.0\.0 pm2 start "\$\(npm root -g\)\/9router\/app\/server\.js" --name openclaw-multibot-9router --interpreter "\$\(command -v node\)"[\s\S]*pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw-multibot/s,
|
|
321
|
-
'VPS multi-bot native script must start the shared gateway via PM2'
|
|
322
|
-
));
|
|
323
|
-
|
|
324
|
-
checks.push(() => expectMatch(
|
|
325
|
-
setup,
|
|
326
|
-
- /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*pm2 start --name openclaw -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw/s,
|
|
327
|
-
+ /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*PORT=20128 HOSTNAME=0\.0\.0\.0 pm2 start "\$\(npm root -g\)\/9router\/app\/server\.js" --name openclaw-9router --interpreter "\$\(command -v node\)"[\s\S]*pm2 start --name openclaw -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw/s,
|
|
328
|
-
'VPS single-bot native script must start one bot via PM2'
|
|
329
|
-
));
|
|
330
|
-
|
|
331
|
-
@@ -374,6 +395,32 @@ checks.push(() => expectMatch(
|
|
332
|
-
'Wizard copy must mention native auto-login and still show the dedicated Docker QR login command'
|
|
333
|
-
));
|
|
334
|
-
|
|
335
|
-
+checks.push(() => expect(
|
|
336
|
-
+ setup.includes('function getGatewayAllowedOrigins(port) {')
|
|
337
|
-
+ && setup.includes('window.location')
|
|
338
|
-
+ && setup.includes('`http://localhost:${normalizedPort}`')
|
|
339
|
-
+ && setup.includes('`http://127.0.0.1:${normalizedPort}`'),
|
|
340
|
-
+ 'Web wizard must expose a helper that seeds likely control UI origins'
|
|
341
|
-
+));
|
|
342
|
-
+
|
|
343
|
-
+checks.push(() => expectMatch(
|
|
344
|
-
+ setup,
|
|
345
|
-
+ /controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791\)/s,
|
|
346
|
-
+ 'Web wizard single-bot gateway config must seed control UI allowed origins'
|
|
347
|
-
+));
|
|
348
|
-
+
|
|
349
|
-
+checks.push(() => expectMatch(
|
|
350
|
-
+ setup,
|
|
351
|
-
+ /controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(basePort\)/s,
|
|
352
|
-
+ 'Web wizard per-bot gateway config must seed control UI allowed origins'
|
|
353
|
-
+));
|
|
354
|
-
+
|
|
355
|
-
+checks.push(() => expectMatch(
|
|
356
|
-
+ setup,
|
|
357
|
-
+ /const patchCmd = `node -e \\\\"const fs=require\('fs'\),os=require\('os'\),p='\/root\/\.openclaw\/openclaw\.json';if\(fs\.existsSync\(p\)\)\{[\s\S]*allowedOrigins:Array\.from\(a\)/s,
|
|
358
|
-
+ 'Web wizard Docker patch command must add interface-based control UI allowed origins'
|
|
359
|
-
+));
|
|
360
|
-
+
|
|
361
|
-
for (const check of checks) {
|
|
362
|
-
check();
|
|
363
|
-
}
|