create-openclaw-bot 5.1.10 → 5.1.12

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 CHANGED
@@ -1,6 +1,26 @@
1
- # Changelog (English)
1
+ # Changelog (English)
2
2
 
3
3
 
4
+
5
+ ## [5.1.12] — 2026-04-07
6
+
7
+ ### 🧠 Expanded Skills & Auto-Select Multi-Bot Relay Plugin
8
+
9
+ - **3-Column Skill Grid**: Skill cards now display 3 per row instead of 4 — wider cards, better readability.
10
+ - **7 New ClawHub Skills**: Added `Web Search`, `GitHub`, `Google Calendar`, `Google Drive`, `Google Sheets`, `Notion`, `Slack` — covering the most common productivity workflows available on the OpenClaw dashboard.
11
+ - **Telegram Multi-Bot Relay Auto-Select**: When multiple Telegram bots are selected (botCount ≥ 2), the `telegram-multibot-relay` plugin is automatically checked and written to `openclaw.json → plugins.entries`. Switching back to 1 bot deselects it.
12
+ - **Plugin Selections → openclaw.json**: All plugins selected by the user (Voice Call, Matrix, MS Teams, Nostr...) are now injected into `plugins.entries` so the OpenClaw Dashboard receives the correct `enabled` state. Unselected = disabled.
13
+ - **Fix Step 3 "Next" disabled**: Removed mandatory `cfg-user-info` requirement (it's optional), fixed multi-bot validation to use `cfg-bot-tab-name`.
14
+ - **Fix Step 4 multi-bot token validation**: Now validates `key-bot-token-0` instead of `key-bot-token` in Telegram multi-bot mode.
15
+ - **Fix native multi-bot AGENTS.md missing security rules**: Security rules are now appended to each bot's AGENTS.md during native multi-bot deployment.
16
+
17
+ ## [5.1.11] — 2026-04-07
18
+
19
+ ### 🌟 Zalo Personal DM Policy
20
+
21
+ - **Open Zalo Inboxes**: The default `dmPolicy` for Zalo Personal deployments has been changed from `pairing` to `open`. This allows any user on the Zalo network to interact with the AI assistant immediately without requiring explicit device pairing approvals natively.
22
+
23
+
4
24
  ## [5.1.10] — 2026-04-07
5
25
 
6
26
  ### 🌟 Native UI Auto-Approve Bypasser
package/CHANGELOG.vi.md CHANGED
@@ -1,6 +1,25 @@
1
- # Changelog (Tiếng Việt)
1
+ # Changelog (Tiếng Việt)
2
2
 
3
3
 
4
+
5
+ ## [5.1.12] — 2026-04-07
6
+
7
+ ### 🧠 Thêm Skills & Tự động chọn Plugin Relay
8
+
9
+ - **Grid Skills 3 cột**: Layout mới 3 card/hàng thay vì 4, card rộng rãi hơn, dễ đọc hơn.
10
+ - **7 Skills mới từ ClawHub**: Bổ sung đầy đủ `Web Search`, `GitHub`, `Google Calendar`, `Google Drive`, `Google Sheets`, `Notion`, `Slack` — phủ khắp các tác vụ năng suất phổ biến nhất trên OpenClaw dashboard.
11
+ - **Plugin Telegram Multi-Bot Relay tự động**: Khi chọn nhiều bot Telegram (botCount ≥ 2), plugin `telegram-multibot-relay` được tự động tick chọn và ghi vào `openclaw.json → plugins.entries`. Khi quay về 1 bot, plugin bị bỏ chọn.
12
+ - **Plugin selections → openclaw.json**: Tất cả plugin được user chọn (Voice Call, Matrix, MS Teams, Nostr...) đều được inject vào `plugins.entries` để Dashboard OpenClaw nhận trạng thái `enabled` đúng. Không chọn = không bật.
13
+ - **Fix Step 3 "Tiếp theo" bị disabled**: Bỏ yêu cầu bắt buộc `cfg-user-info` (optional), sửa multi-bot check dùng `cfg-bot-tab-name`.
14
+ - **Fix Step 4 multi-bot token**: Validate `key-bot-token-0` thay vì `key-bot-token` khi multi-bot Telegram.
15
+ - **Fix AGENTS.md native multi-bot thiếu quy tắc bảo mật**: Inject `securityRules` vào cuối AGENTS.md của từng bot trong native multi-bot deployment.
16
+
17
+
18
+ ### 🌟 Đổi Chính Sách Bảo Mật Zalo Personal
19
+
20
+ - **Thả Ga Inbox Zalo Cầm Tay**: Lược bỏ rào cản duyệt bảo mật của Zalo Personal. Thông số `dmPolicy` trên cài đặt Zalo cá nhân đã được chuyển mặc định từ `pairing` sang `open`. Bây giờ bất cứ ai trên mạng lưới Zalo nhắn tin vào tài khoản của Bot đều sẽ được AI tự động tiếp đón ngay lập tức thay vì bị chặn lại chờ bạn duyệt lệnh kết nối E2E!
21
+
22
+
4
23
  ## [5.1.10] — 2026-04-07
5
24
 
6
25
  ### 🌟 Tự động Auto-Approve Thiết Bị cho Native 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.10-0EA5E9?style=for-the-badge" alt="Version 5.1.10" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.11-0EA5E9?style=for-the-badge" alt="Version 5.1.11" /></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.10
27
+ ## 🆕 What's new in v5.1.11
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.10 for me.
115
+ Read SETUP.md and set up OpenClaw v5.1.11 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.10-0EA5E9?style=for-the-badge" alt="Version 5.1.10" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.11-0EA5E9?style=for-the-badge" alt="Version 5.1.11" /></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.10
27
+ ## 🆕 Có gì mới trong v5.1.11
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.10 for me.
115
+ Read SETUP.md and set up OpenClaw v5.1.11 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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { input, select, checkbox, confirm } from '@inquirer/prompts';
4
4
  import fs from 'fs-extra';
@@ -2104,12 +2104,12 @@ ${hasBrowserDesktop ? ` extra_hosts:
2104
2104
  };
2105
2105
  }
2106
2106
  botConfig.channels['telegram'] = telegramConfig;
2107
- } else if (channelKey === 'zalo-personal') {
2108
- botConfig.channels['zalouser'] = {
2109
- enabled: true,
2110
- dmPolicy: 'pairing',
2111
- autoReply: true
2112
- };
2107
+ } else if (channelKey === 'zalo-personal') {
2108
+ botConfig.channels['zalouser'] = {
2109
+ enabled: true,
2110
+ dmPolicy: 'open',
2111
+ autoReply: true
2112
+ };
2113
2113
  } else if (channelKey === 'zalo-bot') {
2114
2114
  botConfig.channels['zalo'] = { enabled: true, provider: 'official_account' };
2115
2115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.1.10",
3
+ "version": "5.1.12",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "cli.js",
6
6
  "bin": {
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,21 +47,21 @@
47
47
  apiKey: '',
48
48
  projectPath: '',
49
49
  },
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
- }
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
+ }
65
65
 
66
66
 
67
67
  // ========== AI Providers & Models ==========
@@ -161,6 +161,14 @@
161
161
 
162
162
  // ========== Available Plugins (npm packages — runtime/channel extensions) ==========
163
163
  const PLUGINS = [
164
+ {
165
+ id: 'telegram-multibot-relay',
166
+ name: 'Telegram Multi-Bot Relay',
167
+ icon: '🤝',
168
+ descVi: 'Điều phối nhiều bot Telegram trong cùng group — tự động khi chọn nhiều bot', descEn: 'Coordinate multiple Telegram bots in one group — auto-selected with multi-bot',
169
+ package: 'telegram-multibot-relay',
170
+ hidden: true, // hidden in UI, auto-selected programmatically
171
+ },
164
172
  {
165
173
  id: 'voice-call',
166
174
  name: 'Voice Call',
@@ -193,7 +201,6 @@
193
201
 
194
202
  // ========== Available Skills (ClawHub registry — agent capabilities) ==========
195
203
  const SKILLS = [
196
- // Web Search removed — OpenClaw has native search built-in (no Tavily key needed)
197
204
  {
198
205
  id: 'browser',
199
206
  name: 'Browser Automation ⭐(Khuyên dùng)',
@@ -227,7 +234,7 @@
227
234
  id: 'image-gen',
228
235
  name: 'Image Generation',
229
236
  icon: '🎨',
230
- descVi: 'Tạo ảnh bằng AI (DALL·E, Flux...)', descEn: 'Generate images using AI (DALL-E, Flux...)',
237
+ descVi: 'Tạo ảnh bằng AI (DALL·E, Flux, Midjourney...)', descEn: 'Generate images via AI (DALL-E, Flux, Midjourney...)',
231
238
  slug: 'image-gen',
232
239
  noteVi: 'Dùng chung OPENAI_API_KEY (DALL-E) hoặc thêm FLUX_API_KEY', noteEn: 'Uses OPENAI_API_KEY (DALL-E) or FLUX_API_KEY',
233
240
  envVars: ['# FLUX_API_KEY=<your_flux_key> # chỉ cần nếu dùng Flux'],
@@ -243,11 +250,36 @@
243
250
  id: 'email',
244
251
  name: 'Email Assistant',
245
252
  icon: '📧',
246
- descVi: 'Quản lý, soạn, tóm tắt email', descEn: 'Manage, compose, summarize emails',
253
+ descVi: 'Quản lý, soạn, tóm tắt email (Gmail, Outlook...)', descEn: 'Manage, compose, summarize emails (Gmail, Outlook...)',
247
254
  slug: 'email-assistant',
248
255
  noteVi: 'Cần cấu hình SMTP trong .env', noteEn: 'Requires SMTP configuration in .env',
249
256
  envVars: ['SMTP_HOST=smtp.gmail.com', 'SMTP_PORT=587', 'SMTP_USER=<your_email>', 'SMTP_PASS=<your_app_password>'],
250
257
  },
258
+ {
259
+ id: 'web-search',
260
+ name: 'Web Search',
261
+ icon: '🔍',
262
+ descVi: 'Tìm kiếm web thời gian thực (DuckDuckGo) — không cần API key', descEn: 'Real-time web search (DuckDuckGo) — no API key needed',
263
+ slug: 'web-search',
264
+ },
265
+ {
266
+ id: 'notion',
267
+ name: 'Notion',
268
+ icon: '📓',
269
+ descVi: 'Tạo, chỉnh sửa trang và database Notion', descEn: 'Create and edit Notion pages and databases',
270
+ slug: 'notion',
271
+ noteVi: 'Cần Notion Integration Token', noteEn: 'Requires Notion Integration Token',
272
+ envVars: ['NOTION_API_KEY=<your_notion_integration_token>'],
273
+ },
274
+ {
275
+ id: 'slack',
276
+ name: 'Slack',
277
+ icon: '🗨️',
278
+ descVi: 'Gửi tin, react, ghim tin nhắn trong Slack', descEn: 'Send messages, react, pin items in Slack',
279
+ slug: 'slack',
280
+ noteVi: 'Cần Slack Bot Token', noteEn: 'Requires Slack Bot Token',
281
+ envVars: ['SLACK_BOT_TOKEN=<your_slack_bot_token>'],
282
+ },
251
283
  ];
252
284
 
253
285
  // ========== Channel definitions ==========
@@ -530,6 +562,24 @@
530
562
  state.bots.push({ name: '', slashCmd: '', desc: '', provider: 'google', model: 'google/gemini-2.5-flash', token: '', apiKey: '' });
531
563
  }
532
564
 
565
+ // Auto-select telegram-multibot-relay plugin when multi-bot, deselect when single
566
+ const relayId = 'telegram-multibot-relay';
567
+ if (count > 1) {
568
+ if (!state.config.plugins.includes(relayId)) {
569
+ state.config.plugins.push(relayId);
570
+ }
571
+ } else {
572
+ state.config.plugins = state.config.plugins.filter(p => p !== relayId);
573
+ }
574
+ // Sync relay card checkbox if already rendered
575
+ const relayCard = document.querySelector(`.plugin-card[data-plugin="${relayId}"]`);
576
+ if (relayCard) {
577
+ const isSelected = count > 1;
578
+ relayCard.classList.toggle('plugin-card--selected', isSelected);
579
+ const cb = relayCard.querySelector('input[type="checkbox"]');
580
+ if (cb) cb.checked = isSelected;
581
+ }
582
+
533
583
  // Show/hide group option for 2+ bots
534
584
  const groupOpt = document.getElementById('multibot-group-option');
535
585
  if (groupOpt) groupOpt.style.display = count > 1 ? '' : 'none';
@@ -922,19 +972,30 @@
922
972
  // Step 1 (env): always valid
923
973
  // Step 2 (channel): require selection
924
974
  if (state.currentStep === 2 && !state.channel) isDisabled = true;
925
- // Step 3 (bot config): require bot name
975
+ // Step 3 (bot config): require at least one bot name
926
976
  if (state.currentStep === 3) {
927
- const nameVal = document.getElementById('cfg-name')?.value?.trim();
928
- const userInfoVal = document.getElementById('cfg-user-info')?.value?.trim();
929
- if (!nameVal || !userInfoVal) isDisabled = true;
977
+ if (state.botCount > 1) {
978
+ // Multi-bot: require name for the currently active bot tab
979
+ const tabNameVal = document.getElementById('cfg-bot-tab-name')?.value?.trim();
980
+ if (!tabNameVal) isDisabled = true;
981
+ } else {
982
+ // Single bot: require cfg-name or the shared tab name field
983
+ const nameVal = document.getElementById('cfg-name')?.value?.trim()
984
+ || document.getElementById('cfg-bot-tab-name')?.value?.trim();
985
+ if (!nameVal) isDisabled = true;
986
+ }
930
987
  }
931
988
  // Step 4 (api keys): require token/key
932
989
  if (state.currentStep === 4) {
933
- const botTokenEl = document.getElementById('key-bot-token');
934
990
  const apiKeyEl = document.getElementById('key-api-key');
935
991
  const provider = PROVIDERS[state.config.provider];
936
- if ((state.channel === 'telegram' || state.channel === 'zalo-bot') && botTokenEl) {
937
- if (!botTokenEl.value.trim()) isDisabled = true;
992
+ if (state.channel === 'telegram' && state.botCount > 1) {
993
+ // Multi-bot Telegram: require at least the first bot's token
994
+ const firstTokenEl = document.getElementById('key-bot-token-0');
995
+ if (!firstTokenEl || !firstTokenEl.value.trim()) isDisabled = true;
996
+ } else if (state.channel === 'telegram' || state.channel === 'zalo-bot') {
997
+ const botTokenEl = document.getElementById('key-bot-token');
998
+ if (!botTokenEl || !botTokenEl.value.trim()) isDisabled = true;
938
999
  }
939
1000
  if (provider && !provider.isProxy && !provider.isLocal && provider.envKey && apiKeyEl) {
940
1001
  if (!apiKeyEl.value.trim()) isDisabled = true;
@@ -1010,9 +1071,11 @@
1010
1071
  }
1011
1072
 
1012
1073
  // Plugins grid (npm packages — extra channels/extensions)
1074
+ // Filter out hidden plugins from user-facing grid
1075
+ const visiblePlugins = PLUGINS.filter((p) => !p.hidden);
1013
1076
  const pluginGrid = document.getElementById('extra-plugin-grid');
1014
1077
  if (pluginGrid) {
1015
- pluginGrid.innerHTML = PLUGINS.map((p) => `
1078
+ pluginGrid.innerHTML = visiblePlugins.map((p) => `
1016
1079
  <label class="plugin-card" data-plugin="${p.id}">
1017
1080
  <input type="checkbox" class="plugin-checkbox" value="${p.id}" onchange="window.__togglePlugin('${p.id}', this.checked)">
1018
1081
  <div class="plugin-card__icon">${p.icon}</div>
@@ -1591,15 +1654,15 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
1591
1654
  commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
1592
1655
  channels: ch.channelConfig,
1593
1656
  tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
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
- },
1657
+ gateway: {
1658
+ port: 18791,
1659
+ mode: 'local',
1660
+ bind: '0.0.0.0',
1661
+ controlUi: {
1662
+ allowedOrigins: getGatewayAllowedOrigins(18791),
1663
+ },
1664
+ auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
1665
+ },
1603
1666
  };
1604
1667
 
1605
1668
  // 9Router: add proxy endpoint config under models.providers
@@ -1740,6 +1803,17 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
1740
1803
  'telegram-multibot-relay': { enabled: true },
1741
1804
  },
1742
1805
  };
1806
+ } else if (state.config.plugins.length > 0) {
1807
+ // Non-multibot: write selected visible plugins into openclaw.json
1808
+ const pluginEntries = {};
1809
+ state.config.plugins.forEach((pid) => {
1810
+ const plugin = PLUGINS.find((p) => p.id === pid);
1811
+ if (!plugin || plugin.hidden) return;
1812
+ pluginEntries[plugin.package || pid] = { enabled: true };
1813
+ });
1814
+ if (Object.keys(pluginEntries).length > 0) {
1815
+ clawConfig.plugins = { entries: pluginEntries };
1816
+ }
1743
1817
  }
1744
1818
 
1745
1819
  setOutput('out-openclaw-json', JSON.stringify(clawConfig, null, 2));
@@ -1775,19 +1849,19 @@ model:
1775
1849
  // 3. Dockerfile
1776
1850
  const allPlugins = [];
1777
1851
  if (ch.pluginInstall) allPlugins.push(ch.pluginInstall);
1778
- const encodeBase64Utf8 = (value) => btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
1779
- const indentBlock = (text, spaces) => {
1780
- const prefix = ' '.repeat(spaces);
1781
- return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
1782
- };
1783
- const build9RouterComposeEntrypointScript = (syncScriptBase64) => [
1784
- 'npm install -g 9router',
1785
- `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
1786
- 'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
1787
- 'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
1788
- ].join('\n');
1789
-
1790
- state.config.plugins.forEach((pid) => {
1852
+ const encodeBase64Utf8 = (value) => btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
1853
+ const indentBlock = (text, spaces) => {
1854
+ const prefix = ' '.repeat(spaces);
1855
+ return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
1856
+ };
1857
+ const build9RouterComposeEntrypointScript = (syncScriptBase64) => [
1858
+ 'npm install -g 9router',
1859
+ `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
1860
+ 'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
1861
+ 'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
1862
+ ].join('\n');
1863
+
1864
+ state.config.plugins.forEach((pid) => {
1791
1865
  const plug = PLUGINS.find((p) => p.id === pid);
1792
1866
  if (plug) allPlugins.push(plug.package);
1793
1867
  });
@@ -1832,7 +1906,7 @@ model:
1832
1906
  ? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 & '
1833
1907
  : '';
1834
1908
  // Patch config on every startup to keep gateway settings stable
1835
- const patchCmd = `node -e \\"const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\\\`http://\\\${entry.address}:18791\\\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
1909
+ const patchCmd = `node -e \\"const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\\\`http://\\\${entry.address}:18791\\\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
1836
1910
  // Auto-approve device pairing after gateway starts (required since v2026.3.x)
1837
1911
  const autoApproveCmd = '(while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done) & ';
1838
1912
  const finalCmd = `CMD sh -c "${pluginInstallCmd}${patchCmd}${browserPrefix}${autoApproveCmd}${gatewayCmd}"`;
@@ -1844,7 +1918,7 @@ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /v
1844
1918
 
1845
1919
  ARG CACHEBUST=${Date.now()}
1846
1920
  RUN npm install -g openclaw@latest${skillLines}${browserInstallLines}
1847
- RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const files=fs.readdirSync(dir).filter(n=>/\\.js$/.test(n));let patched=0;for(const file of files){const p=path.join(dir,file);let s='';try{s=fs.readFileSync(p,'utf8');}catch{continue;}if(s.includes(to)||!s.includes(from))continue;s=s.replace(from,to);fs.writeFileSync(p,s);patched++;}if(!patched){process.exit(0);}"
1921
+ RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const files=fs.readdirSync(dir).filter(n=>/\\.js$/.test(n));let patched=0;for(const file of files){const p=path.join(dir,file);let s='';try{s=fs.readFileSync(p,'utf8');}catch{continue;}if(s.includes(to)||!s.includes(from))continue;s=s.replace(from,to);fs.writeFileSync(p,s);patched++;}if(!patched){process.exit(0);}"
1848
1922
  WORKDIR /root/.openclaw
1849
1923
 
1850
1924
  EXPOSE 18791
@@ -1863,7 +1937,7 @@ ${finalCmd}`;
1863
1937
  // Background loop inside 9Router container every 30s.
1864
1938
  // Read providerConnections directly from db.json so smart-route survives
1865
1939
  // dashboard auth/response changes in newer 9Router builds.
1866
- const syncScript = `const fs=require('fs');const INTERVAL=30000;const p='/root/.9router/db.json';
1940
+ const syncScript = `const fs=require('fs');const INTERVAL=30000;const p='/root/.9router/db.json';
1867
1941
  const PM={codex:['cx/gpt-5.4','cx/gpt-5.3-codex','cx/gpt-5.3-codex-high','cx/gpt-5.2-codex','cx/gpt-5.2','cx/gpt-5.1-codex-max','cx/gpt-5.1-codex','cx/gpt-5.1','cx/gpt-5-codex'],'claude-code':['cc/claude-opus-4-6','cc/claude-sonnet-4-6','cc/claude-opus-4-5-20251101','cc/claude-sonnet-4-5-20250929','cc/claude-haiku-4-5-20251001'],github:['gh/gpt-5.4','gh/gpt-5.3-codex','gh/gpt-5.2-codex','gh/gpt-5.2','gh/gpt-5.1-codex-max','gh/gpt-5.1-codex','gh/gpt-5.1','gh/gpt-5','gh/gpt-4.1','gh/gpt-4o','gh/claude-opus-4.6','gh/claude-sonnet-4.6','gh/claude-sonnet-4.5','gh/claude-opus-4.5','gh/claude-haiku-4.5','gh/gemini-3-pro-preview','gh/gemini-3-flash-preview','gh/gemini-2.5-pro'],cursor:['cu/default','cu/claude-4.6-opus-max','cu/claude-4.5-opus-high-thinking','cu/claude-4.5-sonnet-thinking','cu/claude-4.5-sonnet','cu/gpt-5.3-codex','cu/gpt-5.2-codex','cu/gemini-3-flash-preview'],kilo:['kc/anthropic/claude-sonnet-4-20250514','kc/anthropic/claude-opus-4-20250514','kc/google/gemini-2.5-pro','kc/google/gemini-2.5-flash','kc/openai/gpt-4.1','kc/deepseek/deepseek-chat'],cline:['cl/anthropic/claude-sonnet-4.6','cl/anthropic/claude-opus-4.6','cl/openai/gpt-5.3-codex','cl/openai/gpt-5.4','cl/google/gemini-3.1-pro-preview'],'gemini-cli':['gc/gemini-3-flash-preview','gc/gemini-3-pro-preview'],iflow:['if/qwen3-coder-plus','if/kimi-k2','if/kimi-k2-thinking','if/glm-4.7','if/deepseek-r1','if/deepseek-v3.2','if/deepseek-v3','if/qwen3-max','if/qwen3-235b','if/iflow-rome-30ba3b'],qwen:['qw/qwen3-coder-plus','qw/qwen3-coder-flash','qw/vision-model','qw/coder-model'],kiro:['kr/claude-sonnet-4.5','kr/claude-haiku-4.5','kr/deepseek-3.2','kr/deepseek-3.1','kr/qwen3-coder-next'],ollama:['ollama/gemma4:e2b','ollama/gemma4:e4b','ollama/gemma4:26b','ollama/gemma4:31b','ollama/qwen3.5','ollama/kimi-k2.5','ollama/glm-5','ollama/glm-4.7-flash','ollama/minimax-m2.5','ollama/gpt-oss:120b'],'kimi-coding':['kmc/kimi-k2.5','kmc/kimi-k2.5-thinking','kmc/kimi-latest'],glm:['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],'glm-cn':['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],minimax:['minimax/MiniMax-M2.7','minimax/MiniMax-M2.5','minimax/MiniMax-M2.1'],kimi:['kimi/kimi-k2.5','kimi/kimi-k2.5-thinking','kimi/kimi-latest'],deepseek:['deepseek/deepseek-chat','deepseek/deepseek-reasoner'],xai:['xai/grok-4','xai/grok-4-fast-reasoning','xai/grok-code-fast-1'],mistral:['mistral/mistral-large-latest','mistral/codestral-latest'],groq:['groq/llama-3.3-70b-versatile','groq/openai/gpt-oss-120b'],cerebras:['cerebras/gpt-oss-120b'],alicode:['alicode/qwen3.5-plus','alicode/qwen3-coder-plus'],openai:['openai/gpt-4o','openai/gpt-4.1'],anthropic:['anthropic/claude-sonnet-4','anthropic/claude-haiku-3.5'],gemini:['gemini/gemini-2.5-flash','gemini/gemini-2.5-pro']};
1868
1942
  console.log('[sync-combo] 9Router sync loop started...');
1869
1943
  const sync = async () => {
@@ -1913,11 +1987,11 @@ const sync = async () => {
1913
1987
  console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models');
1914
1988
  }
1915
1989
  } catch (e) { }
1916
- };
1917
- setTimeout(sync, 5000);
1918
- setInterval(sync, INTERVAL);`;
1919
- const syncScriptBase64 = encodeBase64Utf8(syncScript);
1920
- const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64);
1990
+ };
1991
+ setTimeout(sync, 5000);
1992
+ setInterval(sync, INTERVAL);`;
1993
+ const syncScriptBase64 = encodeBase64Utf8(syncScript);
1994
+ const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64);
1921
1995
 
1922
1996
  let compose;
1923
1997
  if (isMultiBotWizard) {
@@ -1946,14 +2020,14 @@ ${dependsOn}${extraHosts} volumes:
1946
2020
  image: node:22-slim
1947
2021
  container_name: 9router-multibot
1948
2022
  restart: always
1949
- entrypoint:
1950
- - /bin/sh
1951
- - -c
1952
- - |
1953
- ${indentBlock(docker9RouterEntrypointScript, 8)}
1954
- environment:
1955
- - PORT=20128
1956
- - HOSTNAME=0.0.0.0
2023
+ entrypoint:
2024
+ - /bin/sh
2025
+ - -c
2026
+ - |
2027
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
2028
+ environment:
2029
+ - PORT=20128
2030
+ - HOSTNAME=0.0.0.0
1957
2031
  - CI=true
1958
2032
  volumes:
1959
2033
  - 9router-data:/root/.9router
@@ -2039,14 +2113,14 @@ ${extraHostsBlock}
2039
2113
  image: node:22-slim
2040
2114
  container_name: 9router
2041
2115
  restart: always
2042
- entrypoint:
2043
- - /bin/sh
2044
- - -c
2045
- - |
2046
- ${indentBlock(docker9RouterEntrypointScript, 8)}
2047
- environment:
2048
- - PORT=20128
2049
- - HOSTNAME=0.0.0.0
2116
+ entrypoint:
2117
+ - /bin/sh
2118
+ - -c
2119
+ - |
2120
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
2121
+ environment:
2122
+ - PORT=20128
2123
+ - HOSTNAME=0.0.0.0
2050
2124
  - CI=true
2051
2125
  volumes:
2052
2126
  - 9router-data:/root/.9router
@@ -2885,17 +2959,17 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
2885
2959
  // ─── Shared initializer (provider install) ───────────────────────────────
2886
2960
  function providerLines(arr, shell) {
2887
2961
  if (is9Router) {
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
- }
2962
+ if (shell === 'bat') {
2963
+ arr.push('npm install -g 9router');
2964
+ arr.push('start "9Router" cmd /k "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
2965
+ arr.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
2966
+ arr.push('timeout /t 5 /nobreak >nul');
2967
+ } else {
2968
+ arr.push('npm install -g 9router');
2969
+ arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 node "$(npm root -g)/9router/app/server.js" >/tmp/9router.log 2>&1 &');
2970
+ arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
2971
+ arr.push('sleep 3');
2972
+ }
2899
2973
  } else if (isOllama) {
2900
2974
  if (shell === 'bat') {
2901
2975
  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)');
@@ -3053,15 +3127,15 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
3053
3127
  'telegram-multibot-relay': { enabled: true },
3054
3128
  },
3055
3129
  },
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
- },
3130
+ gateway: {
3131
+ port: 18791,
3132
+ mode: 'local',
3133
+ bind: '0.0.0.0',
3134
+ controlUi: {
3135
+ allowedOrigins: getGatewayAllowedOrigins(18791),
3136
+ },
3137
+ auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
3138
+ },
3065
3139
  };
3066
3140
  return JSON.stringify(cfg, null, 2);
3067
3141
  }
@@ -3103,9 +3177,11 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
3103
3177
  files[`.openclaw/${meta.workspaceDir}/SOUL.md`] = isVi
3104
3178
  ? `# Tinh cach\n\n${meta.persona || 'Huu ich that su, gan gui, ngan gon.'}`
3105
3179
  : `# Soul\n\n${meta.persona || 'Helpful, concise, and practical.'}`;
3180
+ const _secRulesForBot = state.config.securityRules || DEFAULT_SECURITY_RULES[isVi ? 'vi' : 'en'];
3106
3181
  files[`.openclaw/${meta.workspaceDir}/AGENTS.md`] = (isVi
3107
- ? `# Huong dan van hanh\n\n## Vai tro\nBan la **${meta.name}**, ${meta.desc.toLowerCase()}.\n\n## Khi nao nen tra loi\n- Trong group, xem user dang goi ban neu tin nhan co alias cua ban: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}\n- Neu user tag username Telegram cua ban thi luon tra loi.\n- Gateway se tu dong tha ack \`👍\` khi nhan message; khong can tu tha them neu ack da hien.\n- Neu user dang goi ro bot khac ${relayTargetNames} thi khong cuop loi.\n\n## Phoi hop\n- Dung \`TEAM.md\` lam nguon su that.\n- Neu user bao ban hoi hoac xin y kien tu ${otherBots.length ? otherBots.map((peer) => `\`${peer.name}\``).join(', ') : '`bot khac`'} thi dung agent-to-agent noi bo ngay trong turn hien tai.\n- Neu ban la bot mo loi, chi gui 1 cau mo dau ngan roi handoff ngay. Khong tu noi thay bot dich tru khi handoff that bai ro rang.\n- Khi handoff, phai goi dung agent id ky thuat ${relayTargetIds}, khong dung ten hien thi.\n- Neu ban la bot dich nhan handoff, hay tra loi cong khai ngay trong cung Telegram chat/thread bang chinh account cua minh. Uu tien tra loi co \`[[reply_to_current]]\`; neu can, dung Telegram send/sendMessage action thay vi chi output thuong.\n- Khong bao user phai tag lai bot kia neu ban co the hoi noi bo duoc.`
3108
- : `# Operating Manual\n\n## Role\nYou are **${meta.name}**, ${meta.desc.toLowerCase()}.\n\n## When To Reply\n- In group chats, treat the message as addressed to you if it contains one of your aliases: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}\n- Always reply when your Telegram username is tagged.\n- The gateway auto-sends the \`👍\` ack reaction on inbound messages; do not duplicate it manually if it already appeared.\n- If the message is clearly calling another bot such as ${relayTargetNames}, do not hijack it.\n\n## Coordination\n- Use \`TEAM.md\` as the source of truth.\n- If the user asks you to consult ${otherBots.length ? otherBots.map((peer) => `\`${peer.name}\``).join(', ') : '`another bot`'}, use internal agent-to-agent handoff in the same turn.\n- If you are the caller bot, send only one short opener then hand off immediately. Do not speak for the target bot unless the handoff clearly fails.\n- When handing off, use the exact technical agent id ${relayTargetIds}, not the display name.\n- If you are the target bot receiving a handoff, publish the real answer into the same Telegram chat/thread from your own account. Prefer replying with \`[[reply_to_current]]\`; if needed, use the Telegram send/sendMessage action instead of plain assistant output.\n- Do not ask the user to tag the other bot again if you can consult internally.`);
3182
+ ? `# Huong dan van hanh\n\n## Vai tro\nBan la **${meta.name}**, ${meta.desc.toLowerCase()}.\n\n## Khi nao nen tra loi\n- Trong group, xem user dang goi ban neu tin nhan co alias cua ban: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}\n- Neu user tag username Telegram cua ban thi luon tra loi.\n- Gateway se tu dong tha ack \`👍\` khi nhan message; khong can tu tha them neu ack da hien.\n- Neu user dang goi ro bot khac ${relayTargetNames} thi khong cuop loi.\n\n## Phoi hop\n- Dung \`TEAM.md\` lam nguon su that.\n- Neu user bao ban hoi hoac xin y kien tu ${otherBots.length ? otherBots.map((peer) => `\`${peer.name}\``).join(', ') : '`bot khac`'} thi dung agent-to-agent noi bo ngay trong turn hien tai.\n- Neu ban la bot mo loi, chi gui 1 cau mo dau ngan roi handoff ngay. Khong tu noi thay bot dich tru khi handoff that bai ro rang.\n- Khi handoff, phai goi dung agent id ky thuat ${relayTargetIds}, khong dung ten hien thi.\n- Neu ban la bot dich nhan handoff, hay tra loi cong khai ngay trong cung Telegram chat/thread bang chinh account cua minh. Uu tien tra loi co \`[[reply_to_current]]\`; neu can, dung Telegram send/sendMessage action thay vi chi output thuong.\n- Khong bao user phai tag lai bot kia neu ban co the hoi noi bo duoc.\n\n${_secRulesForBot}`
3183
+ : `# Operating Manual\n\n## Role\nYou are **${meta.name}**, ${meta.desc.toLowerCase()}.\n\n## When To Reply\n- In group chats, treat the message as addressed to you if it contains one of your aliases: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}\n- Always reply when your Telegram username is tagged.\n- The gateway auto-sends the \`👍\` ack reaction on inbound messages; do not duplicate it manually if it already appeared.\n- If the message is clearly calling another bot such as ${relayTargetNames}, do not hijack it.\n\n## Coordination\n- Use \`TEAM.md\` as the source of truth.\n- If the user asks you to consult ${otherBots.length ? otherBots.map((peer) => `\`${peer.name}\``).join(', ') : '`another bot`'}, use internal agent-to-agent handoff in the same turn.\n- If you are the caller bot, send only one short opener then hand off immediately. Do not speak for the target bot unless the handoff clearly fails.\n- When handing off, use the exact technical agent id ${relayTargetIds}, not the display name.\n- If you are the target bot receiving a handoff, publish the real answer into the same Telegram chat/thread from your own account. Prefer replying with \`[[reply_to_current]]\`; if needed, use the Telegram send/sendMessage action instead of plain assistant output.\n- Do not ask the user to tag the other bot again if you can consult internally.\n\n${_secRulesForBot}`);
3184
+
3109
3185
  files[`.openclaw/${meta.workspaceDir}/TEAM.md`] = teamMd;
3110
3186
  files[`.openclaw/${meta.workspaceDir}/RELAY.md`] = isVi
3111
3187
  ? `# Telegram Relay Playbook\n\n## Muc tieu\n- Cho phep bot mo loi goi bot dich noi bo, sau do bot dich tra loi cong khai bang chinh account cua minh.\n\n## Protocol\n1. Bot mo loi gui 1 cau ngan xac nhan se hoi bot dich.\n2. Bot mo loi handoff noi bo bang dung agent id trong \`TEAM.md\`.\n3. Bot dich tra loi cong khai trong cung chat/thread hien tai.\n4. Neu thay \`[[reply_to_current]]\` hoac Telegram send/sendMessage action kha dung, uu tien dung de bam dung message goc.\n5. Neu handoff that bai ro rang, chi bot mo loi moi duoc fallback tom tat.\n`
@@ -3169,15 +3245,15 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
3169
3245
  },
3170
3246
  commands: { native: 'auto', nativeSkills: 'auto', restart: true },
3171
3247
  channels: channelConfig,
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
- },
3248
+ gateway: {
3249
+ port: basePort,
3250
+ mode: 'local',
3251
+ bind: '0.0.0.0',
3252
+ controlUi: {
3253
+ allowedOrigins: getGatewayAllowedOrigins(basePort),
3254
+ },
3255
+ auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
3256
+ },
3181
3257
 
3182
3258
  };
3183
3259
  return JSON.stringify(cfg, null, 2);
@@ -3556,12 +3632,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3556
3632
 
3557
3633
  if (isMultiBot) {
3558
3634
  vps.push('echo "--- Creating shared multi-agent runtime ---"');
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
- }
3635
+ appendShWriteCommands(vps, sharedNativeFileMap());
3636
+ vps.push('echo "--- Starting shared gateway via PM2 ---"');
3637
+ if (is9Router) {
3638
+ 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)"');
3639
+ vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
3640
+ }
3565
3641
  vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
3566
3642
  vps.push('pm2 save && pm2 startup');
3567
3643
  vps.push(`echo ""`);
@@ -3569,12 +3645,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3569
3645
  vps.push(`echo "Commands:"`);
3570
3646
  vps.push(`echo " pm2 status # Status gateway"`);
3571
3647
  vps.push(`echo " pm2 logs openclaw-multibot"`);
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
- }
3648
+ } else {
3649
+ appendShWriteCommands(vps, botFiles(0));
3650
+ if (is9Router) {
3651
+ 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)"');
3652
+ vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
3653
+ }
3578
3654
  vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
3579
3655
  vps.push('pm2 save && pm2 startup');
3580
3656
  vps.push('echo "Bot dang chay! Xem log: pm2 logs openclaw"');
package/style.css CHANGED
@@ -788,7 +788,7 @@ body::after {
788
788
  /* ============ Plugin Grid ============ */
789
789
  .plugin-grid {
790
790
  display: grid;
791
- grid-template-columns: repeat(4, 1fr);
791
+ grid-template-columns: repeat(3, 1fr);
792
792
  gap: 12px;
793
793
  }
794
794
 
@@ -218,7 +218,7 @@ checks.push(() => expectMatch(
218
218
 
219
219
  checks.push(() => expectMatch(
220
220
  cli,
221
- /channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'pairing',\s*autoReply: true/s,
221
+ /channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'open',\s*autoReply: true/s,
222
222
  'CLI must configure Zalo Personal under channels.zalouser'
223
223
  ));
224
224