create-openclaw-bot 5.1.6 → 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 +16 -0
- package/CHANGELOG.vi.md +16 -0
- package/README.md +3 -3
- package/README.vi.md +3 -3
- package/cli.js +161 -89
- package/package.json +1 -1
- package/setup.js +70 -44
- package/tests/smoke-cli-logic.mjs +62 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
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
|
+
|
|
12
|
+
## [5.1.7] — 2026-04-07
|
|
13
|
+
|
|
14
|
+
### 🌟 Fix Control UI CORS & Native 9Router Path Resolution
|
|
15
|
+
|
|
16
|
+
- **Fix Control UI CORS Rejections**: OpenClaw v2026.3.x strict CORS policies blocked remote dashboard access. The setup configuration and Docker patching scripts now automatically resolve all active IPv4 interfaces (`os.networkInterfaces()`) alongside localhost to pre-populate the `gateway.controlUi.allowedOrigins` array. This ensures the Web UI works flawlessly out-of-the-box on remote VPS instances.
|
|
17
|
+
- **Improved Native PM2 Path Resolution**: To prevent PM2 `$PATH` lookup failures with `nvm` on Linux, the script now bypasses the OS `9router` binary wrapper entirely. Instead, it computes the exact explicit path using `$(npm root -g)/9router/app/server.js` and executes it directly via the NodeJS interpreter.
|
|
18
|
+
|
|
19
|
+
|
|
4
20
|
## [5.1.6] — 2026-04-07
|
|
5
21
|
|
|
6
22
|
### 🐞 Fix PM2 SIGKILL on Native VPS Installs
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
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
|
+
|
|
12
|
+
## [5.1.7] — 2026-04-07
|
|
13
|
+
|
|
14
|
+
### 🌟 Sửa lỗi CORS Control UI & Đường dẫn 9Router Native
|
|
15
|
+
|
|
16
|
+
- **Sửa lỗi dội ngược CORS khi vào Control UI**: OpenClaw v2026.3.x siết chặt policy CORS khiến việc truy cập dashboard từ IP ngoài bị block. Các script tạo config và vá Docker giờ đã tự động quét toàn bộ IPv4 hiện có của server (`os.networkInterfaces()`) để nhúng vào mảng `gateway.controlUi.allowedOrigins`. Đảm bảo người dùng VPS vào được thẳng Control UI mà không bị lỗi mạng.
|
|
17
|
+
- **Tối ưu đường dẫn PM2 Native**: Để tránh trường hợp tính năng PM2 không nhận diện đúng môi trường (lỗi `\$PATH` khi dùng `nvm`), bộ cài giờ bỏ qua file thực thi `9router` của HĐH. Thay vào đó, bộ cài tự tính toán đường dẫn tuyệt đối `\$(npm root -g)/9router/app/server.js` và truyền thẳng vào trình thông dịch Node, đảm bảo PM2 100% tìm thấy file khởi chạy 9Router.
|
|
18
|
+
|
|
19
|
+
|
|
4
20
|
## [5.1.6] — 2026-04-07
|
|
5
21
|
|
|
6
22
|
### 🐞 Khắc phục lỗi PM2 ngắt cài đặt (SIGKILL) trên VPS
|
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
|
@@ -146,49 +146,87 @@ function spawnBackgroundProcess(command, args, options = {}) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
function resolveNative9RouterDesktopLaunch() {
|
|
149
|
-
if (process.platform === 'win32') {
|
|
150
|
-
const npmRoot = (() => {
|
|
151
|
-
try {
|
|
152
|
-
return execSync('npm root -g', {
|
|
153
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
154
|
-
encoding: 'utf8',
|
|
155
|
-
shell: true,
|
|
156
|
-
env: process.env
|
|
157
|
-
}).trim();
|
|
158
|
-
} catch {
|
|
159
|
-
return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
|
|
160
|
-
}
|
|
161
|
-
})();
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
command: process.execPath,
|
|
165
|
-
args: [path.join(npmRoot, '9router', 'app', 'server.js')],
|
|
166
|
-
env: {
|
|
167
|
-
PORT: '20128',
|
|
168
|
-
HOSTNAME: '0.0.0.0'
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
149
|
return {
|
|
174
|
-
command:
|
|
175
|
-
args: [
|
|
176
|
-
env: {
|
|
150
|
+
command: process.execPath,
|
|
151
|
+
args: [path.join(getGlobalNpmRoot(), '9router', 'app', 'server.js')],
|
|
152
|
+
env: {
|
|
153
|
+
PORT: '20128',
|
|
154
|
+
HOSTNAME: '0.0.0.0'
|
|
155
|
+
}
|
|
177
156
|
};
|
|
178
157
|
}
|
|
179
158
|
|
|
180
|
-
function getNative9RouterDataDir() {
|
|
181
|
-
if (process.platform === 'win32') {
|
|
182
|
-
return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), '9router');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return path.join(os.homedir(), '.9router');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
`http://
|
|
159
|
+
function getNative9RouterDataDir() {
|
|
160
|
+
if (process.platform === 'win32') {
|
|
161
|
+
return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), '9router');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return path.join(os.homedir(), '.9router');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getGatewayAllowedOrigins(port) {
|
|
168
|
+
const normalizedPort = Number(port) || 18791;
|
|
169
|
+
const origins = new Set([
|
|
170
|
+
`http://localhost:${normalizedPort}`,
|
|
171
|
+
`http://127.0.0.1:${normalizedPort}`,
|
|
172
|
+
`http://0.0.0.0:${normalizedPort}`
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
for (const entries of Object.values(os.networkInterfaces() || {})) {
|
|
176
|
+
for (const entry of entries || []) {
|
|
177
|
+
if (!entry || entry.internal || entry.family !== 'IPv4' || !entry.address) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
origins.add(`http://${entry.address}:${normalizedPort}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return Array.from(origins);
|
|
185
|
+
}
|
|
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
|
+
|
|
226
|
+
async function waitFor9RouterApiReady({ port = 20128, timeoutMs = 15000 } = {}) {
|
|
227
|
+
const deadline = Date.now() + timeoutMs;
|
|
228
|
+
const candidates = [
|
|
229
|
+
`http://127.0.0.1:${port}/api/settings/require-login`,
|
|
192
230
|
`http://127.0.0.1:${port}/api/version`
|
|
193
231
|
];
|
|
194
232
|
|
|
@@ -380,6 +418,22 @@ function resolveCommandOnPath(command) {
|
|
|
380
418
|
}
|
|
381
419
|
}
|
|
382
420
|
|
|
421
|
+
function getGlobalNpmRoot() {
|
|
422
|
+
try {
|
|
423
|
+
return execSync('npm root -g', {
|
|
424
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
425
|
+
encoding: 'utf8',
|
|
426
|
+
shell: true,
|
|
427
|
+
env: process.env
|
|
428
|
+
}).trim();
|
|
429
|
+
} catch {
|
|
430
|
+
if (process.platform === 'win32') {
|
|
431
|
+
return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
|
|
432
|
+
}
|
|
433
|
+
return path.join(os.homedir(), '.local', 'lib', 'node_modules');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
383
437
|
function indentBlock(text, spaces) {
|
|
384
438
|
const prefix = ' '.repeat(spaces);
|
|
385
439
|
return String(text)
|
|
@@ -426,27 +480,45 @@ function getTokenizedDashboardUrl(projectDir) {
|
|
|
426
480
|
}
|
|
427
481
|
}
|
|
428
482
|
|
|
429
|
-
function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gatewayPort = 18791 }) {
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (
|
|
436
|
-
console.log(chalk.
|
|
437
|
-
? ` →
|
|
438
|
-
: ` →
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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'));
|
|
450
522
|
console.log(chalk.gray(isVi
|
|
451
523
|
? ' → Sau khi login 9Router xong, bot sẽ tự dùng model smart-route qua http://localhost:20128/v1'
|
|
452
524
|
: ' → Once 9Router is logged in, the bot will use smart-route through http://localhost:20128/v1'));
|
|
@@ -625,10 +697,10 @@ function runPm2Save({ projectDir, isVi }) {
|
|
|
625
697
|
|
|
626
698
|
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
627
699
|
const routerAppName = `${appName}-9router`;
|
|
628
|
-
const
|
|
700
|
+
const routerLaunch = resolveNative9RouterDesktopLaunch();
|
|
629
701
|
execFileSync('pm2', [
|
|
630
702
|
'start',
|
|
631
|
-
|
|
703
|
+
routerLaunch.command,
|
|
632
704
|
'--name',
|
|
633
705
|
routerAppName,
|
|
634
706
|
'--cwd',
|
|
@@ -636,17 +708,11 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
|
636
708
|
'--interpreter',
|
|
637
709
|
'none',
|
|
638
710
|
'--',
|
|
639
|
-
|
|
640
|
-
'-l',
|
|
641
|
-
'-H',
|
|
642
|
-
'0.0.0.0',
|
|
643
|
-
'-p',
|
|
644
|
-
'20128',
|
|
645
|
-
'--skip-update'
|
|
711
|
+
...routerLaunch.args
|
|
646
712
|
], {
|
|
647
713
|
cwd: projectDir,
|
|
648
714
|
stdio: 'inherit',
|
|
649
|
-
env: process.env
|
|
715
|
+
env: { ...process.env, ...routerLaunch.env }
|
|
650
716
|
});
|
|
651
717
|
if (syncScriptPath) {
|
|
652
718
|
const syncAppName = `${appName}-9router-sync`;
|
|
@@ -1273,7 +1339,7 @@ async function main() {
|
|
|
1273
1339
|
}
|
|
1274
1340
|
|
|
1275
1341
|
|
|
1276
|
-
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));}`;
|
|
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));}`;
|
|
1277
1343
|
const b64Patch = Buffer.from(patchScript).toString('base64');
|
|
1278
1344
|
|
|
1279
1345
|
// Browser Playwright (both desktop & server modes need chromium)
|
|
@@ -1732,13 +1798,17 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1732
1798
|
allow: agentMetas.map((meta) => meta.agentId),
|
|
1733
1799
|
},
|
|
1734
1800
|
},
|
|
1735
|
-
gateway: {
|
|
1736
|
-
port: 18791,
|
|
1737
|
-
mode: 'local',
|
|
1738
|
-
bind: 'custom',
|
|
1739
|
-
customBindHost: '0.0.0.0',
|
|
1740
|
-
|
|
1741
|
-
|
|
1801
|
+
gateway: {
|
|
1802
|
+
port: 18791,
|
|
1803
|
+
mode: 'local',
|
|
1804
|
+
bind: 'custom',
|
|
1805
|
+
customBindHost: '0.0.0.0',
|
|
1806
|
+
controlUi: {
|
|
1807
|
+
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
1808
|
+
requireDeviceIdentity: false,
|
|
1809
|
+
},
|
|
1810
|
+
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' },
|
|
1811
|
+
},
|
|
1742
1812
|
};
|
|
1743
1813
|
sharedConfig.plugins = {
|
|
1744
1814
|
entries: {
|
|
@@ -1965,10 +2035,11 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1965
2035
|
commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
|
|
1966
2036
|
channels: {},
|
|
1967
2037
|
tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
|
|
1968
|
-
gateway: {
|
|
1969
|
-
port: 18791 + (isMultiBot ? bIndex : 0), mode: 'local', bind: 'custom', customBindHost: '0.0.0.0',
|
|
1970
|
-
|
|
1971
|
-
|
|
2038
|
+
gateway: {
|
|
2039
|
+
port: 18791 + (isMultiBot ? bIndex : 0), mode: 'local', bind: 'custom', customBindHost: '0.0.0.0',
|
|
2040
|
+
controlUi: { allowedOrigins: getGatewayAllowedOrigins(18791 + (isMultiBot ? bIndex : 0)), requireDeviceIdentity: false },
|
|
2041
|
+
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' }
|
|
2042
|
+
}
|
|
1972
2043
|
};
|
|
1973
2044
|
|
|
1974
2045
|
if (hasBrowserDesktop) {
|
|
@@ -2220,13 +2291,14 @@ fi
|
|
|
2220
2291
|
if (code === 0) {
|
|
2221
2292
|
console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoàn tất! Bot đang chạy.' : 'Setup complete! Bot is running.'}`));
|
|
2222
2293
|
|
|
2223
|
-
if (providerKey === '9router') {
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
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...)'));
|
|
2230
2302
|
console.log(chalk.gray(isVi
|
|
2231
2303
|
? ' → Sau khi kết nối provider, bot sẽ tự động hoạt động qua combo "smart-route"'
|
|
2232
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
|
============================================ */
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
// ========== State ==========
|
|
27
|
-
const state = {
|
|
27
|
+
const state = {
|
|
28
28
|
currentStep: 1,
|
|
29
29
|
totalSteps: 5,
|
|
30
30
|
channel: null,
|
|
@@ -47,7 +47,21 @@
|
|
|
47
47
|
apiKey: '',
|
|
48
48
|
projectPath: '',
|
|
49
49
|
},
|
|
50
|
-
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function getGatewayAllowedOrigins(port) {
|
|
53
|
+
const normalizedPort = Number(port) || 18791;
|
|
54
|
+
const origins = new Set([
|
|
55
|
+
`http://localhost:${normalizedPort}`,
|
|
56
|
+
`http://127.0.0.1:${normalizedPort}`,
|
|
57
|
+
`http://0.0.0.0:${normalizedPort}`,
|
|
58
|
+
]);
|
|
59
|
+
const currentHost = (window.location && window.location.hostname) ? window.location.hostname.trim() : '';
|
|
60
|
+
if (currentHost) {
|
|
61
|
+
origins.add(`http://${currentHost}:${normalizedPort}`);
|
|
62
|
+
}
|
|
63
|
+
return Array.from(origins);
|
|
64
|
+
}
|
|
51
65
|
|
|
52
66
|
|
|
53
67
|
// ========== AI Providers & Models ==========
|
|
@@ -1577,12 +1591,16 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
1577
1591
|
commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
|
|
1578
1592
|
channels: ch.channelConfig,
|
|
1579
1593
|
tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
|
|
1580
|
-
gateway: {
|
|
1581
|
-
port: 18791,
|
|
1582
|
-
mode: 'local',
|
|
1583
|
-
bind: '0.0.0.0',
|
|
1584
|
-
|
|
1585
|
-
|
|
1594
|
+
gateway: {
|
|
1595
|
+
port: 18791,
|
|
1596
|
+
mode: 'local',
|
|
1597
|
+
bind: '0.0.0.0',
|
|
1598
|
+
controlUi: {
|
|
1599
|
+
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
1600
|
+
requireDeviceIdentity: false,
|
|
1601
|
+
},
|
|
1602
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
1603
|
+
},
|
|
1586
1604
|
};
|
|
1587
1605
|
|
|
1588
1606
|
// 9Router: add proxy endpoint config under models.providers
|
|
@@ -1815,7 +1833,7 @@ model:
|
|
|
1815
1833
|
? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 & '
|
|
1816
1834
|
: '';
|
|
1817
1835
|
// Patch config on every startup to keep gateway settings stable
|
|
1818
|
-
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));}\\" && `;
|
|
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));}\\" && `;
|
|
1819
1837
|
// Auto-approve device pairing after gateway starts (required since v2026.3.x)
|
|
1820
1838
|
const autoApproveCmd = '(while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done) & ';
|
|
1821
1839
|
const finalCmd = `CMD sh -c "${pluginInstallCmd}${patchCmd}${browserPrefix}${autoApproveCmd}${gatewayCmd}"`;
|
|
@@ -2868,17 +2886,17 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
2868
2886
|
// ─── Shared initializer (provider install) ───────────────────────────────
|
|
2869
2887
|
function providerLines(arr, shell) {
|
|
2870
2888
|
if (is9Router) {
|
|
2871
|
-
if (shell === 'bat') {
|
|
2872
|
-
arr.push('npm install -g 9router');
|
|
2889
|
+
if (shell === 'bat') {
|
|
2890
|
+
arr.push('npm install -g 9router');
|
|
2873
2891
|
arr.push('start "9Router" cmd /k "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
2874
|
-
arr.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
|
|
2875
|
-
arr.push('timeout /t 5 /nobreak >nul');
|
|
2876
|
-
} else {
|
|
2877
|
-
arr.push('npm install -g 9router');
|
|
2878
|
-
arr.push('nohup
|
|
2879
|
-
arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
|
|
2880
|
-
arr.push('sleep 3');
|
|
2881
|
-
}
|
|
2892
|
+
arr.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
|
|
2893
|
+
arr.push('timeout /t 5 /nobreak >nul');
|
|
2894
|
+
} else {
|
|
2895
|
+
arr.push('npm install -g 9router');
|
|
2896
|
+
arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 node "$(npm root -g)/9router/app/server.js" >/tmp/9router.log 2>&1 &');
|
|
2897
|
+
arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
|
|
2898
|
+
arr.push('sleep 3');
|
|
2899
|
+
}
|
|
2882
2900
|
} else if (isOllama) {
|
|
2883
2901
|
if (shell === 'bat') {
|
|
2884
2902
|
arr.push('where ollama >nul 2>&1 || (powershell -Command "Invoke-WebRequest -Uri https://ollama.com/download/OllamaSetup.exe -OutFile OllamaSetup.exe" && OllamaSetup.exe && del OllamaSetup.exe)');
|
|
@@ -3036,12 +3054,16 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
3036
3054
|
'telegram-multibot-relay': { enabled: true },
|
|
3037
3055
|
},
|
|
3038
3056
|
},
|
|
3039
|
-
gateway: {
|
|
3040
|
-
port: 18791,
|
|
3041
|
-
mode: 'local',
|
|
3042
|
-
bind: '0.0.0.0',
|
|
3043
|
-
|
|
3044
|
-
|
|
3057
|
+
gateway: {
|
|
3058
|
+
port: 18791,
|
|
3059
|
+
mode: 'local',
|
|
3060
|
+
bind: '0.0.0.0',
|
|
3061
|
+
controlUi: {
|
|
3062
|
+
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
3063
|
+
requireDeviceIdentity: false,
|
|
3064
|
+
},
|
|
3065
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3066
|
+
},
|
|
3045
3067
|
};
|
|
3046
3068
|
return JSON.stringify(cfg, null, 2);
|
|
3047
3069
|
}
|
|
@@ -3149,12 +3171,16 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
3149
3171
|
},
|
|
3150
3172
|
commands: { native: 'auto', nativeSkills: 'auto', restart: true },
|
|
3151
3173
|
channels: channelConfig,
|
|
3152
|
-
gateway: {
|
|
3153
|
-
port: basePort,
|
|
3154
|
-
mode: 'local',
|
|
3155
|
-
bind: '0.0.0.0',
|
|
3156
|
-
|
|
3157
|
-
|
|
3174
|
+
gateway: {
|
|
3175
|
+
port: basePort,
|
|
3176
|
+
mode: 'local',
|
|
3177
|
+
bind: '0.0.0.0',
|
|
3178
|
+
controlUi: {
|
|
3179
|
+
allowedOrigins: getGatewayAllowedOrigins(basePort),
|
|
3180
|
+
requireDeviceIdentity: false,
|
|
3181
|
+
},
|
|
3182
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3183
|
+
},
|
|
3158
3184
|
|
|
3159
3185
|
};
|
|
3160
3186
|
return JSON.stringify(cfg, null, 2);
|
|
@@ -3533,12 +3559,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3533
3559
|
|
|
3534
3560
|
if (isMultiBot) {
|
|
3535
3561
|
vps.push('echo "--- Creating shared multi-agent runtime ---"');
|
|
3536
|
-
appendShWriteCommands(vps, sharedNativeFileMap());
|
|
3537
|
-
vps.push('echo "--- Starting shared gateway via PM2 ---"');
|
|
3538
|
-
if (is9Router) {
|
|
3539
|
-
vps.push('pm2 start --name openclaw-multibot-9router --
|
|
3540
|
-
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3541
|
-
}
|
|
3562
|
+
appendShWriteCommands(vps, sharedNativeFileMap());
|
|
3563
|
+
vps.push('echo "--- Starting shared gateway via PM2 ---"');
|
|
3564
|
+
if (is9Router) {
|
|
3565
|
+
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)"');
|
|
3566
|
+
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3567
|
+
}
|
|
3542
3568
|
vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
|
|
3543
3569
|
vps.push('pm2 save && pm2 startup');
|
|
3544
3570
|
vps.push(`echo ""`);
|
|
@@ -3546,12 +3572,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3546
3572
|
vps.push(`echo "Commands:"`);
|
|
3547
3573
|
vps.push(`echo " pm2 status # Status gateway"`);
|
|
3548
3574
|
vps.push(`echo " pm2 logs openclaw-multibot"`);
|
|
3549
|
-
} else {
|
|
3550
|
-
appendShWriteCommands(vps, botFiles(0));
|
|
3551
|
-
if (is9Router) {
|
|
3552
|
-
vps.push('pm2 start --name openclaw-9router --
|
|
3553
|
-
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3554
|
-
}
|
|
3575
|
+
} else {
|
|
3576
|
+
appendShWriteCommands(vps, botFiles(0));
|
|
3577
|
+
if (is9Router) {
|
|
3578
|
+
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)"');
|
|
3579
|
+
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3580
|
+
}
|
|
3555
3581
|
vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
|
|
3556
3582
|
vps.push('pm2 save && pm2 startup');
|
|
3557
3583
|
vps.push('echo "Bot dang chay! Xem log: pm2 logs openclaw"');
|
|
@@ -107,6 +107,23 @@ checks.push(() => expectMatch(
|
|
|
107
107
|
'CLI must resolve the correct native 9Router data directory on both Windows and Unix'
|
|
108
108
|
));
|
|
109
109
|
|
|
110
|
+
checks.push(() => expect(
|
|
111
|
+
cli.includes('function getGatewayAllowedOrigins(port) {')
|
|
112
|
+
&& cli.includes('Object.values(os.networkInterfaces() || {})')
|
|
113
|
+
&& cli.includes('`http://localhost:${normalizedPort}`')
|
|
114
|
+
&& cli.includes('`http://127.0.0.1:${normalizedPort}`')
|
|
115
|
+
&& cli.includes('`http://0.0.0.0:${normalizedPort}`'),
|
|
116
|
+
'CLI must derive control UI allowed origins from localhost plus non-internal IPv4 interfaces'
|
|
117
|
+
));
|
|
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
|
+
|
|
110
127
|
checks.push(() => expect(
|
|
111
128
|
cli.includes("Removed smart-route (no active providers)")
|
|
112
129
|
&& cli.includes("if (!a.length) {")
|
|
@@ -135,7 +152,7 @@ checks.push(() => expectMatch(
|
|
|
135
152
|
|
|
136
153
|
checks.push(() => expectMatch(
|
|
137
154
|
cli,
|
|
138
|
-
/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,
|
|
139
156
|
'Native PM2 flow must expose dashboard access info and the tokenized dashboard command'
|
|
140
157
|
));
|
|
141
158
|
|
|
@@ -187,6 +204,18 @@ checks.push(() => expectMatch(
|
|
|
187
204
|
'Native 9Router config must target localhost instead of the Docker hostname'
|
|
188
205
|
));
|
|
189
206
|
|
|
207
|
+
checks.push(() => expectMatch(
|
|
208
|
+
cli,
|
|
209
|
+
/controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791\)/s,
|
|
210
|
+
'Native shared gateway config must seed control UI allowed origins'
|
|
211
|
+
));
|
|
212
|
+
|
|
213
|
+
checks.push(() => expectMatch(
|
|
214
|
+
cli,
|
|
215
|
+
/controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791 \+ \(isMultiBot \? bIndex : 0\)\)/s,
|
|
216
|
+
'Native per-bot gateway config must seed control UI allowed origins for each port'
|
|
217
|
+
));
|
|
218
|
+
|
|
190
219
|
checks.push(() => expectMatch(
|
|
191
220
|
cli,
|
|
192
221
|
/channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'pairing',\s*autoReply: true/s,
|
|
@@ -195,7 +224,7 @@ checks.push(() => expectMatch(
|
|
|
195
224
|
|
|
196
225
|
checks.push(() => expectMatch(
|
|
197
226
|
cli,
|
|
198
|
-
/function startNative9RouterPm2\(\{ isVi, projectDir, appName, syncScriptPath \}\) \{[\s\S]*
|
|
227
|
+
/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,
|
|
199
228
|
'VPS native 9Router flow must start a standalone 9Router dashboard on port 20128 via PM2'
|
|
200
229
|
));
|
|
201
230
|
|
|
@@ -207,8 +236,8 @@ checks.push(() => expectMatch(
|
|
|
207
236
|
|
|
208
237
|
checks.push(() => expectMatch(
|
|
209
238
|
cli,
|
|
210
|
-
/function resolveNative9RouterDesktopLaunch\(\) \{[\s\S]*process\.
|
|
211
|
-
'Native desktop 9Router launch must bypass the interactive CLI menu
|
|
239
|
+
/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,
|
|
240
|
+
'Native desktop 9Router launch must bypass the interactive CLI menu by running the 9Router server entry directly'
|
|
212
241
|
));
|
|
213
242
|
|
|
214
243
|
checks.push(() => expectMatch(
|
|
@@ -292,7 +321,7 @@ checks.push(() => expectMatch(
|
|
|
292
321
|
|
|
293
322
|
checks.push(() => expectMatch(
|
|
294
323
|
setup,
|
|
295
|
-
/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
|
|
324
|
+
/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,
|
|
296
325
|
'Native script generation must install and start a standalone 9Router dashboard on port 20128'
|
|
297
326
|
));
|
|
298
327
|
|
|
@@ -340,13 +369,13 @@ checks.push(() => expect(
|
|
|
340
369
|
|
|
341
370
|
checks.push(() => expectMatch(
|
|
342
371
|
setup,
|
|
343
|
-
/else if \(state\.nativeOs === 'vps'\) \{[\s\S]*pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw-multibot/s,
|
|
372
|
+
/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,
|
|
344
373
|
'VPS multi-bot native script must start the shared gateway via PM2'
|
|
345
374
|
));
|
|
346
375
|
|
|
347
376
|
checks.push(() => expectMatch(
|
|
348
377
|
setup,
|
|
349
|
-
/else if \(state\.nativeOs === 'vps'\) \{[\s\S]*pm2 start --name openclaw -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw/s,
|
|
378
|
+
/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,
|
|
350
379
|
'VPS single-bot native script must start one bot via PM2'
|
|
351
380
|
));
|
|
352
381
|
|
|
@@ -374,6 +403,32 @@ checks.push(() => expectMatch(
|
|
|
374
403
|
'Wizard copy must mention native auto-login and still show the dedicated Docker QR login command'
|
|
375
404
|
));
|
|
376
405
|
|
|
406
|
+
checks.push(() => expect(
|
|
407
|
+
setup.includes('function getGatewayAllowedOrigins(port) {')
|
|
408
|
+
&& setup.includes('window.location')
|
|
409
|
+
&& setup.includes('`http://localhost:${normalizedPort}`')
|
|
410
|
+
&& setup.includes('`http://127.0.0.1:${normalizedPort}`'),
|
|
411
|
+
'Web wizard must expose a helper that seeds likely control UI origins'
|
|
412
|
+
));
|
|
413
|
+
|
|
414
|
+
checks.push(() => expectMatch(
|
|
415
|
+
setup,
|
|
416
|
+
/controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791\)/s,
|
|
417
|
+
'Web wizard single-bot gateway config must seed control UI allowed origins'
|
|
418
|
+
));
|
|
419
|
+
|
|
420
|
+
checks.push(() => expectMatch(
|
|
421
|
+
setup,
|
|
422
|
+
/controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(basePort\)/s,
|
|
423
|
+
'Web wizard per-bot gateway config must seed control UI allowed origins'
|
|
424
|
+
));
|
|
425
|
+
|
|
426
|
+
checks.push(() => expectMatch(
|
|
427
|
+
setup,
|
|
428
|
+
/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,
|
|
429
|
+
'Web wizard Docker patch command must add interface-based control UI allowed origins'
|
|
430
|
+
));
|
|
431
|
+
|
|
377
432
|
for (const check of checks) {
|
|
378
433
|
check();
|
|
379
434
|
}
|