create-openclaw-bot 5.8.2 → 5.8.3

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/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.8.2-0EA5E9?style=for-the-badge" alt="Version 5.8.2" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.3-0EA5E9?style=for-the-badge" alt="Version 5.8.3" /></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>
@@ -23,7 +23,7 @@ A next-generation **Web UI Setup** and management dashboard that automates 100%
23
23
 
24
24
  ---
25
25
 
26
- ## 🆕 What's New in v5.8.2
26
+ ## 🆕 What's New in v5.8.3
27
27
 
28
28
  - 🔄 **Smart Header Update Button**: Instantly upgrades the setup wizard from the UI! The button queries the npm registry dynamically and only reveals itself when a newer release is published.
29
29
  - 📡 **Live Log-Streaming Upgrade**: Kicking off the update automatically executes the migration (running `git pull && npm install && npm run build` for Git clones, or `npm install -g create-openclaw-bot` for NPM installs) while streaming standard outputs in real-time straight to the dashboard's Logs terminal.
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.8.2-0EA5E9?style=for-the-badge" alt="Version 5.8.2" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.3-0EA5E9?style=for-the-badge" alt="Version 5.8.3" /></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>
@@ -23,7 +23,7 @@ Trình cài đặt và quản trị **Web UI Setup** thế hệ mới giúp tự
23
23
 
24
24
  ---
25
25
 
26
- ## 🆕 Có gì mới trong v5.8.2
26
+ ## 🆕 Có gì mới trong v5.8.3
27
27
 
28
28
  - 🔄 **Nút cập nhật Header thông minh**: Nâng cấp trực tiếp setup wizard từ giao diện! Nút cập nhật tự truy vấn npm registry và chỉ hiển thị khi có phiên bản mới hơn.
29
29
  - 📡 **Nâng cấp Stream Log trực tiếp**: Khởi chạy cập nhật sẽ tự động nâng cấp (chạy `git pull && npm install && npm run build` cho bản Git, hoặc `npm install -g create-openclaw-bot` cho bản NPM) và đẩy dòng log theo thời gian thực về tab Logs.
@@ -74,6 +74,30 @@ function detectOs() {
74
74
  return 'linux-desktop';
75
75
  }
76
76
 
77
+ // Blacklist of Windows system/large directories that should never be walked
78
+ const SYSTEM_DIR_BLACKLIST = new Set([
79
+ 'windows', 'program files', 'program files (x86)', 'programdata',
80
+ '$recycle.bin', 'system volume information', 'recovery', 'boot',
81
+ 'perflogs', 'msocache', 'intel', 'amd', 'nvidia',
82
+ '$windows.~bt', '$windows.~ws', 'config.msi', 'documents and settings',
83
+ 'swapfile.sys', 'pagefile.sys', 'hiberfil.sys',
84
+ ]);
85
+
86
+ /** Discover all available drive letters on Windows (A-Z). Returns ['C:\\', 'D:\\', ...] */
87
+ async function getAvailableDrives() {
88
+ if (process.platform !== 'win32') return ['/'];
89
+ const drives = [];
90
+ for (let code = 65; code <= 90; code++) { // A-Z
91
+ const letter = String.fromCharCode(code);
92
+ const drive = `${letter}:\\`;
93
+ try {
94
+ await fsp.access(drive);
95
+ drives.push(drive);
96
+ } catch {}
97
+ }
98
+ return drives.length ? drives : ['C:\\', 'D:\\'];
99
+ }
100
+
77
101
  function recommendedMode(osChoice) {
78
102
  if (osChoice === 'win' || osChoice === 'macos') return 'docker';
79
103
  return 'native';
@@ -566,6 +590,26 @@ function ensureZaloApiChannel(cfg, token) {
566
590
  });
567
591
  }
568
592
 
593
+ function ensureZaloModPluginConfig(entry, cfg) {
594
+ entry.hooks = entry.hooks || {};
595
+ entry.hooks.allowConversationAccess = true;
596
+ entry.config = entry.config || {};
597
+ // Auto-assign dashboardPort = gateway port + 1
598
+ if (!entry.config.dashboardPort) {
599
+ const gwPort = Number(cfg.gateway?.port) || state.gatewayPort || 18789;
600
+ entry.config.dashboardPort = gwPort + 1;
601
+ }
602
+ // Auto-assign botName from first agent name
603
+ if (!entry.config.botName) {
604
+ const agentName = cfg.agents?.list?.[0]?.name;
605
+ if (agentName) entry.config.botName = agentName;
606
+ }
607
+ // Auto-assign zaloDisplayNames from botName
608
+ if ((!entry.config.zaloDisplayNames || entry.config.zaloDisplayNames.length === 0) && entry.config.botName) {
609
+ entry.config.zaloDisplayNames = [entry.config.botName];
610
+ }
611
+ }
612
+
569
613
  function readProjectConfig(projectDir) {
570
614
  const cfgPath = join(projectDir || '', '.openclaw', 'openclaw.json');
571
615
  if (!projectDir || !existsSync(cfgPath)) return null;
@@ -935,6 +979,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
935
979
  if (existsSync(cfgPath)) await fsp.copyFile(cfgPath, `${cfgPath}.bak`);
936
980
  await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
937
981
 
982
+ const hasScheduler = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
938
983
  const files = buildWorkspaceFileMap({
939
984
  isVi: true,
940
985
  botName,
@@ -945,6 +990,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
945
990
  agentWorkspaceDir: workspaceDir,
946
991
  workspacePath: `.openclaw/${workspaceDir}`,
947
992
  hasZaloMod: channel === 'zalo-personal',
993
+ hasScheduler,
948
994
  });
949
995
  const wsRoot = join(openclawHome, workspaceDir);
950
996
  for (const [name, content] of Object.entries(files)) {
@@ -1033,6 +1079,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
1033
1079
  }
1034
1080
  }
1035
1081
 
1082
+ const hasScheduler = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
1036
1083
  const files = buildWorkspaceFileMap({
1037
1084
  isVi: true,
1038
1085
  botName,
@@ -1043,6 +1090,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
1043
1090
  agentWorkspaceDir: workspaceDir,
1044
1091
  workspacePath: `.openclaw/${workspaceDir}`,
1045
1092
  hasZaloMod: channel === 'zalo-personal',
1093
+ hasScheduler,
1046
1094
  });
1047
1095
  const wsRoot = join(projectDir, '.openclaw', workspaceDir);
1048
1096
  for (const [name, content] of Object.entries(files)) {
@@ -1388,6 +1436,23 @@ async function syncDockerInfra(projectDir, force = false) {
1388
1436
  sendLog(`[sync] Updating Docker infrastructure files (v${existingVersion} \u2192 v${SETUP_VERSION})`);
1389
1437
  await fsp.writeFile(join(dockerDir, 'Dockerfile'), docker.dockerfile, 'utf8');
1390
1438
  await fsp.writeFile(join(dockerDir, 'docker-compose.yml'), newCompose, 'utf8');
1439
+ // Preserve zalo-mod dashboard port if plugin is active
1440
+ try {
1441
+ const syncCfg = JSON.parse(await fsp.readFile(cfgPath, 'utf8'));
1442
+ const zmEntry = syncCfg.plugins?.entries?.['zalo-mod'] || syncCfg.plugins?.entries?.['openclaw-zalo-mod'];
1443
+ if (zmEntry?.enabled !== false && zmEntry?.config?.dashboardPort) {
1444
+ const dp = zmEntry.config.dashboardPort;
1445
+ let cc = await fsp.readFile(join(dockerDir, 'docker-compose.yml'), 'utf8');
1446
+ if (!cc.includes(`:${dp}`)) {
1447
+ const gpStr = String(gatewayPort);
1448
+ cc = cc.replace(
1449
+ new RegExp(`^(\\s*-\\s*"(?:\\d+:)?${gpStr}(?::${gpStr})?"\\s*)$`, 'm'),
1450
+ `$1\n - "127.0.0.1:${dp}:${dp}" # zalo-mod dashboard`
1451
+ );
1452
+ await fsp.writeFile(join(dockerDir, 'docker-compose.yml'), cc, 'utf8');
1453
+ }
1454
+ }
1455
+ } catch {}
1391
1456
  await fsp.writeFile(entrypointPath, entryScript, 'utf8');
1392
1457
  if (docker.syncScript) await fsp.writeFile(join(dockerDir, 'sync.js'), docker.syncScript, 'utf8');
1393
1458
  if (docker.patchScript) await fsp.writeFile(join(dockerDir, 'patch-9router.js'), docker.patchScript, 'utf8');
@@ -1678,11 +1743,16 @@ async function findLatestProject(rootProjectDir) {
1678
1743
  join(rootProjectDir, DEFAULT_PROJECT_NAME),
1679
1744
  dirname(rootProjectDir),
1680
1745
  os.homedir(),
1681
- 'D:\\tmp',
1682
1746
  ];
1683
- for (const drive of ['D:\\', 'E:\\']) {
1747
+ // Scan all available drives, walking top-level dirs but skipping system folders
1748
+ const drives = await getAvailableDrives();
1749
+ for (const drive of drives) {
1684
1750
  const entries = await fsp.readdir(drive, { withFileTypes: true }).catch(() => []);
1685
- for (const e of entries) if (e.isDirectory() && !e.name.startsWith('$')) roots.push(join(drive, e.name));
1751
+ for (const e of entries) {
1752
+ if (e.isDirectory() && !e.name.startsWith('$') && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
1753
+ roots.push(join(drive, e.name));
1754
+ }
1755
+ }
1686
1756
  }
1687
1757
  const candidates = [];
1688
1758
  async function walk(dir, depth = 0) {
@@ -1693,7 +1763,11 @@ async function findLatestProject(rootProjectDir) {
1693
1763
  return;
1694
1764
  }
1695
1765
  const entries = await fsp.readdir(dir, { withFileTypes: true }).catch(() => []);
1696
- for (const e of entries) if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules') await walk(join(dir, e.name), depth + 1);
1766
+ for (const e of entries) {
1767
+ if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules' && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
1768
+ await walk(join(dir, e.name), depth + 1);
1769
+ }
1770
+ }
1697
1771
  }
1698
1772
  for (const r of roots) await walk(r);
1699
1773
  candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
@@ -1706,10 +1780,10 @@ async function discoverProjects(rootProjectDir) {
1706
1780
  rootProjectDir,
1707
1781
  dirname(rootProjectDir),
1708
1782
  process.env.OPENCLAW_HOME ? dirname(process.env.OPENCLAW_HOME) : '',
1709
- 'D:\\tmp',
1710
- 'D:\\',
1711
- 'E:\\',
1712
1783
  ];
1784
+ // Add all available drives for scanning
1785
+ const drives = await getAvailableDrives();
1786
+ for (const drive of drives) roots.push(drive);
1713
1787
  const seen = new Set();
1714
1788
  const hits = [];
1715
1789
  async function walk(dir, depth = 0) {
@@ -1743,7 +1817,7 @@ async function discoverProjects(rootProjectDir) {
1743
1817
  const entries = await fsp.readdir(full, { withFileTypes: true }).catch(() => []);
1744
1818
  for (const e of entries) {
1745
1819
  if (!e.isDirectory()) continue;
1746
- if (e.name === 'node_modules' || e.name.startsWith('.git')) continue;
1820
+ if (e.name === 'node_modules' || e.name.startsWith('.git') || SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) continue;
1747
1821
  await walk(join(full, e.name), depth + 1);
1748
1822
  }
1749
1823
  }
@@ -1987,9 +2061,13 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
1987
2061
  cfg.commands.ownerAllowFrom = Array.from(new Set([...(cfg.commands.ownerAllowFrom || []), '*']));
1988
2062
  const cronGuide = `## ⏰ Cron / Lên lịch nhắc nhở (tool: \`cron\`)
1989
2063
  - **Tên tool chính xác:** Tên công cụ là \`cron\` (tuyệt đối không nhầm là \`native\` hay command line bên ngoài).
2064
+ - **⛔ TUYỆT ĐỐI KHÔNG sửa trực tiếp file JSON** như \`jobs.json\`, \`jobs-state.json\` trong thư mục \`.openclaw/cron/\`. Dữ liệu cron được lưu trong SQLite database, file JSON chỉ là legacy format đã ngưng hỗ trợ. Mọi thao tác PHẢI thông qua tool \`cron\`.
1990
2065
  - **Khi tạo cronjob mới (action \`add\`):**
1991
2066
  - **TUYỆT ĐỐI KHÔNG điền trường \`agentId\`** trong object \`job\` (hãy bỏ qua/omitted trường này). Hệ thống OpenClaw sẽ tự động gán chính xác ID của bạn vào job đó.
1992
2067
  - Tuyệt đối **không tự điền** \`agentId\` là \`"bot"\` hay \`"main"\`, vì làm vậy sẽ khiến cronjob thuộc về agent khác và bạn sẽ mất quyền kiểm soát/xóa nó sau này.
2068
+ - **Session:** Luôn dùng \`sessionTarget: "isolated"\` cho các job chạy nền (báo cáo, nhắc nhở, gửi tin nhắn tự động). Chỉ dùng \`"main"\` cho system event/reminder ngắn.
2069
+ - **Timezone:** Luôn chỉ định timezone rõ ràng bằng trường \`tz\` (ví dụ: \`"Asia/Ho_Chi_Minh"\`). Nếu không chỉ định, hệ thống sẽ dùng timezone của Gateway host (thường là UTC) và job sẽ chạy sai giờ.
2070
+ - **Delivery:** Đối với job cần gửi kết quả ra chat, set \`delivery.mode: "announce"\` kèm \`delivery.channel\` và \`delivery.to\`.
1993
2071
  - **Khi user yêu cầu tắt/bật/xóa cronjob:**
1994
2072
  1. **Bước 1 (Tìm kiếm):** Gọi tool \`cron\` với action \`list\` (và \`includeDisabled: true\`) để xem danh sách tất cả cronjob đang chạy trên hệ thống và tìm đúng \`jobId\` phù hợp với yêu cầu.
1995
2073
  2. **Bước 2 (Xử lý):**
@@ -1999,7 +2077,8 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
1999
2077
  3. **Tuyên bố trung thực:** Tuyệt đối không bao giờ trả lời "đã xóa" hay "không có" dựa trên suy đoán của bản thân mà chưa gọi tool \`cron\` để kiểm tra thực tế.
2000
2078
  - Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool \`cron\` (action \`add\`) để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.
2001
2079
  - Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
2002
- - **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố \`group:\` trước ID của group (ví dụ: \`group:3815464776067464419\` hoặc \`group:xxxx\`). Tuyệt đối không được chỉ điền ID thuần túy hệ thống sẽ hiểu nhầm đó một DM chat nhân (direct message) gửi sai địa chỉ.
2080
+ - **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố thích hợp trước ID của group. Với kênh Telegram/Matrix/Discord/Slack, dùng tiền tố \`group:\` (ví dụ: \`group:123456\`). RIÊNG với kênh Zalo (\`zalouser\`), **bắt buộc** phải sử dụng tiền tố \`g:\` (ví dụ: \`g:3815464776067464419\`) để tránh bị OpenClaw core lược bỏ tiền tố gửi nhầm vào DM chat nhân.
2081
+ - **One-shot job:** Dùng schedule kind \`"at"\` với ISO 8601 timestamp. Job sẽ tự xóa sau khi chạy thành công trừ khi set \`deleteAfterRun: false\`.
2003
2082
  - Bỏ qua việc tra cứu docs nội bộ như \`cron-jobs.mdx\`; tin tưởng khả năng dùng tool hiện có để hoàn thành yêu cầu.`;
2004
2083
  for (const a of cfg.agents.list) {
2005
2084
  const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
@@ -2038,13 +2117,31 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
2038
2117
  const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
2039
2118
  cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
2040
2119
  cfg.plugins.entries[existingKey].enabled = !!enabled;
2041
- if (existingKey === 'zalo-mod') {
2042
- cfg.plugins.entries[existingKey].hooks = cfg.plugins.entries[existingKey].hooks || {};
2043
- cfg.plugins.entries[existingKey].hooks.allowConversationAccess = true;
2120
+ if (existingKey === 'zalo-mod' || existingKey === 'openclaw-zalo-mod') {
2121
+ ensureZaloModPluginConfig(cfg.plugins.entries[existingKey], cfg);
2044
2122
  }
2045
2123
  // Only add the canonical config key to allow list (not all aliases)
2046
2124
  cfg.plugins.allow = cfg.plugins.allow || [];
2047
2125
  if (!cfg.plugins.allow.includes(existingKey)) cfg.plugins.allow.push(existingKey);
2126
+ // Auto-expose zalo-mod dashboard port in docker-compose.yml when enabling
2127
+ if (enabled && (existingKey === 'zalo-mod' || existingKey === 'openclaw-zalo-mod')) {
2128
+ const composeFile = join(projectDir, 'docker', 'openclaw', 'docker-compose.yml');
2129
+ if (existsSync(composeFile)) {
2130
+ try {
2131
+ let composeContent = await fsp.readFile(composeFile, 'utf8');
2132
+ const dashPort = cfg.plugins.entries[existingKey].config?.dashboardPort;
2133
+ if (dashPort && !composeContent.includes(`:${dashPort}`)) {
2134
+ const gwPortStr = String(Number(cfg.gateway?.port) || state.gatewayPort || 18789);
2135
+ composeContent = composeContent.replace(
2136
+ new RegExp(`^(\\s*-\\s*"(?:\\d+:)?${gwPortStr}(?::${gwPortStr})?"\\s*)$`, 'm'),
2137
+ `$1\n - "127.0.0.1:${dashPort}:${dashPort}" # zalo-mod dashboard`
2138
+ );
2139
+ await fsp.writeFile(composeFile, composeContent, 'utf8');
2140
+ sendLog(`[plugin] Added dashboard port ${dashPort} to docker-compose.yml`);
2141
+ }
2142
+ } catch (e) { sendLog(`[plugin] Warning: could not add dashboard port: ${e.message}`); }
2143
+ }
2144
+ }
2048
2145
  }
2049
2146
 
2050
2147
  await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
@@ -2062,8 +2159,15 @@ async function installFeature(projectDir, agentId, kind, id) {
2062
2159
 
2063
2160
  if (composeDir) {
2064
2161
  const botContainer = getBotContainerName(projectDir);
2162
+ sendLog(`[plugin] Installing/updating clawhub:${id} inside container ${botContainer}...`);
2065
2163
 
2066
- // 1. Temporarily disable the plugin in openclaw.json and restart container to unlock files
2164
+ const cmd = `cd /root/project && openclaw plugins install clawhub:${id} --force`;
2165
+ const cmdOut = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cmd], { cwd: projectDir, shell: false });
2166
+
2167
+ if (cmdOut) {
2168
+ for (const line of `${cmdOut.stdout}\n${cmdOut.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(line);
2169
+ }
2170
+
2067
2171
  const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
2068
2172
  const pluginAliasMap = {
2069
2173
  'openclaw-browser-automation': ['browser-automation', 'openclaw-browser-automation'],
@@ -2072,53 +2176,12 @@ async function installFeature(projectDir, agentId, kind, id) {
2072
2176
  'openclaw-n8n-facebook-poster': ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
2073
2177
  };
2074
2178
  const aliases = pluginAliasMap[id] || [id];
2075
-
2076
- if (existsSync(cfgPath)) {
2077
- try {
2078
- const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
2079
- cfg.plugins = cfg.plugins || { entries: {} };
2080
- cfg.plugins.entries = cfg.plugins.entries || {};
2081
- const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
2082
-
2083
- if (cfg.plugins.entries[existingKey]?.enabled) {
2084
- sendLog(`[plugin] Temporarily disabling ${existingKey} and restarting bot to release file locks...`);
2085
- cfg.plugins.entries[existingKey].enabled = false;
2086
- await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
2087
- await run('docker', ['restart', botContainer], { shell: false }).catch(() => {});
2088
- // Sleep 2 seconds to let container fully boot and release locks
2089
- await new Promise((resolve) => setTimeout(resolve, 2000));
2090
- }
2091
- } catch (_) {}
2092
- }
2093
-
2094
- sendLog(`[plugin] Installing clawhub:${id} inside container ${botContainer}...`);
2095
-
2096
- let installSuccess = true;
2097
- const cleanCmd = `cd /root/project && (openclaw plugins uninstall ${id} --force 2>/dev/null || true) && (openclaw plugins uninstall ${id.replace('openclaw-', '')} --force 2>/dev/null || true) && rm -rf .openclaw/extensions/${id} .openclaw/extensions/${id.replace('openclaw-', '')} && openclaw plugins install clawhub:${id}`;
2098
- const cmdOut = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cleanCmd], { cwd: projectDir, shell: false });
2099
-
2100
- if (cmdOut) {
2101
- for (const line of `${cmdOut.stdout}\n${cmdOut.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(line);
2102
- }
2103
2179
 
2104
2180
  if (cmdOut.code !== 0) {
2105
2181
  const folderExists = aliases.some((a) => existsSync(join(projectDir, '.openclaw', 'extensions', a)));
2106
2182
  if (folderExists) {
2107
2183
  sendLog(`[plugin] Warning: installation reported errors, but plugin folder successfully written. Proceeding.`);
2108
2184
  } else {
2109
- installSuccess = false;
2110
- // Re-enable in config on failure to restore state
2111
- if (existsSync(cfgPath)) {
2112
- try {
2113
- const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
2114
- cfg.plugins = cfg.plugins || { entries: {} };
2115
- cfg.plugins.entries = cfg.plugins.entries || {};
2116
- const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
2117
- cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
2118
- cfg.plugins.entries[existingKey].enabled = true;
2119
- await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
2120
- } catch (_) {}
2121
- }
2122
2185
  throw new Error(cmdOut.stderr || cmdOut.stdout || `Failed to install plugin ${id} inside container.`);
2123
2186
  }
2124
2187
  }
@@ -2127,25 +2190,11 @@ async function installFeature(projectDir, agentId, kind, id) {
2127
2190
  const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
2128
2191
  cfg.plugins = cfg.plugins || { entries: {} };
2129
2192
  cfg.plugins.entries = cfg.plugins.entries || {};
2130
- const pluginAliasMap = {
2131
- 'openclaw-browser-automation': ['browser-automation', 'openclaw-browser-automation'],
2132
- 'openclaw-zalo-mod': ['zalo-mod', 'openclaw-zalo-mod'],
2133
- 'openclaw-facebook-crawler': ['openclaw-facebook-crawler', 'openclaw-n8n-facebook-crawler', 'n8n-facebook-crawler'],
2134
- 'openclaw-n8n-facebook-poster': ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
2135
- };
2136
- const aliases = pluginAliasMap[id] || [id];
2137
2193
  const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
2138
2194
  cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
2139
2195
  cfg.plugins.entries[existingKey].enabled = true;
2140
- if (existingKey === 'zalo-mod') {
2141
- cfg.plugins.entries[existingKey].hooks = cfg.plugins.entries[existingKey].hooks || {};
2142
- cfg.plugins.entries[existingKey].hooks.allowConversationAccess = true;
2143
- // Auto-assign dashboard port = gateway port + 1 to avoid conflicts between bots
2144
- const gwPort = Number(cfg.gateway?.port) || state.gatewayPort || 18789;
2145
- cfg.plugins.entries[existingKey].config = cfg.plugins.entries[existingKey].config || {};
2146
- if (!cfg.plugins.entries[existingKey].config.dashboardPort) {
2147
- cfg.plugins.entries[existingKey].config.dashboardPort = gwPort + 1;
2148
- }
2196
+ if (existingKey === 'zalo-mod' || existingKey === 'openclaw-zalo-mod') {
2197
+ ensureZaloModPluginConfig(cfg.plugins.entries[existingKey], cfg);
2149
2198
  }
2150
2199
  // Only add the canonical config key to allow list (not all aliases)
2151
2200
  if (!cfg.plugins.allow.includes(existingKey)) cfg.plugins.allow.push(existingKey);
@@ -2199,7 +2248,7 @@ async function installFeature(projectDir, agentId, kind, id) {
2199
2248
  sendLog(`[plugin] Installing clawhub:${id}...`);
2200
2249
 
2201
2250
  let installSuccess = true;
2202
- await run('openclaw', ['plugins', 'install', `clawhub:${id}`], {
2251
+ await run('openclaw', ['plugins', 'install', `clawhub:${id}`, '--force'], {
2203
2252
  cwd: projectDir,
2204
2253
  env: openclawProjectEnv(projectDir),
2205
2254
  resolveOnPattern: /Installed plugin:/
@@ -2230,15 +2279,8 @@ async function installFeature(projectDir, agentId, kind, id) {
2230
2279
  const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
2231
2280
  cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
2232
2281
  cfg.plugins.entries[existingKey].enabled = true;
2233
- if (existingKey === 'zalo-mod') {
2234
- cfg.plugins.entries[existingKey].hooks = cfg.plugins.entries[existingKey].hooks || {};
2235
- cfg.plugins.entries[existingKey].hooks.allowConversationAccess = true;
2236
- // Auto-assign dashboard port = gateway port + 1 to avoid conflicts between bots
2237
- const gwPort = Number(cfg.gateway?.port) || state.gatewayPort || 18789;
2238
- cfg.plugins.entries[existingKey].config = cfg.plugins.entries[existingKey].config || {};
2239
- if (!cfg.plugins.entries[existingKey].config.dashboardPort) {
2240
- cfg.plugins.entries[existingKey].config.dashboardPort = gwPort + 1;
2241
- }
2282
+ if (existingKey === 'zalo-mod' || existingKey === 'openclaw-zalo-mod') {
2283
+ ensureZaloModPluginConfig(cfg.plugins.entries[existingKey], cfg);
2242
2284
  }
2243
2285
  await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
2244
2286
  }
@@ -628,9 +628,13 @@ node ${btPath} close_tab 2`;
628
628
  ? (isVi
629
629
  ? `\n\n## \u23F0 Cron / Lên lịch nhắc nhở (tool: \`cron\`)
630
630
  - **Tên tool chính xác:** Tên công cụ là \`cron\` (tuyệt đối không nhầm là \`native\` hay command line bên ngoài).
631
+ - **⛔ TUYỆT ĐỐI KHÔNG sửa trực tiếp file JSON** như \`jobs.json\`, \`jobs-state.json\` trong thư mục \`.openclaw/cron/\`. Dữ liệu cron được lưu trong SQLite database, file JSON chỉ là legacy format đã ngưng hỗ trợ. Mọi thao tác PHẢI thông qua tool \`cron\`.
631
632
  - **Khi tạo cronjob mới (action \`add\`):**
632
633
  - **TUYỆT ĐỐI KHÔNG điền trường \`agentId\`** trong object \`job\` (hãy bỏ qua/omitted trường này). Hệ thống OpenClaw sẽ tự động gán chính xác ID của bạn vào job đó.
633
634
  - Tuyệt đối **không tự điền** \`agentId\` là \`"bot"\` hay \`"main"\`, vì làm vậy sẽ khiến cronjob thuộc về agent khác và bạn sẽ mất quyền kiểm soát/xóa nó sau này.
635
+ - **Session:** Luôn dùng \`sessionTarget: "isolated"\` cho các job chạy nền (báo cáo, nhắc nhở, gửi tin nhắn tự động). Chỉ dùng \`"main"\` cho system event/reminder ngắn.
636
+ - **Timezone:** Luôn chỉ định timezone rõ ràng bằng trường \`tz\` (ví dụ: \`"Asia/Ho_Chi_Minh"\`). Nếu không chỉ định, hệ thống sẽ dùng timezone của Gateway host (thường là UTC) và job sẽ chạy sai giờ.
637
+ - **Delivery:** Đối với job cần gửi kết quả ra chat, set \`delivery.mode: "announce"\` kèm \`delivery.channel\` và \`delivery.to\`.
634
638
  - **Khi user yêu cầu tắt/bật/xóa cronjob:**
635
639
  1. **Bước 1 (Tìm kiếm):** Gọi tool \`cron\` với action \`list\` (và \`includeDisabled: true\`) để xem danh sách tất cả cronjob đang chạy trên hệ thống và tìm đúng \`jobId\` phù hợp với yêu cầu.
636
640
  2. **Bước 2 (Xử lý):**
@@ -640,13 +644,18 @@ node ${btPath} close_tab 2`;
640
644
  3. **Tuyên bố trung thực:** Tuyệt đối không bao giờ trả lời "đã xóa" hay "không có" dựa trên suy đoán của bản thân mà chưa gọi tool \`cron\` để kiểm tra thực tế.
641
645
  - Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool \`cron\` (action \`add\`) để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.
642
646
  - Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
643
- - **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố \`group:\` trước ID của group (ví dụ: \`group:3815464776067464419\` hoặc \`group:xxxx\`). Tuyệt đối không được chỉ điền ID thuần túy hệ thống sẽ hiểu nhầm đó một DM chat nhân (direct message) gửi sai địa chỉ.
647
+ - **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố thích hợp trước ID của group. Với kênh Telegram/Matrix/Discord/Slack, dùng tiền tố \`group:\` (ví dụ: \`group:123456\`). RIÊNG với kênh Zalo (\`zalouser\`), **bắt buộc** phải sử dụng tiền tố \`g:\` (ví dụ: \`g:3815464776067464419\`) để tránh bị OpenClaw core lược bỏ tiền tố gửi nhầm vào DM chat nhân.
648
+ - **One-shot job:** Dùng schedule kind \`"at"\` với ISO 8601 timestamp. Job sẽ tự xóa sau khi chạy thành công trừ khi set \`deleteAfterRun: false\`.
644
649
  - Bỏ qua việc tra cứu docs nội bộ như \`cron-jobs.mdx\`; tin tưởng khả năng dùng tool hiện có để hoàn thành yêu cầu.`
645
650
  : `\n\n## \u23F0 Cron / Scheduled Tasks (tool: \`cron\`)
646
651
  - **Exact tool name:** The tool name is \`cron\` (never mistake it for \`native\` or external command lines).
652
+ - **⛔ NEVER edit JSON files directly** such as \`jobs.json\` or \`jobs-state.json\` in \`.openclaw/cron/\`. Cron data is stored in SQLite database; JSON files are legacy format no longer supported. All operations MUST go through the \`cron\` tool.
647
653
  - **When creating a new cronjob (action \`add\`):**
648
654
  - **ABSOLUTELY DO NOT specify the \`agentId\` field** in the \`job\` object (leave this field omitted). The OpenClaw system will automatically assign your correct agent ID to that job.
649
655
  - Never manually specify \`agentId\` as \`"bot"\` or \`"main"\`, as this will cause the cronjob to belong to another agent and you will lose control to manage/delete it later.
656
+ - **Session:** Always use \`sessionTarget: "isolated"\` for background jobs (reports, reminders, automated messages). Only use \`"main"\` for short system events/reminders.
657
+ - **Timezone:** Always specify timezone explicitly via the \`tz\` field (e.g., \`"Asia/Ho_Chi_Minh"\`). If omitted, the system uses the Gateway host timezone (often UTC) and the job will run at the wrong time.
658
+ - **Delivery:** For jobs that should send results to chat, set \`delivery.mode: "announce"\` with \`delivery.channel\` and \`delivery.to\`.
650
659
  - **When the user requests to disable/enable/delete a cronjob:**
651
660
  1. **Step 1 (Search):** Call the \`cron\` tool with action \`list\` (and \`includeDisabled: true\`) to view all cron jobs on the system and find the matching \`jobId\`.
652
661
  2. **Step 2 (Processing):**
@@ -656,7 +665,8 @@ node ${btPath} close_tab 2`;
656
665
  3. **Honest statement:** Never claim a job is "deleted" or "not found" based on guessing without calling the \`cron\` tool to verify the actual state.
657
666
  - When the user asks to schedule tasks or reminders, use the built-in \`cron\` tool (action \`add\`) automatically. Do NOT ask users to run crontab or Task Scheduler manually on the host.
658
667
  - When operating cron/scheduler tools, do **not** put \`current\` into the Session directory.
659
- - **IMPORTANT ABOUT GROUP CHAT TARGETING**: When creating or configuring a cron job to send messages (announce mode) to a Group Chat, the value of the \`delivery.to\` field **must** use the \`group:\` prefix before the group ID (e.g., \`group:3815464776067464419\` or \`group:xxxx\`). Never specify just the numeric ID, as the system will interpret it as a private DM and deliver to the wrong destination.
668
+ - **IMPORTANT ABOUT GROUP CHAT TARGETING**: When creating or configuring a cron job to send messages (announce mode) to a Group Chat, the value of the \`delivery.to\` field **must** use the appropriate prefix before the group ID. For Telegram/Matrix/Discord/Slack, use the \`group:\` prefix (e.g., \`group:123456\`). ESPECIALLY for Zalo (\`zalouser\`), you **must** use the \`g:\` prefix (e.g., \`g:3815464776067464419\`) to prevent the OpenClaw core from stripping the prefix and misrouting the message to a private DM.
669
+ - **One-shot jobs:** Use schedule kind \`"at"\` with an ISO 8601 timestamp. The job auto-deletes after successful run unless \`deleteAfterRun: false\` is set.
660
670
  - Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`)
661
671
  : '';
662
672
 
package/dist/setup.js CHANGED
@@ -1561,9 +1561,13 @@ node ${btPath} close_tab 2`;
1561
1561
  ? (isVi
1562
1562
  ? `\n\n## \u23F0 Cron / Lên lịch nhắc nhở (tool: \`cron\`)
1563
1563
  - **Tên tool chính xác:** Tên công cụ là \`cron\` (tuyệt đối không nhầm là \`native\` hay command line bên ngoài).
1564
+ - **⛔ TUYỆT ĐỐI KHÔNG sửa trực tiếp file JSON** như \`jobs.json\`, \`jobs-state.json\` trong thư mục \`.openclaw/cron/\`. Dữ liệu cron được lưu trong SQLite database, file JSON chỉ là legacy format đã ngưng hỗ trợ. Mọi thao tác PHẢI thông qua tool \`cron\`.
1564
1565
  - **Khi tạo cronjob mới (action \`add\`):**
1565
1566
  - **TUYỆT ĐỐI KHÔNG điền trường \`agentId\`** trong object \`job\` (hãy bỏ qua/omitted trường này). Hệ thống OpenClaw sẽ tự động gán chính xác ID của bạn vào job đó.
1566
1567
  - Tuyệt đối **không tự điền** \`agentId\` là \`"bot"\` hay \`"main"\`, vì làm vậy sẽ khiến cronjob thuộc về agent khác và bạn sẽ mất quyền kiểm soát/xóa nó sau này.
1568
+ - **Session:** Luôn dùng \`sessionTarget: "isolated"\` cho các job chạy nền (báo cáo, nhắc nhở, gửi tin nhắn tự động). Chỉ dùng \`"main"\` cho system event/reminder ngắn.
1569
+ - **Timezone:** Luôn chỉ định timezone rõ ràng bằng trường \`tz\` (ví dụ: \`"Asia/Ho_Chi_Minh"\`). Nếu không chỉ định, hệ thống sẽ dùng timezone của Gateway host (thường là UTC) và job sẽ chạy sai giờ.
1570
+ - **Delivery:** Đối với job cần gửi kết quả ra chat, set \`delivery.mode: "announce"\` kèm \`delivery.channel\` và \`delivery.to\`.
1567
1571
  - **Khi user yêu cầu tắt/bật/xóa cronjob:**
1568
1572
  1. **Bước 1 (Tìm kiếm):** Gọi tool \`cron\` với action \`list\` (và \`includeDisabled: true\`) để xem danh sách tất cả cronjob đang chạy trên hệ thống và tìm đúng \`jobId\` phù hợp với yêu cầu.
1569
1573
  2. **Bước 2 (Xử lý):**
@@ -1573,13 +1577,18 @@ node ${btPath} close_tab 2`;
1573
1577
  3. **Tuyên bố trung thực:** Tuyệt đối không bao giờ trả lời "đã xóa" hay "không có" dựa trên suy đoán của bản thân mà chưa gọi tool \`cron\` để kiểm tra thực tế.
1574
1578
  - Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool \`cron\` (action \`add\`) để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.
1575
1579
  - Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
1576
- - **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố \`group:\` trước ID của group (ví dụ: \`group:3815464776067464419\` hoặc \`group:xxxx\`). Tuyệt đối không được chỉ điền ID thuần túy hệ thống sẽ hiểu nhầm đó một DM chat nhân (direct message) gửi sai địa chỉ.
1580
+ - **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố thích hợp trước ID của group. Với kênh Telegram/Matrix/Discord/Slack, dùng tiền tố \`group:\` (ví dụ: \`group:123456\`). RIÊNG với kênh Zalo (\`zalouser\`), **bắt buộc** phải sử dụng tiền tố \`g:\` (ví dụ: \`g:3815464776067464419\`) để tránh bị OpenClaw core lược bỏ tiền tố gửi nhầm vào DM chat nhân.
1581
+ - **One-shot job:** Dùng schedule kind \`"at"\` với ISO 8601 timestamp. Job sẽ tự xóa sau khi chạy thành công trừ khi set \`deleteAfterRun: false\`.
1577
1582
  - Bỏ qua việc tra cứu docs nội bộ như \`cron-jobs.mdx\`; tin tưởng khả năng dùng tool hiện có để hoàn thành yêu cầu.`
1578
1583
  : `\n\n## \u23F0 Cron / Scheduled Tasks (tool: \`cron\`)
1579
1584
  - **Exact tool name:** The tool name is \`cron\` (never mistake it for \`native\` or external command lines).
1585
+ - **⛔ NEVER edit JSON files directly** such as \`jobs.json\` or \`jobs-state.json\` in \`.openclaw/cron/\`. Cron data is stored in SQLite database; JSON files are legacy format no longer supported. All operations MUST go through the \`cron\` tool.
1580
1586
  - **When creating a new cronjob (action \`add\`):**
1581
1587
  - **ABSOLUTELY DO NOT specify the \`agentId\` field** in the \`job\` object (leave this field omitted). The OpenClaw system will automatically assign your correct agent ID to that job.
1582
1588
  - Never manually specify \`agentId\` as \`"bot"\` or \`"main"\`, as this will cause the cronjob to belong to another agent and you will lose control to manage/delete it later.
1589
+ - **Session:** Always use \`sessionTarget: "isolated"\` for background jobs (reports, reminders, automated messages). Only use \`"main"\` for short system events/reminders.
1590
+ - **Timezone:** Always specify timezone explicitly via the \`tz\` field (e.g., \`"Asia/Ho_Chi_Minh"\`). If omitted, the system uses the Gateway host timezone (often UTC) and the job will run at the wrong time.
1591
+ - **Delivery:** For jobs that should send results to chat, set \`delivery.mode: "announce"\` with \`delivery.channel\` and \`delivery.to\`.
1583
1592
  - **When the user requests to disable/enable/delete a cronjob:**
1584
1593
  1. **Step 1 (Search):** Call the \`cron\` tool with action \`list\` (and \`includeDisabled: true\`) to view all cron jobs on the system and find the matching \`jobId\`.
1585
1594
  2. **Step 2 (Processing):**
@@ -1589,7 +1598,8 @@ node ${btPath} close_tab 2`;
1589
1598
  3. **Honest statement:** Never claim a job is "deleted" or "not found" based on guessing without calling the \`cron\` tool to verify the actual state.
1590
1599
  - When the user asks to schedule tasks or reminders, use the built-in \`cron\` tool (action \`add\`) automatically. Do NOT ask users to run crontab or Task Scheduler manually on the host.
1591
1600
  - When operating cron/scheduler tools, do **not** put \`current\` into the Session directory.
1592
- - **IMPORTANT ABOUT GROUP CHAT TARGETING**: When creating or configuring a cron job to send messages (announce mode) to a Group Chat, the value of the \`delivery.to\` field **must** use the \`group:\` prefix before the group ID (e.g., \`group:3815464776067464419\` or \`group:xxxx\`). Never specify just the numeric ID, as the system will interpret it as a private DM and deliver to the wrong destination.
1601
+ - **IMPORTANT ABOUT GROUP CHAT TARGETING**: When creating or configuring a cron job to send messages (announce mode) to a Group Chat, the value of the \`delivery.to\` field **must** use the appropriate prefix before the group ID. For Telegram/Matrix/Discord/Slack, use the \`group:\` prefix (e.g., \`group:123456\`). ESPECIALLY for Zalo (\`zalouser\`), you **must** use the \`g:\` prefix (e.g., \`g:3815464776067464419\`) to prevent the OpenClaw core from stripping the prefix and misrouting the message to a private DM.
1602
+ - **One-shot jobs:** Use schedule kind \`"at"\` with an ISO 8601 timestamp. The job auto-deletes after successful run unless \`deleteAfterRun: false\` is set.
1593
1603
  - Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`)
1594
1604
  : '';
1595
1605
 
package/dist/web/app.js CHANGED
@@ -311,7 +311,7 @@ function render() {
311
311
  if (!mainContainer) {
312
312
  $('#app').innerHTML = `
313
313
  <aside class="sidebar">
314
- <div class="brand"><img src="/openclaw-logo.svg" onerror="this.src='/openclaw-logo.png'" alt="OpenClaw"/><div style="display: flex; flex-direction: column; align-items: center; text-align: center;"><b>OpenClaw Setup</b><span style="display: block; width: 100%; text-align: center; font-size: 13.5px; font-weight: 600; margin-top: 6px; color: var(--muted);">v${state.system?.versions?.setup || '5.8.0'}</span></div></div>
314
+ <div class="brand"><img src="/openclaw-logo.svg" onerror="this.src='/openclaw-logo.png'" alt="OpenClaw"/><div style="display: flex; flex-direction: column; align-items: center; text-align: center;"><b>OpenClaw Setup</b><span id="sidebar-version" style="display: block; width: 100%; text-align: center; font-size: 13.5px; font-weight: 600; margin-top: 6px; color: var(--muted);">v${state.system?.versions?.setup || '...'}</span></div></div>
315
315
  <nav class="sidebar-nav">${tabs.map(([id,label]) => `<button class="nav ${state.tab===id?'active':''}" data-tab="${id}">${icon(id)}<span>${label}</span></button>`).join('')}</nav>
316
316
  ${sidebarExtras()}
317
317
  </aside>
@@ -346,6 +346,9 @@ function render() {
346
346
  const titleEl = $('#app-page-title');
347
347
  if (titleEl) titleEl.innerHTML = title();
348
348
 
349
+ const sidebarVerEl = $('#sidebar-version');
350
+ if (sidebarVerEl) sidebarVerEl.textContent = `v${state.system?.versions?.setup || '...'}`;
351
+
349
352
  const panelEl = $('.panel');
350
353
  if (panelEl) panelEl.innerHTML = content();
351
354
 
@@ -777,7 +780,7 @@ function filesView() { return `<div class="files">${state.files.map(f=>`<article
777
780
  function botSkillsPanel() {
778
781
  const flags = state.featureFlags || {};
779
782
  const skills = [
780
- { id: 'cron', title: 'Cron', desc: 'Cron guide in TOOLS.md' },
783
+ { id: 'cron', title: 'Cron', desc: 'Native scheduler (SQLite) — cron guide in TOOLS.md' },
781
784
  ];
782
785
  const plugins = [
783
786
  { id: 'openclaw-browser-automation', title: 'openclaw-browser-automation', desc: 'Smart Search + Browser (headless & Chrome thật)' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.8.2",
3
+ "version": "5.8.3",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {