create-openclaw-bot 5.1.6 → 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 +8 -0
- package/CHANGELOG.vi.md +8 -0
- package/README.md +3 -3
- package/README.vi.md +3 -3
- package/cli.js +74 -61
- package/package.json +1 -1
- package/setup.js +66 -43
- package/tests/smoke-cli-logic.mjs +53 -6
- package/tmp_diff.patch +363 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
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
|
+
|
|
4
12
|
## [5.1.6] — 2026-04-07
|
|
5
13
|
|
|
6
14
|
### 🐞 Fix PM2 SIGKILL on Native VPS Installs
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
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
|
+
|
|
4
12
|
## [5.1.6] — 2026-04-07
|
|
5
13
|
|
|
6
14
|
### 🐞 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.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
|
@@ -146,49 +146,48 @@ 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
|
+
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)
|
|
@@ -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,17 +651,11 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
|
636
651
|
'--interpreter',
|
|
637
652
|
'none',
|
|
638
653
|
'--',
|
|
639
|
-
|
|
640
|
-
'-l',
|
|
641
|
-
'-H',
|
|
642
|
-
'0.0.0.0',
|
|
643
|
-
'-p',
|
|
644
|
-
'20128',
|
|
645
|
-
'--skip-update'
|
|
654
|
+
...routerLaunch.args
|
|
646
655
|
], {
|
|
647
656
|
cwd: projectDir,
|
|
648
657
|
stdio: 'inherit',
|
|
649
|
-
env: process.env
|
|
658
|
+
env: { ...process.env, ...routerLaunch.env }
|
|
650
659
|
});
|
|
651
660
|
if (syncScriptPath) {
|
|
652
661
|
const syncAppName = `${appName}-9router-sync`;
|
|
@@ -1273,7 +1282,7 @@ async function main() {
|
|
|
1273
1282
|
}
|
|
1274
1283
|
|
|
1275
1284
|
|
|
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));}`;
|
|
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));}`;
|
|
1277
1286
|
const b64Patch = Buffer.from(patchScript).toString('base64');
|
|
1278
1287
|
|
|
1279
1288
|
// Browser Playwright (both desktop & server modes need chromium)
|
|
@@ -1732,13 +1741,16 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1732
1741
|
allow: agentMetas.map((meta) => meta.agentId),
|
|
1733
1742
|
},
|
|
1734
1743
|
},
|
|
1735
|
-
gateway: {
|
|
1736
|
-
port: 18791,
|
|
1737
|
-
mode: 'local',
|
|
1738
|
-
bind: 'custom',
|
|
1739
|
-
customBindHost: '0.0.0.0',
|
|
1740
|
-
|
|
1741
|
-
|
|
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
|
+
},
|
|
1742
1754
|
};
|
|
1743
1755
|
sharedConfig.plugins = {
|
|
1744
1756
|
entries: {
|
|
@@ -1965,10 +1977,11 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1965
1977
|
commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
|
|
1966
1978
|
channels: {},
|
|
1967
1979
|
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
|
-
|
|
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
|
+
}
|
|
1972
1985
|
};
|
|
1973
1986
|
|
|
1974
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
|
|
@@ -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');
|
|
2888
|
+
if (shell === 'bat') {
|
|
2889
|
+
arr.push('npm install -g 9router');
|
|
2873
2890
|
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
|
-
}
|
|
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 -l -H 0\.0\.0\.0 -p 20128 --skip-update"[\s\S]*nohup
|
|
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
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
diff --git a/cli.js b/cli.js
|
|
2
|
+
index 4b32aaf..4323e60 100644
|
|
3
|
+
--- a/cli.js
|
|
4
|
+
+++ b/cli.js
|
|
5
|
+
@@ -146,34 +146,13 @@ function spawnBackgroundProcess(command, args, options = {}) {
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function resolveNative9RouterDesktopLaunch() {
|
|
9
|
+
- if (process.platform === 'win32') {
|
|
10
|
+
- const npmRoot = (() => {
|
|
11
|
+
- try {
|
|
12
|
+
- return execSync('npm root -g', {
|
|
13
|
+
- stdio: ['ignore', 'pipe', 'ignore'],
|
|
14
|
+
- encoding: 'utf8',
|
|
15
|
+
- shell: true,
|
|
16
|
+
- env: process.env
|
|
17
|
+
- }).trim();
|
|
18
|
+
- } catch {
|
|
19
|
+
- return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
|
|
20
|
+
- }
|
|
21
|
+
- })();
|
|
22
|
+
-
|
|
23
|
+
- return {
|
|
24
|
+
- command: process.execPath,
|
|
25
|
+
- args: [path.join(npmRoot, '9router', 'app', 'server.js')],
|
|
26
|
+
- env: {
|
|
27
|
+
- PORT: '20128',
|
|
28
|
+
- HOSTNAME: '0.0.0.0'
|
|
29
|
+
- }
|
|
30
|
+
- };
|
|
31
|
+
- }
|
|
32
|
+
-
|
|
33
|
+
return {
|
|
34
|
+
- command: '9router',
|
|
35
|
+
- args: ['-n', '-l', '-H', '0.0.0.0', '-p', '20128', '--skip-update'],
|
|
36
|
+
- env: {}
|
|
37
|
+
+ command: process.execPath,
|
|
38
|
+
+ args: [path.join(getGlobalNpmRoot(), '9router', 'app', 'server.js')],
|
|
39
|
+
+ env: {
|
|
40
|
+
+ PORT: '20128',
|
|
41
|
+
+ HOSTNAME: '0.0.0.0'
|
|
42
|
+
+ }
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@@ -185,6 +164,26 @@ function getNative9RouterDataDir() {
|
|
47
|
+
return path.join(os.homedir(), '.9router');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
+function getGatewayAllowedOrigins(port) {
|
|
51
|
+
+ const normalizedPort = Number(port) || 18791;
|
|
52
|
+
+ const origins = new Set([
|
|
53
|
+
+ `http://localhost:${normalizedPort}`,
|
|
54
|
+
+ `http://127.0.0.1:${normalizedPort}`,
|
|
55
|
+
+ `http://0.0.0.0:${normalizedPort}`
|
|
56
|
+
+ ]);
|
|
57
|
+
+
|
|
58
|
+
+ for (const entries of Object.values(os.networkInterfaces() || {})) {
|
|
59
|
+
+ for (const entry of entries || []) {
|
|
60
|
+
+ if (!entry || entry.internal || entry.family !== 'IPv4' || !entry.address) {
|
|
61
|
+
+ continue;
|
|
62
|
+
+ }
|
|
63
|
+
+ origins.add(`http://${entry.address}:${normalizedPort}`);
|
|
64
|
+
+ }
|
|
65
|
+
+ }
|
|
66
|
+
+
|
|
67
|
+
+ return Array.from(origins);
|
|
68
|
+
+}
|
|
69
|
+
+
|
|
70
|
+
async function waitFor9RouterApiReady({ port = 20128, timeoutMs = 15000 } = {}) {
|
|
71
|
+
const deadline = Date.now() + timeoutMs;
|
|
72
|
+
const candidates = [
|
|
73
|
+
@@ -380,6 +379,22 @@ function resolveCommandOnPath(command) {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
+function getGlobalNpmRoot() {
|
|
78
|
+
+ try {
|
|
79
|
+
+ return execSync('npm root -g', {
|
|
80
|
+
+ stdio: ['ignore', 'pipe', 'ignore'],
|
|
81
|
+
+ encoding: 'utf8',
|
|
82
|
+
+ shell: true,
|
|
83
|
+
+ env: process.env
|
|
84
|
+
+ }).trim();
|
|
85
|
+
+ } catch {
|
|
86
|
+
+ if (process.platform === 'win32') {
|
|
87
|
+
+ return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
|
|
88
|
+
+ }
|
|
89
|
+
+ return path.join(os.homedir(), '.local', 'lib', 'node_modules');
|
|
90
|
+
+ }
|
|
91
|
+
+}
|
|
92
|
+
+
|
|
93
|
+
function indentBlock(text, spaces) {
|
|
94
|
+
const prefix = ' '.repeat(spaces);
|
|
95
|
+
return String(text)
|
|
96
|
+
@@ -625,10 +640,10 @@ function runPm2Save({ projectDir, isVi }) {
|
|
97
|
+
|
|
98
|
+
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
99
|
+
const routerAppName = `${appName}-9router`;
|
|
100
|
+
- const routerCommand = resolveCommandOnPath('9router');
|
|
101
|
+
+ const routerLaunch = resolveNative9RouterDesktopLaunch();
|
|
102
|
+
execFileSync('pm2', [
|
|
103
|
+
'start',
|
|
104
|
+
- routerCommand,
|
|
105
|
+
+ routerLaunch.command,
|
|
106
|
+
'--name',
|
|
107
|
+
routerAppName,
|
|
108
|
+
'--cwd',
|
|
109
|
+
@@ -636,17 +651,11 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
110
|
+
'--interpreter',
|
|
111
|
+
'none',
|
|
112
|
+
'--',
|
|
113
|
+
- '-n',
|
|
114
|
+
- '-l',
|
|
115
|
+
- '-H',
|
|
116
|
+
- '0.0.0.0',
|
|
117
|
+
- '-p',
|
|
118
|
+
- '20128',
|
|
119
|
+
- '--skip-update'
|
|
120
|
+
+ ...routerLaunch.args
|
|
121
|
+
], {
|
|
122
|
+
cwd: projectDir,
|
|
123
|
+
stdio: 'inherit',
|
|
124
|
+
- env: process.env
|
|
125
|
+
+ env: { ...process.env, ...routerLaunch.env }
|
|
126
|
+
});
|
|
127
|
+
if (syncScriptPath) {
|
|
128
|
+
const syncAppName = `${appName}-9router-sync`;
|
|
129
|
+
@@ -1273,7 +1282,7 @@ async function main() {
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
- const patchScript = `const fs=require('fs'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0'});fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
|
|
134
|
+
+ const patchScript = `const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\`http://\${entry.address}:18791\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
|
|
135
|
+
const b64Patch = Buffer.from(patchScript).toString('base64');
|
|
136
|
+
|
|
137
|
+
// Browser Playwright (both desktop & server modes need chromium)
|
|
138
|
+
@@ -1737,6 +1746,9 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
139
|
+
mode: 'local',
|
|
140
|
+
bind: 'custom',
|
|
141
|
+
customBindHost: '0.0.0.0',
|
|
142
|
+
+ controlUi: {
|
|
143
|
+
+ allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
144
|
+
+ },
|
|
145
|
+
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' },
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
@@ -1967,6 +1979,7 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
149
|
+
tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
|
|
150
|
+
gateway: {
|
|
151
|
+
port: 18791 + (isMultiBot ? bIndex : 0), mode: 'local', bind: 'custom', customBindHost: '0.0.0.0',
|
|
152
|
+
+ controlUi: { allowedOrigins: getGatewayAllowedOrigins(18791 + (isMultiBot ? bIndex : 0)) },
|
|
153
|
+
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' }
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
diff --git a/setup.js b/setup.js
|
|
157
|
+
index 885a34a..2a33996 100644
|
|
158
|
+
--- a/setup.js
|
|
159
|
+
+++ b/setup.js
|
|
160
|
+
@@ -49,6 +49,20 @@
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
+ function getGatewayAllowedOrigins(port) {
|
|
165
|
+
+ const normalizedPort = Number(port) || 18791;
|
|
166
|
+
+ const origins = new Set([
|
|
167
|
+
+ `http://localhost:${normalizedPort}`,
|
|
168
|
+
+ `http://127.0.0.1:${normalizedPort}`,
|
|
169
|
+
+ `http://0.0.0.0:${normalizedPort}`,
|
|
170
|
+
+ ]);
|
|
171
|
+
+ const currentHost = (window.location && window.location.hostname) ? window.location.hostname.trim() : '';
|
|
172
|
+
+ if (currentHost) {
|
|
173
|
+
+ origins.add(`http://${currentHost}:${normalizedPort}`);
|
|
174
|
+
+ }
|
|
175
|
+
+ return Array.from(origins);
|
|
176
|
+
+ }
|
|
177
|
+
+
|
|
178
|
+
|
|
179
|
+
// ========== AI Providers & Models ==========
|
|
180
|
+
const PROVIDERS = {
|
|
181
|
+
@@ -1581,6 +1595,9 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
182
|
+
port: 18791,
|
|
183
|
+
mode: 'local',
|
|
184
|
+
bind: '0.0.0.0',
|
|
185
|
+
+ controlUi: {
|
|
186
|
+
+ allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
187
|
+
+ },
|
|
188
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
@@ -1815,7 +1832,7 @@ model:
|
|
192
|
+
? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 & '
|
|
193
|
+
: '';
|
|
194
|
+
// Patch config on every startup to keep gateway settings stable
|
|
195
|
+
- const patchCmd = `node -e \\"const fs=require('fs'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0'});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
|
|
196
|
+
+ const patchCmd = `node -e \\"const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\\\`http://\\\${entry.address}:18791\\\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
|
|
197
|
+
// Auto-approve device pairing after gateway starts (required since v2026.3.x)
|
|
198
|
+
const autoApproveCmd = '(while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done) & ';
|
|
199
|
+
const finalCmd = `CMD sh -c "${pluginInstallCmd}${patchCmd}${browserPrefix}${autoApproveCmd}${gatewayCmd}"`;
|
|
200
|
+
@@ -2875,7 +2892,7 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
201
|
+
arr.push('timeout /t 5 /nobreak >nul');
|
|
202
|
+
} else {
|
|
203
|
+
arr.push('npm install -g 9router');
|
|
204
|
+
- arr.push('nohup 9router -n -l -H 0.0.0.0 -p 20128 --skip-update >/tmp/9router.log 2>&1 &');
|
|
205
|
+
+ arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 node "$(npm root -g)/9router/app/server.js" >/tmp/9router.log 2>&1 &');
|
|
206
|
+
arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
|
|
207
|
+
arr.push('sleep 3');
|
|
208
|
+
}
|
|
209
|
+
@@ -3040,6 +3057,9 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
210
|
+
port: 18791,
|
|
211
|
+
mode: 'local',
|
|
212
|
+
bind: '0.0.0.0',
|
|
213
|
+
+ controlUi: {
|
|
214
|
+
+ allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
215
|
+
+ },
|
|
216
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
@@ -3153,6 +3173,9 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
220
|
+
port: basePort,
|
|
221
|
+
mode: 'local',
|
|
222
|
+
bind: '0.0.0.0',
|
|
223
|
+
+ controlUi: {
|
|
224
|
+
+ allowedOrigins: getGatewayAllowedOrigins(basePort),
|
|
225
|
+
+ },
|
|
226
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
@@ -3536,7 +3559,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
230
|
+
appendShWriteCommands(vps, sharedNativeFileMap());
|
|
231
|
+
vps.push('echo "--- Starting shared gateway via PM2 ---"');
|
|
232
|
+
if (is9Router) {
|
|
233
|
+
- vps.push('pm2 start --name openclaw-multibot-9router -- sh -c "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
234
|
+
+ vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-multibot-9router --interpreter "$(command -v node)"');
|
|
235
|
+
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
236
|
+
}
|
|
237
|
+
vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
|
|
238
|
+
@@ -3549,7 +3572,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
239
|
+
} else {
|
|
240
|
+
appendShWriteCommands(vps, botFiles(0));
|
|
241
|
+
if (is9Router) {
|
|
242
|
+
- vps.push('pm2 start --name openclaw-9router -- sh -c "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
243
|
+
+ vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-9router --interpreter "$(command -v node)"');
|
|
244
|
+
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
245
|
+
}
|
|
246
|
+
vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
|
|
247
|
+
diff --git a/tests/smoke-cli-logic.mjs b/tests/smoke-cli-logic.mjs
|
|
248
|
+
index 23c1982..0827726 100644
|
|
249
|
+
--- a/tests/smoke-cli-logic.mjs
|
|
250
|
+
+++ b/tests/smoke-cli-logic.mjs
|
|
251
|
+
@@ -107,6 +107,15 @@ checks.push(() => expectMatch(
|
|
252
|
+
'CLI must resolve the correct native 9Router data directory on both Windows and Unix'
|
|
253
|
+
));
|
|
254
|
+
|
|
255
|
+
+checks.push(() => expect(
|
|
256
|
+
+ cli.includes('function getGatewayAllowedOrigins(port) {')
|
|
257
|
+
+ && cli.includes('Object.values(os.networkInterfaces() || {})')
|
|
258
|
+
+ && cli.includes('`http://localhost:${normalizedPort}`')
|
|
259
|
+
+ && cli.includes('`http://127.0.0.1:${normalizedPort}`')
|
|
260
|
+
+ && cli.includes('`http://0.0.0.0:${normalizedPort}`'),
|
|
261
|
+
+ 'CLI must derive control UI allowed origins from localhost plus non-internal IPv4 interfaces'
|
|
262
|
+
+));
|
|
263
|
+
+
|
|
264
|
+
checks.push(() => expect(
|
|
265
|
+
cli.includes("Removed smart-route (no active providers)")
|
|
266
|
+
&& cli.includes("if (!a.length) {")
|
|
267
|
+
@@ -187,6 +196,18 @@ checks.push(() => expectMatch(
|
|
268
|
+
'Native 9Router config must target localhost instead of the Docker hostname'
|
|
269
|
+
));
|
|
270
|
+
|
|
271
|
+
+checks.push(() => expectMatch(
|
|
272
|
+
+ cli,
|
|
273
|
+
+ /controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791\)/s,
|
|
274
|
+
+ 'Native shared gateway config must seed control UI allowed origins'
|
|
275
|
+
+));
|
|
276
|
+
+
|
|
277
|
+
+checks.push(() => expectMatch(
|
|
278
|
+
+ cli,
|
|
279
|
+
+ /controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791 \+ \(isMultiBot \? bIndex : 0\)\)/s,
|
|
280
|
+
+ 'Native per-bot gateway config must seed control UI allowed origins for each port'
|
|
281
|
+
+));
|
|
282
|
+
+
|
|
283
|
+
checks.push(() => expectMatch(
|
|
284
|
+
cli,
|
|
285
|
+
/channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'pairing',\s*autoReply: true/s,
|
|
286
|
+
@@ -195,7 +216,7 @@ checks.push(() => expectMatch(
|
|
287
|
+
|
|
288
|
+
checks.push(() => expectMatch(
|
|
289
|
+
cli,
|
|
290
|
+
- /function startNative9RouterPm2\(\{ isVi, projectDir, appName, syncScriptPath \}\) \{[\s\S]*resolveCommandOnPath\('9router'\)[\s\S]*execFileSync\('pm2'[\s\S]*--interpreter'?,?[\s\S]*none[\s\S]*'-n'[\s\S]*'-l'[\s\S]*'--skip-update'[\s\S]*nohup "\$\{process\.execPath\}" "\$\{normalizedSyncScriptPath\}" >\/tmp\/\$\{syncAppName\}\.log 2>&1 &[\s\S]*runPm2Save\(\{ projectDir, isVi \}\)/s,
|
|
291
|
+
+ /function startNative9RouterPm2\(\{ isVi, projectDir, appName, syncScriptPath \}\) \{[\s\S]*resolveNative9RouterDesktopLaunch\(\)[\s\S]*execFileSync\('pm2'[\s\S]*routerLaunch\.command[\s\S]*--interpreter'?,?[\s\S]*none[\s\S]*routerLaunch\.args[\s\S]*routerLaunch\.env[\s\S]*nohup "\$\{process\.execPath\}" "\$\{normalizedSyncScriptPath\}" >\/tmp\/\$\{syncAppName\}\.log 2>&1 &[\s\S]*runPm2Save\(\{ projectDir, isVi \}\)/s,
|
|
292
|
+
'VPS native 9Router flow must start a standalone 9Router dashboard on port 20128 via PM2'
|
|
293
|
+
));
|
|
294
|
+
|
|
295
|
+
@@ -207,8 +228,8 @@ checks.push(() => expectMatch(
|
|
296
|
+
|
|
297
|
+
checks.push(() => expectMatch(
|
|
298
|
+
cli,
|
|
299
|
+
- /function resolveNative9RouterDesktopLaunch\(\) \{[\s\S]*process\.platform === 'win32'[\s\S]*npm root -g[\s\S]*9router', 'app', 'server\.js'[\s\S]*PORT: '20128'[\s\S]*HOSTNAME: '0\.0\.0\.0'[\s\S]*command: '9router'[\s\S]*\['-n', '-l', '-H', '0\.0\.0\.0', '-p', '20128', '--skip-update'\]/s,
|
|
300
|
+
- 'Native desktop 9Router launch must bypass the interactive CLI menu on Windows while preserving the standard CLI launch elsewhere'
|
|
301
|
+
+ /function resolveNative9RouterDesktopLaunch\(\) \{[\s\S]*command: process\.execPath[\s\S]*getGlobalNpmRoot\(\), '9router', 'app', 'server\.js'[\s\S]*PORT: '20128'[\s\S]*HOSTNAME: '0\.0\.0\.0'/s,
|
|
302
|
+
+ 'Native desktop 9Router launch must bypass the interactive CLI menu by running the 9Router server entry directly'
|
|
303
|
+
));
|
|
304
|
+
|
|
305
|
+
checks.push(() => expectMatch(
|
|
306
|
+
@@ -292,7 +313,7 @@ checks.push(() => expectMatch(
|
|
307
|
+
|
|
308
|
+
checks.push(() => expectMatch(
|
|
309
|
+
setup,
|
|
310
|
+
- /function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*start "9Router" cmd \/k "9router -n -l -H 0\.0\.0\.0 -p 20128 --skip-update"[\s\S]*nohup 9router -n -l -H 0\.0\.0\.0 -p 20128 --skip-update[\s\S]*9router-smart-route-sync\.js/s,
|
|
311
|
+
+ /function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*start "9Router" cmd \/k "9router -n -l -H 0\.0\.0\.0 -p 20128 --skip-update"[\s\S]*nohup env PORT=20128 HOSTNAME=0\.0\.0\.0 node "\$\(npm root -g\)\/9router\/app\/server\.js"[\s\S]*9router-smart-route-sync\.js/s,
|
|
312
|
+
'Native script generation must install and start a standalone 9Router dashboard on port 20128'
|
|
313
|
+
));
|
|
314
|
+
|
|
315
|
+
@@ -340,13 +361,13 @@ checks.push(() => expect(
|
|
316
|
+
|
|
317
|
+
checks.push(() => expectMatch(
|
|
318
|
+
setup,
|
|
319
|
+
- /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw-multibot/s,
|
|
320
|
+
+ /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*PORT=20128 HOSTNAME=0\.0\.0\.0 pm2 start "\$\(npm root -g\)\/9router\/app\/server\.js" --name openclaw-multibot-9router --interpreter "\$\(command -v node\)"[\s\S]*pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw-multibot/s,
|
|
321
|
+
'VPS multi-bot native script must start the shared gateway via PM2'
|
|
322
|
+
));
|
|
323
|
+
|
|
324
|
+
checks.push(() => expectMatch(
|
|
325
|
+
setup,
|
|
326
|
+
- /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*pm2 start --name openclaw -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw/s,
|
|
327
|
+
+ /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*PORT=20128 HOSTNAME=0\.0\.0\.0 pm2 start "\$\(npm root -g\)\/9router\/app\/server\.js" --name openclaw-9router --interpreter "\$\(command -v node\)"[\s\S]*pm2 start --name openclaw -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw/s,
|
|
328
|
+
'VPS single-bot native script must start one bot via PM2'
|
|
329
|
+
));
|
|
330
|
+
|
|
331
|
+
@@ -374,6 +395,32 @@ checks.push(() => expectMatch(
|
|
332
|
+
'Wizard copy must mention native auto-login and still show the dedicated Docker QR login command'
|
|
333
|
+
));
|
|
334
|
+
|
|
335
|
+
+checks.push(() => expect(
|
|
336
|
+
+ setup.includes('function getGatewayAllowedOrigins(port) {')
|
|
337
|
+
+ && setup.includes('window.location')
|
|
338
|
+
+ && setup.includes('`http://localhost:${normalizedPort}`')
|
|
339
|
+
+ && setup.includes('`http://127.0.0.1:${normalizedPort}`'),
|
|
340
|
+
+ 'Web wizard must expose a helper that seeds likely control UI origins'
|
|
341
|
+
+));
|
|
342
|
+
+
|
|
343
|
+
+checks.push(() => expectMatch(
|
|
344
|
+
+ setup,
|
|
345
|
+
+ /controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(18791\)/s,
|
|
346
|
+
+ 'Web wizard single-bot gateway config must seed control UI allowed origins'
|
|
347
|
+
+));
|
|
348
|
+
+
|
|
349
|
+
+checks.push(() => expectMatch(
|
|
350
|
+
+ setup,
|
|
351
|
+
+ /controlUi:\s*\{\s*allowedOrigins: getGatewayAllowedOrigins\(basePort\)/s,
|
|
352
|
+
+ 'Web wizard per-bot gateway config must seed control UI allowed origins'
|
|
353
|
+
+));
|
|
354
|
+
+
|
|
355
|
+
+checks.push(() => expectMatch(
|
|
356
|
+
+ setup,
|
|
357
|
+
+ /const patchCmd = `node -e \\\\"const fs=require\('fs'\),os=require\('os'\),p='\/root\/\.openclaw\/openclaw\.json';if\(fs\.existsSync\(p\)\)\{[\s\S]*allowedOrigins:Array\.from\(a\)/s,
|
|
358
|
+
+ 'Web wizard Docker patch command must add interface-based control UI allowed origins'
|
|
359
|
+
+));
|
|
360
|
+
+
|
|
361
|
+
for (const check of checks) {
|
|
362
|
+
check();
|
|
363
|
+
}
|