create-openclaw-bot 5.1.5 → 5.1.7
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 +112 -81
- package/package.json +1 -1
- package/setup.js +68 -45
- package/tests/smoke-cli-logic.mjs +53 -6
- package/tmp_diff.patch +331 -82
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# Changelog (English)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [5.1.7] — 2026-04-07
|
|
5
|
+
|
|
6
|
+
### 🌟 Fix Control UI CORS & Native 9Router Path Resolution
|
|
7
|
+
|
|
8
|
+
- **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.
|
|
9
|
+
- **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.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## [5.1.6] — 2026-04-07
|
|
13
|
+
|
|
14
|
+
### 🐞 Fix PM2 SIGKILL on Native VPS Installs
|
|
15
|
+
|
|
16
|
+
- **Fix `PM2 SIGKILL` Error**: Removed the `-t` (interactive TTY) flag from all background `9router` launches. This terminal-dependent flag could cause PM2 to hang and aggressively SIGKILL the spawned process on headless VPS environments.
|
|
17
|
+
- **Robust PM2 Sync Helper**: Added a two-stage fallback for the 9Router smart-route sync script. If PM2 encounters `SIGKILL` or memory limits while spawning the sync helper, the setup gracefully falls back to a background `nohup node ... &` process instead of throwing a hard exception. If both fail, it logs a warning but allows the overall OpenClaw setup to finish successfully.
|
|
18
|
+
|
|
19
|
+
|
|
4
20
|
## [5.1.5] — 2026-04-06
|
|
5
21
|
|
|
6
22
|
### 🐞 Fix Native PM2 9Router Startup
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# Changelog (Tiếng Việt)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [5.1.7] — 2026-04-07
|
|
5
|
+
|
|
6
|
+
### 🌟 Sửa lỗi CORS Control UI & Đường dẫn 9Router Native
|
|
7
|
+
|
|
8
|
+
- **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.
|
|
9
|
+
- **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.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## [5.1.6] — 2026-04-07
|
|
13
|
+
|
|
14
|
+
### 🐞 Khắc phục lỗi PM2 ngắt cài đặt (SIGKILL) trên VPS
|
|
15
|
+
|
|
16
|
+
- **Sửa lỗi `PM2 SIGKILL`**: Loại bỏ cờ `-t` (chế độ giao diện terminal) khỏi tất cả các lệnh gọi `9router` chạy ngầm. Trên các VPS không giao diện (headless), cờ này có thể khiến PM2 bị treo và ném ra lỗi SIGKILL làm chết toàn bộ quá trình cài đặt.
|
|
17
|
+
- **Tối ưu Sync Helper chạy ngầm**: Bổ sung cơ chế dự phòng 2 lớp cho script tự động đồng bộ (sync helper). Nếu PM2 bị giới hạn RAM hoặc quá tải gây lỗi SIGKILL, script sẽ không văng lỗi sập Setup nữa mà tự động fallback xuống chạy ẩn bằng `nohup node ... &`. Trong trường hợp xấu nhất, bộ cài chỉ báo cảnh báo vàng và rẽ nhánh cho phép tiến trình Setup tiếp tục tới bước cuối cùng thành công.
|
|
18
|
+
|
|
19
|
+
|
|
4
20
|
## [5.1.5] — 2026-04-06
|
|
5
21
|
|
|
6
22
|
### 🐞 Sửa lỗi PM2 khởi động 9Router trên 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.7-0EA5E9?style=for-the-badge" alt="Version 5.1.7" /></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.7
|
|
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.7 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.7-0EA5E9?style=for-the-badge" alt="Version 5.1.7" /></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.7
|
|
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.7 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
|
@@ -145,50 +145,49 @@ function spawnBackgroundProcess(command, args, options = {}) {
|
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
function resolveNative9RouterDesktopLaunch() {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
return {
|
|
174
|
-
command: '9router',
|
|
175
|
-
args: ['-n', '-t', '-l', '-H', '0.0.0.0', '-p', '20128', '--skip-update'],
|
|
176
|
-
env: {}
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
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
|
-
}
|
|
148
|
+
function resolveNative9RouterDesktopLaunch() {
|
|
149
|
+
return {
|
|
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
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
187
158
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
async function waitFor9RouterApiReady({ port = 20128, timeoutMs = 15000 } = {}) {
|
|
188
|
+
const deadline = Date.now() + timeoutMs;
|
|
189
|
+
const candidates = [
|
|
190
|
+
`http://127.0.0.1:${port}/api/settings/require-login`,
|
|
192
191
|
`http://127.0.0.1:${port}/api/version`
|
|
193
192
|
];
|
|
194
193
|
|
|
@@ -380,6 +379,22 @@ function resolveCommandOnPath(command) {
|
|
|
380
379
|
}
|
|
381
380
|
}
|
|
382
381
|
|
|
382
|
+
function getGlobalNpmRoot() {
|
|
383
|
+
try {
|
|
384
|
+
return execSync('npm root -g', {
|
|
385
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
386
|
+
encoding: 'utf8',
|
|
387
|
+
shell: true,
|
|
388
|
+
env: process.env
|
|
389
|
+
}).trim();
|
|
390
|
+
} catch {
|
|
391
|
+
if (process.platform === 'win32') {
|
|
392
|
+
return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
|
|
393
|
+
}
|
|
394
|
+
return path.join(os.homedir(), '.local', 'lib', 'node_modules');
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
383
398
|
function indentBlock(text, spaces) {
|
|
384
399
|
const prefix = ' '.repeat(spaces);
|
|
385
400
|
return String(text)
|
|
@@ -393,7 +408,7 @@ function build9RouterComposeEntrypointScript(syncScriptBase64) {
|
|
|
393
408
|
'npm install -g 9router',
|
|
394
409
|
`node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
|
|
395
410
|
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
396
|
-
'exec 9router -n -
|
|
411
|
+
'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
|
|
397
412
|
].join('\n');
|
|
398
413
|
}
|
|
399
414
|
|
|
@@ -625,10 +640,10 @@ function runPm2Save({ projectDir, isVi }) {
|
|
|
625
640
|
|
|
626
641
|
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
627
642
|
const routerAppName = `${appName}-9router`;
|
|
628
|
-
const
|
|
643
|
+
const routerLaunch = resolveNative9RouterDesktopLaunch();
|
|
629
644
|
execFileSync('pm2', [
|
|
630
645
|
'start',
|
|
631
|
-
|
|
646
|
+
routerLaunch.command,
|
|
632
647
|
'--name',
|
|
633
648
|
routerAppName,
|
|
634
649
|
'--cwd',
|
|
@@ -636,35 +651,47 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
|
636
651
|
'--interpreter',
|
|
637
652
|
'none',
|
|
638
653
|
'--',
|
|
639
|
-
|
|
640
|
-
'-t',
|
|
641
|
-
'-l',
|
|
642
|
-
'-H',
|
|
643
|
-
'0.0.0.0',
|
|
644
|
-
'-p',
|
|
645
|
-
'20128',
|
|
646
|
-
'--skip-update'
|
|
654
|
+
...routerLaunch.args
|
|
647
655
|
], {
|
|
648
656
|
cwd: projectDir,
|
|
649
657
|
stdio: 'inherit',
|
|
650
|
-
env: process.env
|
|
658
|
+
env: { ...process.env, ...routerLaunch.env }
|
|
651
659
|
});
|
|
652
660
|
if (syncScriptPath) {
|
|
653
661
|
const syncAppName = `${appName}-9router-sync`;
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
662
|
+
const normalizedSyncScriptPath = syncScriptPath.replace(/\\/g, '/');
|
|
663
|
+
try {
|
|
664
|
+
execFileSync('pm2', [
|
|
665
|
+
'start',
|
|
666
|
+
normalizedSyncScriptPath,
|
|
667
|
+
'--name',
|
|
668
|
+
syncAppName,
|
|
669
|
+
'--cwd',
|
|
670
|
+
projectDir.replace(/\\/g, '/'),
|
|
671
|
+
'--interpreter',
|
|
672
|
+
process.execPath
|
|
673
|
+
], {
|
|
674
|
+
cwd: projectDir,
|
|
675
|
+
stdio: 'inherit',
|
|
676
|
+
env: process.env
|
|
677
|
+
});
|
|
678
|
+
} catch {
|
|
679
|
+
try {
|
|
680
|
+
execSync(`nohup "${process.execPath}" "${normalizedSyncScriptPath}" >/tmp/${syncAppName}.log 2>&1 &`, {
|
|
681
|
+
cwd: projectDir,
|
|
682
|
+
stdio: 'ignore',
|
|
683
|
+
shell: true,
|
|
684
|
+
env: process.env
|
|
685
|
+
});
|
|
686
|
+
console.log(chalk.yellow(isVi
|
|
687
|
+
? `⚠️ PM2 khong khoi dong duoc sync helper. Da fallback sang background node: /tmp/${syncAppName}.log`
|
|
688
|
+
: `⚠️ PM2 could not start the sync helper. Fell back to a background node process: /tmp/${syncAppName}.log`));
|
|
689
|
+
} catch {
|
|
690
|
+
console.log(chalk.yellow(isVi
|
|
691
|
+
? `⚠️ Khong the khoi dong 9Router sync helper. 9Router van chay, nhung smart-route co the can dong bo thu cong sau.`
|
|
692
|
+
: `⚠️ Could not start the 9Router sync helper. 9Router is still running, but smart-route may need manual syncing later.`));
|
|
693
|
+
}
|
|
694
|
+
}
|
|
668
695
|
}
|
|
669
696
|
runPm2Save({ projectDir, isVi });
|
|
670
697
|
console.log(chalk.green(`\n✅ ${isVi ? '9Router da duoc khoi dong qua PM2.' : '9Router is running via PM2.'}`));
|
|
@@ -1255,7 +1282,7 @@ async function main() {
|
|
|
1255
1282
|
}
|
|
1256
1283
|
|
|
1257
1284
|
|
|
1258
|
-
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));}`;
|
|
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));}`;
|
|
1259
1286
|
const b64Patch = Buffer.from(patchScript).toString('base64');
|
|
1260
1287
|
|
|
1261
1288
|
// Browser Playwright (both desktop & server modes need chromium)
|
|
@@ -1714,13 +1741,16 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1714
1741
|
allow: agentMetas.map((meta) => meta.agentId),
|
|
1715
1742
|
},
|
|
1716
1743
|
},
|
|
1717
|
-
gateway: {
|
|
1718
|
-
port: 18791,
|
|
1719
|
-
mode: 'local',
|
|
1720
|
-
bind: 'custom',
|
|
1721
|
-
customBindHost: '0.0.0.0',
|
|
1722
|
-
|
|
1723
|
-
|
|
1744
|
+
gateway: {
|
|
1745
|
+
port: 18791,
|
|
1746
|
+
mode: 'local',
|
|
1747
|
+
bind: 'custom',
|
|
1748
|
+
customBindHost: '0.0.0.0',
|
|
1749
|
+
controlUi: {
|
|
1750
|
+
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
1751
|
+
},
|
|
1752
|
+
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' },
|
|
1753
|
+
},
|
|
1724
1754
|
};
|
|
1725
1755
|
sharedConfig.plugins = {
|
|
1726
1756
|
entries: {
|
|
@@ -1947,10 +1977,11 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1947
1977
|
commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
|
|
1948
1978
|
channels: {},
|
|
1949
1979
|
tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
|
|
1950
|
-
gateway: {
|
|
1951
|
-
port: 18791 + (isMultiBot ? bIndex : 0), mode: 'local', bind: 'custom', customBindHost: '0.0.0.0',
|
|
1952
|
-
|
|
1953
|
-
|
|
1980
|
+
gateway: {
|
|
1981
|
+
port: 18791 + (isMultiBot ? bIndex : 0), mode: 'local', bind: 'custom', customBindHost: '0.0.0.0',
|
|
1982
|
+
controlUi: { allowedOrigins: getGatewayAllowedOrigins(18791 + (isMultiBot ? bIndex : 0)) },
|
|
1983
|
+
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' }
|
|
1984
|
+
}
|
|
1954
1985
|
};
|
|
1955
1986
|
|
|
1956
1987
|
if (hasBrowserDesktop) {
|
package/package.json
CHANGED
package/setup.js
CHANGED
|
@@ -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,15 @@ 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
|
+
},
|
|
1601
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
1602
|
+
},
|
|
1586
1603
|
};
|
|
1587
1604
|
|
|
1588
1605
|
// 9Router: add proxy endpoint config under models.providers
|
|
@@ -1767,7 +1784,7 @@ model:
|
|
|
1767
1784
|
'npm install -g 9router',
|
|
1768
1785
|
`node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
|
|
1769
1786
|
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
1770
|
-
'exec 9router -n -
|
|
1787
|
+
'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
|
|
1771
1788
|
].join('\n');
|
|
1772
1789
|
|
|
1773
1790
|
state.config.plugins.forEach((pid) => {
|
|
@@ -1815,7 +1832,7 @@ model:
|
|
|
1815
1832
|
? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 & '
|
|
1816
1833
|
: '';
|
|
1817
1834
|
// 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));}\\" && `;
|
|
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));}\\" && `;
|
|
1819
1836
|
// Auto-approve device pairing after gateway starts (required since v2026.3.x)
|
|
1820
1837
|
const autoApproveCmd = '(while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done) & ';
|
|
1821
1838
|
const finalCmd = `CMD sh -c "${pluginInstallCmd}${patchCmd}${browserPrefix}${autoApproveCmd}${gatewayCmd}"`;
|
|
@@ -2868,17 +2885,17 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
2868
2885
|
// ─── Shared initializer (provider install) ───────────────────────────────
|
|
2869
2886
|
function providerLines(arr, shell) {
|
|
2870
2887
|
if (is9Router) {
|
|
2871
|
-
if (shell === 'bat') {
|
|
2872
|
-
arr.push('npm install -g 9router');
|
|
2873
|
-
arr.push('start "9Router" cmd /k "9router -n -
|
|
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
|
-
}
|
|
2888
|
+
if (shell === 'bat') {
|
|
2889
|
+
arr.push('npm install -g 9router');
|
|
2890
|
+
arr.push('start "9Router" cmd /k "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
2891
|
+
arr.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
|
|
2892
|
+
arr.push('timeout /t 5 /nobreak >nul');
|
|
2893
|
+
} else {
|
|
2894
|
+
arr.push('npm install -g 9router');
|
|
2895
|
+
arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 node "$(npm root -g)/9router/app/server.js" >/tmp/9router.log 2>&1 &');
|
|
2896
|
+
arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
|
|
2897
|
+
arr.push('sleep 3');
|
|
2898
|
+
}
|
|
2882
2899
|
} else if (isOllama) {
|
|
2883
2900
|
if (shell === 'bat') {
|
|
2884
2901
|
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 +3053,15 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
3036
3053
|
'telegram-multibot-relay': { enabled: true },
|
|
3037
3054
|
},
|
|
3038
3055
|
},
|
|
3039
|
-
gateway: {
|
|
3040
|
-
port: 18791,
|
|
3041
|
-
mode: 'local',
|
|
3042
|
-
bind: '0.0.0.0',
|
|
3043
|
-
|
|
3044
|
-
|
|
3056
|
+
gateway: {
|
|
3057
|
+
port: 18791,
|
|
3058
|
+
mode: 'local',
|
|
3059
|
+
bind: '0.0.0.0',
|
|
3060
|
+
controlUi: {
|
|
3061
|
+
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
3062
|
+
},
|
|
3063
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3064
|
+
},
|
|
3045
3065
|
};
|
|
3046
3066
|
return JSON.stringify(cfg, null, 2);
|
|
3047
3067
|
}
|
|
@@ -3149,12 +3169,15 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
3149
3169
|
},
|
|
3150
3170
|
commands: { native: 'auto', nativeSkills: 'auto', restart: true },
|
|
3151
3171
|
channels: channelConfig,
|
|
3152
|
-
gateway: {
|
|
3153
|
-
port: basePort,
|
|
3154
|
-
mode: 'local',
|
|
3155
|
-
bind: '0.0.0.0',
|
|
3156
|
-
|
|
3157
|
-
|
|
3172
|
+
gateway: {
|
|
3173
|
+
port: basePort,
|
|
3174
|
+
mode: 'local',
|
|
3175
|
+
bind: '0.0.0.0',
|
|
3176
|
+
controlUi: {
|
|
3177
|
+
allowedOrigins: getGatewayAllowedOrigins(basePort),
|
|
3178
|
+
},
|
|
3179
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3180
|
+
},
|
|
3158
3181
|
|
|
3159
3182
|
};
|
|
3160
3183
|
return JSON.stringify(cfg, null, 2);
|
|
@@ -3533,12 +3556,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3533
3556
|
|
|
3534
3557
|
if (isMultiBot) {
|
|
3535
3558
|
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
|
-
}
|
|
3559
|
+
appendShWriteCommands(vps, sharedNativeFileMap());
|
|
3560
|
+
vps.push('echo "--- Starting shared gateway via PM2 ---"');
|
|
3561
|
+
if (is9Router) {
|
|
3562
|
+
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)"');
|
|
3563
|
+
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3564
|
+
}
|
|
3542
3565
|
vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
|
|
3543
3566
|
vps.push('pm2 save && pm2 startup');
|
|
3544
3567
|
vps.push(`echo ""`);
|
|
@@ -3546,12 +3569,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3546
3569
|
vps.push(`echo "Commands:"`);
|
|
3547
3570
|
vps.push(`echo " pm2 status # Status gateway"`);
|
|
3548
3571
|
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
|
-
}
|
|
3572
|
+
} else {
|
|
3573
|
+
appendShWriteCommands(vps, botFiles(0));
|
|
3574
|
+
if (is9Router) {
|
|
3575
|
+
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)"');
|
|
3576
|
+
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3577
|
+
}
|
|
3555
3578
|
vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
|
|
3556
3579
|
vps.push('pm2 save && pm2 startup');
|
|
3557
3580
|
vps.push('echo "Bot dang chay! Xem log: pm2 logs openclaw"');
|
|
@@ -107,6 +107,15 @@ 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
|
+
|
|
110
119
|
checks.push(() => expect(
|
|
111
120
|
cli.includes("Removed smart-route (no active providers)")
|
|
112
121
|
&& cli.includes("if (!a.length) {")
|
|
@@ -187,6 +196,18 @@ checks.push(() => expectMatch(
|
|
|
187
196
|
'Native 9Router config must target localhost instead of the Docker hostname'
|
|
188
197
|
));
|
|
189
198
|
|
|
199
|
+
checks.push(() => expectMatch(
|
|
200
|
+
cli,
|
|
201
|
+
/controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791\)/s,
|
|
202
|
+
'Native shared gateway config must seed control UI allowed origins'
|
|
203
|
+
));
|
|
204
|
+
|
|
205
|
+
checks.push(() => expectMatch(
|
|
206
|
+
cli,
|
|
207
|
+
/controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791 \+ \(isMultiBot \? bIndex : 0\)\)/s,
|
|
208
|
+
'Native per-bot gateway config must seed control UI allowed origins for each port'
|
|
209
|
+
));
|
|
210
|
+
|
|
190
211
|
checks.push(() => expectMatch(
|
|
191
212
|
cli,
|
|
192
213
|
/channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'pairing',\s*autoReply: true/s,
|
|
@@ -195,7 +216,7 @@ checks.push(() => expectMatch(
|
|
|
195
216
|
|
|
196
217
|
checks.push(() => expectMatch(
|
|
197
218
|
cli,
|
|
198
|
-
/function startNative9RouterPm2\(\{ isVi, projectDir, appName, syncScriptPath \}\) \{[\s\S]*
|
|
219
|
+
/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
220
|
'VPS native 9Router flow must start a standalone 9Router dashboard on port 20128 via PM2'
|
|
200
221
|
));
|
|
201
222
|
|
|
@@ -207,8 +228,8 @@ checks.push(() => expectMatch(
|
|
|
207
228
|
|
|
208
229
|
checks.push(() => expectMatch(
|
|
209
230
|
cli,
|
|
210
|
-
/function resolveNative9RouterDesktopLaunch\(\) \{[\s\S]*process\.
|
|
211
|
-
'Native desktop 9Router launch must bypass the interactive CLI menu
|
|
231
|
+
/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,
|
|
232
|
+
'Native desktop 9Router launch must bypass the interactive CLI menu by running the 9Router server entry directly'
|
|
212
233
|
));
|
|
213
234
|
|
|
214
235
|
checks.push(() => expectMatch(
|
|
@@ -292,7 +313,7 @@ checks.push(() => expectMatch(
|
|
|
292
313
|
|
|
293
314
|
checks.push(() => expectMatch(
|
|
294
315
|
setup,
|
|
295
|
-
/function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*start "9Router" cmd \/k "9router -n -
|
|
316
|
+
/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
317
|
'Native script generation must install and start a standalone 9Router dashboard on port 20128'
|
|
297
318
|
));
|
|
298
319
|
|
|
@@ -340,13 +361,13 @@ checks.push(() => expect(
|
|
|
340
361
|
|
|
341
362
|
checks.push(() => expectMatch(
|
|
342
363
|
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,
|
|
364
|
+
/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
365
|
'VPS multi-bot native script must start the shared gateway via PM2'
|
|
345
366
|
));
|
|
346
367
|
|
|
347
368
|
checks.push(() => expectMatch(
|
|
348
369
|
setup,
|
|
349
|
-
/else if \(state\.nativeOs === 'vps'\) \{[\s\S]*pm2 start --name openclaw -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw/s,
|
|
370
|
+
/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
371
|
'VPS single-bot native script must start one bot via PM2'
|
|
351
372
|
));
|
|
352
373
|
|
|
@@ -374,6 +395,32 @@ checks.push(() => expectMatch(
|
|
|
374
395
|
'Wizard copy must mention native auto-login and still show the dedicated Docker QR login command'
|
|
375
396
|
));
|
|
376
397
|
|
|
398
|
+
checks.push(() => expect(
|
|
399
|
+
setup.includes('function getGatewayAllowedOrigins(port) {')
|
|
400
|
+
&& setup.includes('window.location')
|
|
401
|
+
&& setup.includes('`http://localhost:${normalizedPort}`')
|
|
402
|
+
&& setup.includes('`http://127.0.0.1:${normalizedPort}`'),
|
|
403
|
+
'Web wizard must expose a helper that seeds likely control UI origins'
|
|
404
|
+
));
|
|
405
|
+
|
|
406
|
+
checks.push(() => expectMatch(
|
|
407
|
+
setup,
|
|
408
|
+
/controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791\)/s,
|
|
409
|
+
'Web wizard single-bot gateway config must seed control UI allowed origins'
|
|
410
|
+
));
|
|
411
|
+
|
|
412
|
+
checks.push(() => expectMatch(
|
|
413
|
+
setup,
|
|
414
|
+
/controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(basePort\)/s,
|
|
415
|
+
'Web wizard per-bot gateway config must seed control UI allowed origins'
|
|
416
|
+
));
|
|
417
|
+
|
|
418
|
+
checks.push(() => expectMatch(
|
|
419
|
+
setup,
|
|
420
|
+
/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,
|
|
421
|
+
'Web wizard Docker patch command must add interface-based control UI allowed origins'
|
|
422
|
+
));
|
|
423
|
+
|
|
377
424
|
for (const check of checks) {
|
|
378
425
|
check();
|
|
379
426
|
}
|
package/tmp_diff.patch
CHANGED
|
@@ -1,114 +1,363 @@
|
|
|
1
1
|
diff --git a/cli.js b/cli.js
|
|
2
|
-
index
|
|
2
|
+
index 4b32aaf..4323e60 100644
|
|
3
3
|
--- a/cli.js
|
|
4
4
|
+++ b/cli.js
|
|
5
|
-
@@ -
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import os from 'os';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
-import { spawn, execSync } from 'child_process';
|
|
10
|
-
+import { spawn, execSync, execFileSync } from 'child_process';
|
|
11
|
-
const TELEGRAM_RELAY_PLUGIN_ID = 'openclaw-telegram-multibot-relay';
|
|
12
|
-
// Use plain npm package name ΓÇö clawhub: protocol not supported in all OpenClaw versions
|
|
13
|
-
const TELEGRAM_RELAY_PLUGIN_SPEC = TELEGRAM_RELAY_PLUGIN_ID;
|
|
14
|
-
@@ -363,6 +363,23 @@ setTimeout(sync, 5000);
|
|
15
|
-
setInterval(sync, INTERVAL);`;
|
|
5
|
+
@@ -146,34 +146,13 @@ function spawnBackgroundProcess(command, args, options = {}) {
|
|
16
6
|
}
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
+ }
|
|
21
65
|
+ }
|
|
22
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() {
|
|
23
78
|
+ try {
|
|
24
|
-
+ return execSync(
|
|
79
|
+
+ return execSync('npm root -g', {
|
|
25
80
|
+ stdio: ['ignore', 'pipe', 'ignore'],
|
|
26
81
|
+ encoding: 'utf8',
|
|
27
82
|
+ shell: true,
|
|
28
83
|
+ env: process.env
|
|
29
|
-
+ }).trim()
|
|
84
|
+
+ }).trim();
|
|
30
85
|
+ } catch {
|
|
31
|
-
+
|
|
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');
|
|
32
90
|
+ }
|
|
33
91
|
+}
|
|
34
92
|
+
|
|
35
93
|
function indentBlock(text, spaces) {
|
|
36
94
|
const prefix = ' '.repeat(spaces);
|
|
37
95
|
return String(text)
|
|
38
|
-
@@ -
|
|
96
|
+
@@ -625,10 +640,10 @@ function runPm2Save({ projectDir, isVi }) {
|
|
39
97
|
|
|
40
98
|
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
41
99
|
const routerAppName = `${appName}-9router`;
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
+
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
+
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
+
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
+ env: process.env
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
+
|
|
72
|
-
+ 'start',
|
|
73
|
-
+ syncScriptPath.replace(/\\/g, '/'),
|
|
74
|
-
+ '--name',
|
|
75
|
-
+ syncAppName,
|
|
76
|
-
+ '--cwd',
|
|
77
|
-
+ projectDir.replace(/\\/g, '/'),
|
|
78
|
-
+ '--interpreter',
|
|
79
|
-
+ process.execPath
|
|
80
|
-
+ ], {
|
|
81
|
-
cwd: projectDir,
|
|
82
|
-
stdio: 'inherit',
|
|
83
|
-
- shell: true,
|
|
84
|
-
env: process.env
|
|
85
|
-
- }
|
|
86
|
-
- );
|
|
87
|
-
- if (syncScriptPath) {
|
|
88
|
-
- const syncAppName = `${appName}-9router-sync`;
|
|
89
|
-
- execSync(
|
|
90
|
-
- `pm2 start "node ${syncScriptPath.replace(/\\/g, '/')}" --name "${syncAppName}" --cwd "${projectDir.replace(/\\/g, '/')}"`,
|
|
91
|
-
- {
|
|
92
|
-
- cwd: projectDir,
|
|
93
|
-
- stdio: 'inherit',
|
|
94
|
-
- shell: true,
|
|
95
|
-
- env: process.env
|
|
96
|
-
- }
|
|
97
|
-
- );
|
|
98
|
-
+ });
|
|
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() {
|
|
99
130
|
}
|
|
100
|
-
|
|
101
|
-
|
|
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"');
|
|
102
247
|
diff --git a/tests/smoke-cli-logic.mjs b/tests/smoke-cli-logic.mjs
|
|
103
|
-
index
|
|
248
|
+
index 23c1982..0827726 100644
|
|
104
249
|
--- a/tests/smoke-cli-logic.mjs
|
|
105
250
|
+++ b/tests/smoke-cli-logic.mjs
|
|
106
|
-
@@ -
|
|
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(
|
|
107
287
|
|
|
108
288
|
checks.push(() => expectMatch(
|
|
109
289
|
cli,
|
|
110
|
-
- /function startNative9RouterPm2\(\{ isVi, projectDir, appName, syncScriptPath \}\) \{[\s\S]*9router
|
|
111
|
-
+ /function startNative9RouterPm2\(\{ isVi, projectDir, appName, syncScriptPath \}\) \{[\s\S]*
|
|
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,
|
|
112
292
|
'VPS native 9Router flow must start a standalone 9Router dashboard on port 20128 via PM2'
|
|
113
293
|
));
|
|
114
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
|
+
}
|