create-openclaw-bot 5.3.3 → 5.3.5

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,33 @@
1
1
  # Changelog (English)
2
2
 
3
3
 
4
+ ## [5.3.5] — 2026-04-12
5
+
6
+ ### 🐛 Fix: MEMORY.md Syntax Error in Zalo Workspace Files
7
+
8
+ - **Fix: `SyntaxError: Unexpected token ':'` in `setup.js`** — the previous TOOLS.md patch was inserted after the `vi` ternary arm of MEMORY.md, orphaning the `: en-value` colon arm below it. VS Code showed this as 7 error badges on `setup.js`. Now fixed; both MEMORY.md arms are contiguous, then TOOLS.md follows as a clean separate property.
9
+
10
+ ### 🐟 Improvement: Uninstall Script Written to Project Folder
11
+
12
+ - **All 4 OS native flows + Docker ZIP** now include the matching `uninstall-*.{bat,sh}` script **in the project folder** during setup. Previously the uninstall was only available as a separate browser download. Pattern mirrors `start-chrome-debug.bat` / `.sh`:
13
+ - Windows native: `uninstall-openclaw-win.bat` written via `appendBatWriteCommands`
14
+ - macOS native: `uninstall-openclaw.sh` written via `appendShWriteCommands`
15
+ - VPS/Ubuntu: `uninstall-openclaw-vps.sh` written via `appendShWriteCommands`
16
+ - Linux Desktop: `uninstall-openclaw.sh` written via `appendShWriteCommands`
17
+ - Docker (all OS): uninstall script included in the generatedFiles ZIP
18
+
19
+
20
+
21
+ ### 🐛 Windows Native — Gateway & Workspace Stability
22
+
23
+ - **Fix: Gateway terminal auto-closing** — `call openclaw gateway run` was blocking the setup terminal indefinitely; closing the window killed the gateway process. The gateway is now launched in a **dedicated, visible CMD window** via a PS1 background launcher (same pattern as 9Router), so the setup terminal closes cleanly while the gateway keeps running.
24
+ - **Fix: `call` missing from `openclaw gateway stop`** — The gateway stop command before relaunch was missing the `call` keyword, which could prevent the bat script from returning control after the stop completes. Added `call openclaw gateway stop 2>nul` in all affected paths.
25
+ - **Fix: Workspace naming** — Single-bot deployments previously used a generic `.openclaw/workspace` path. All agents (single-bot and multi-bot) now use a named directory matching their agent ID: `.openclaw/workspace-{agentId}` (e.g. `workspace-williams`, `workspace-luna`). This prevents workspace collisions and aligns the folder structure with the `agents/{agentId}` directory convention.
26
+ - **Improve: TOOLS.md enriched for all bots** — The generated `TOOLS.md` now includes both the custom skills & conventions section AND a "Local Notes" template section adapted from the OpenClaw workspace default, giving users a clear starting point for documenting environment-specific config.
27
+ - **Improve: Zalo bot AGENTS.md now includes security rules** — The generated `AGENTS.md` for Zalo Personal bots (Luna-pattern) now appends the same `🔐 Security Rules — MANDATORY` block that Telegram bots receive (file-system boundaries, credential hygiene, crypto wallet protection, Docker mount rules).
28
+ - **Improve: Zalo bot TOOLS.md added** — Zalo bot workspaces now receive a `TOOLS.md` file with the same skills list and conventions structure as Telegram bots.
29
+
30
+
4
31
  ## [5.3.3] — 2026-04-11
5
32
 
6
33
  ### 🧹 Automated Uninstall Scripts
package/CHANGELOG.vi.md CHANGED
@@ -1,6 +1,34 @@
1
1
  # Changelog (Tiếng Việt)
2
2
 
3
3
 
4
+ ## [5.3.5] — 2026-04-12
5
+
6
+ ### 🐛 Sửa: Lỗi Syntax MEMORY.md trong Workspace Zalo
7
+
8
+ - **Sửa: `SyntaxError: Unexpected token ':'` trong `setup.js`** — Patch TOOLS.md trước đó được chèn sau nhánh `vi` của ternary MEMORY.md, khiến nhánh `: en-value` bị bỏ lơ phía dưới. VS Code hiển thị badge đỏ số "7" trên `setup.js`. Đã fix; hai nhánh MEMORY.md nay liền kề nhau, TOOLS.md theo sau như property riêng biệt.
9
+
10
+ ### 🐟 Cải Tiến: Script Gỡ Cài Đặt Nằm Trong Thư Mục Project
11
+
12
+ - **Tất cả 4 luồng native OS + Docker ZIP** giờ đều có sẵn file `uninstall-*.{bat,sh}` **trong thư mục project** ngay sau khi setup chạy xong. Trước đây uninstall chỉ có thể tải riêng từ trình duyệt. Pattern giống `start-chrome-debug.bat` / `.sh`:
13
+ - Windows native: `uninstall-openclaw-win.bat` viết qua `appendBatWriteCommands`
14
+ - macOS native: `uninstall-openclaw.sh` viết qua `appendShWriteCommands`
15
+ - VPS/Ubuntu: `uninstall-openclaw-vps.sh` viết qua `appendShWriteCommands`
16
+ - Linux Desktop: `uninstall-openclaw.sh` viết qua `appendShWriteCommands`
17
+ - Docker (mọi OS): uninstall script có trong ZIP generatedFiles
18
+
19
+
20
+ ## [5.3.4] — 2026-04-12
21
+
22
+ ### 🐛 Windows Native — Ổn Định Gateway & Workspace
23
+
24
+ - **Sửa: Terminal tự đóng sau khi khởi động gateway** — `call openclaw gateway run` chặn terminal vô thời hạn; đóng cửa sổ khiến gateway chết theo. Gateway giờ được mở trong **cửa sổ CMD riêng biệt** qua PS1 launcher (giống 9Router), setup terminal đóng gọn mà gateway vẫn chạy độc lập.
25
+ - **Sửa: Thiếu `call` trước `openclaw gateway stop`** — Lệnh dừng gateway trước khi khởi động lại thiếu từ khóa `call`, có thể khiến bat script không trả quyền điều hành sau khi stop. Đã thêm `call openclaw gateway stop 2>nul` cho tất cả luồng.
26
+ - **Sửa: Tên workspace** — Single-bot deployment trước đây dùng chung `.openclaw/workspace`. Giờ tất cả agent đều có thư mục riêng theo tên agent ID: `.openclaw/workspace-{agentId}` (ví dụ `workspace-williams`, `workspace-luna`). Tránh xung đột và đồng bộ với cấu trúc `agents/{agentId}`.
27
+ - **Cải tiến: TOOLS.md đầy đủ hơn cho mọi bot** — File `TOOLS.md` được tạo ra giờ bao gồm cả mục danh sách skills + quy ước VÀ section "Ghi chú thiết lập của bạn" theo chuẩn OpenClaw, giúp người dùng có điểm xuất phát rõ ràng để ghi lại cấu hình môi trường riêng.
28
+ - **Cải tiến: AGENTS.md cho Zalo bot bổ sung quy tắc bảo mật** — `AGENTS.md` sinh ra cho bot Zalo Personal (kiểu Luna) giờ có thêm block `🔐 Quy Tắc Bảo Mật — BẮT BUỘC` giống hệt bot Telegram (giới hạn file-system, bảo vệ credentials, ví crypto, mount Docker).
29
+ - **Cải tiến: Thêm TOOLS.md cho Zalo bot** — Workspace của bot Zalo giờ cũng có file `TOOLS.md` cùng cấu trúc với bot Telegram.
30
+
31
+
4
32
  ## [5.3.3] — 2026-04-11
5
33
 
6
34
  ### 🧹 Tự Động Tạo Script Gỡ Cài Đặt
package/cli.js CHANGED
@@ -451,31 +451,57 @@ const sync = async () => {
451
451
  let db = {};
452
452
  try { db = JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e) {}
453
453
  if (!db.combos) db.combos = [];
454
- const removeSmartRoute = () => {
454
+ const resCombo = await fetch(ROUTER + '/api/combos').catch(() => null);
455
+ let memoryCombos = [];
456
+ if (resCombo && resCombo.ok) {
457
+ const cData = await resCombo.json();
458
+ memoryCombos = cData.combos || [];
459
+ }
460
+ const removeSmartRoute = async () => {
455
461
  const next = db.combos.filter(x => x.id !== 'smart-route');
456
462
  if (next.length !== db.combos.length) {
457
463
  db.combos = next;
458
464
  fs.writeFileSync(p, JSON.stringify(db, null, 2));
459
465
  console.log('[sync-combo] Removed smart-route (no active providers)');
460
466
  }
467
+ if (memoryCombos.find(x => x.id === 'smart-route')) {
468
+ await fetch(ROUTER + '/api/combos/smart-route', { method: 'DELETE' }).catch(()=>{});
469
+ console.log('[sync-combo] Removed smart-route from 9Router memory');
470
+ }
461
471
  };
462
- if (!a.length) { removeSmartRoute(); return; }
472
+ if (!a.length) { await removeSmartRoute(); return; }
463
473
  const PREF = ['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];
464
474
  a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
465
475
  const m = a.flatMap(pv => PM[pv] || []);
466
- if (!m.length) { removeSmartRoute(); return; }
476
+ if (!m.length) { await removeSmartRoute(); return; }
467
477
  const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
468
478
  const i = db.combos.findIndex(x => x.id === 'smart-route');
479
+ let dbUpdated = false;
469
480
  if (i >= 0) {
470
481
  if (JSON.stringify(db.combos[i].models) !== JSON.stringify(c.models)) {
471
482
  db.combos[i] = c;
472
483
  fs.writeFileSync(p, JSON.stringify(db, null, 2));
473
- console.log('[sync-combo] Updated smart-route: ' + c.models.length + ' models from: ' + a.join(','));
484
+ dbUpdated = true;
474
485
  }
475
486
  } else {
476
487
  db.combos.push(c);
477
488
  fs.writeFileSync(p, JSON.stringify(db, null, 2));
478
- console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models from: ' + a.join(','));
489
+ dbUpdated = true;
490
+ }
491
+ const inMemory = memoryCombos.find(x => x.id === 'smart-route');
492
+ let memUpdated = false;
493
+ if (inMemory) {
494
+ if (JSON.stringify(inMemory.models) !== JSON.stringify(c.models)) {
495
+ await fetch(ROUTER + '/api/combos/smart-route', { method: 'DELETE' }).catch(()=>{});
496
+ await fetch(ROUTER + '/api/combos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(c) }).catch(()=>{});
497
+ memUpdated = true;
498
+ }
499
+ } else {
500
+ await fetch(ROUTER + '/api/combos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(c) }).catch(()=>{});
501
+ memUpdated = true;
502
+ }
503
+ if (dbUpdated || memUpdated) {
504
+ console.log('[sync-combo] Synced smart-route (Memory+Disk): ' + c.models.length + ' models from: ' + a.join(','));
479
505
  }
480
506
  } catch(e) { console.log('[sync-combo] Error:', e.message); }
481
507
  };
@@ -578,7 +604,9 @@ function build9RouterComposeEntrypointScript(syncScriptBase64) {
578
604
  }
579
605
 
580
606
  async function writeNative9RouterSyncScript(projectDir) {
581
- const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
607
+ // Write to .9router/ (DATA_DIR) so 9router's own data dir has the sync helper,
608
+ // keeping .openclaw/ focused on openclaw configs only.
609
+ const syncScriptPath = path.join(projectDir, '.9router', '9router-smart-route-sync.js');
582
610
  await fs.ensureDir(path.dirname(syncScriptPath));
583
611
  await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(getProject9RouterDataDir(projectDir), 'db.json')));
584
612
  return syncScriptPath;
@@ -2593,7 +2621,7 @@ if exist "%TARGET%" (
2593
2621
  echo.
2594
2622
  echo [4/4] Xoa thu muc .9router va .openclaw trong Home (neu co)...
2595
2623
  if exist "%USERPROFILE%\\.9router" (
2596
- set /p CLEAN_HOME=Xoa ca %USERPROFILE%\\.9router? (YES/no):
2624
+ set /p CLEAN_HOME=Xoa ca %USERPROFILE%\\.9router? [YES/no]:
2597
2625
  if /i "%CLEAN_HOME%"=="YES" rd /s /q "%USERPROFILE%\\.9router" >nul 2>&1
2598
2626
  )
2599
2627
  echo.
@@ -2667,7 +2695,7 @@ echo ""
2667
2695
  echo "[4/4] Checking for home-level .9router / .openclaw..."
2668
2696
  for dir in "$HOME/.9router" "$HOME/.openclaw"; do
2669
2697
  if [ -d "$dir" ]; then
2670
- read -rp "Delete $dir ? (YES/no): " CLEAN
2698
+ read -rp "Delete $dir ? [YES/no]: " CLEAN
2671
2699
  if [ "$CLEAN" = "YES" ]; then
2672
2700
  rm -rf "$dir"
2673
2701
  echo " OK: Deleted $dir"
@@ -2757,7 +2785,7 @@ echo ""
2757
2785
  echo "[5/5] Checking for home-level .9router / .openclaw..."
2758
2786
  for dir in "$HOME/.9router" "$HOME/.openclaw"; do
2759
2787
  if [ -d "$dir" ]; then
2760
- read -rp "Delete $dir ? (YES/no): " CLEAN
2788
+ read -rp "Delete $dir ? [YES/no]: " CLEAN
2761
2789
  if [ "$CLEAN" = "YES" ]; then
2762
2790
  rm -rf "$dir"
2763
2791
  echo " OK: Deleted $dir"
@@ -2827,7 +2855,7 @@ fi
2827
2855
  echo ""
2828
2856
  echo "[3/3] Checking for home-level .openclaw..."
2829
2857
  if [ -d "$HOME/.openclaw" ]; then
2830
- read -rp "Delete $HOME/.openclaw? (YES/no): " CLEAN
2858
+ read -rp "Delete $HOME/.openclaw? [YES/no]: " CLEAN
2831
2859
  if [ "$CLEAN" = "YES" ]; then
2832
2860
  rm -rf "$HOME/.openclaw"
2833
2861
  echo " OK: Deleted $HOME/.openclaw"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.3.3",
3
+ "version": "5.3.5",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/patch-tray.js ADDED
@@ -0,0 +1,7 @@
1
+ const fs = require('fs');
2
+ let b = fs.readFileSync('C:/Users/Admin/Downloads/setup-openclaw-win.bat', 'utf8');
3
+ const before = '9router -n -H 0.0.0.0 -p 20128 --skip-update"';
4
+ const after = '9router -n -H 0.0.0.0 -p 20128 --skip-update --tray"';
5
+ b = b.split(before).join(after);
6
+ fs.writeFileSync('C:/Users/Admin/Downloads/setup-openclaw-win.bat', b);
7
+ console.log('Fixed! Has --tray:', b.includes('--tray'));
package/setup.js CHANGED
@@ -1933,7 +1933,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
1933
1933
  },
1934
1934
  list: [{
1935
1935
  id: agentId,
1936
- workspace: 'workspace',
1936
+ workspace: `.openclaw/workspace-${agentId}`,
1937
1937
  agentDir: `agents/${agentId}/agent`,
1938
1938
  model: { primary: state.config.model, fallbacks: [] },
1939
1939
  }],
@@ -2050,7 +2050,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
2050
2050
  clawConfig.agents.list = multiBotAgentMetas.map((meta) => ({
2051
2051
  id: meta.agentId,
2052
2052
  name: meta.name,
2053
- workspace: meta.workspaceDir,
2053
+ workspace: '.openclaw/' + meta.workspaceDir,
2054
2054
  agentDir: `agents/${meta.agentId}/agent`,
2055
2055
  model: { primary: state.config.model, fallbacks: [] },
2056
2056
  }));
@@ -3022,7 +3022,7 @@ fi
3022
3022
  botConfig.agents.defaults.model = { primary: state.config.model, fallbacks: [] };
3023
3023
  botConfig.agents.list = [{
3024
3024
  id: botAgentId,
3025
- workspace: 'workspace',
3025
+ workspace: `.openclaw/workspace-${botAgentId}`,
3026
3026
  agentDir: `agents/${botAgentId}/agent`,
3027
3027
  model: { primary: state.config.model, fallbacks: [] },
3028
3028
  }];
@@ -3086,6 +3086,9 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
3086
3086
  generatedFiles['start-chrome-debug.bat'] = chromeBatContent;
3087
3087
  generatedFiles['start-chrome-debug.sh'] = chromeShContent;
3088
3088
  }
3089
+ // Include uninstall script in downloaded project files (same as start-chrome-debug pattern)
3090
+ const _uninstallForZip = generateUninstallScript();
3091
+ if (_uninstallForZip) generatedFiles[_uninstallForZip.name] = _uninstallForZip.content;
3089
3092
 
3090
3093
  state._generatedFiles = generatedFiles;
3091
3094
  if (isSharedMultiBot) {
@@ -3240,6 +3243,7 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
3240
3243
  .filter((skill) => skill && skill.id !== 'scheduler' && skill.slug && skill.slug !== 'browser-automation');
3241
3244
  const selectedModel = (state.config.model || 'ollama/gemma4:e2b').replace('ollama/', '');
3242
3245
  const isMultiBot = state.botCount > 1 && state.channel === 'telegram';
3246
+ const isComboChannel = state.channel === 'telegram+zalo-personal';
3243
3247
  const projectDir = state.config.projectPath || '.';
3244
3248
  const todayStamp = new Date().toISOString().slice(0, 10);
3245
3249
 
@@ -3282,7 +3286,12 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3282
3286
  function providerLines(arr, shell) {
3283
3287
  if (is9Router) {
3284
3288
  if (shell === 'bat') {
3289
+ arr.push(':: Dung 9Router dang chay (neu co) - tranh loi EBUSY khi npm cap nhat file dang bi lock');
3290
+ arr.push('wmic process where "Name=\'node.exe\' and CommandLine like \'%%9router%%\'" delete >nul 2>&1');
3291
+ arr.push('wmic process where "Name=\'cmd.exe\' and CommandLine like \'%%9router%%\'" delete >nul 2>&1');
3292
+ arr.push('timeout /t 3 /nobreak >nul');
3285
3293
  arr.push('call npm install -g 9router || goto :fail');
3294
+ arr.push('echo [OK] 9Router da duoc cai dat thanh cong.');
3286
3295
  // Pre-create DATA_DIR and seed db.json with requireLogin:false BEFORE starting 9router.
3287
3296
  // If db.json is missing when 9router boots, it defaults to ~/.9router and requireLogin:true,
3288
3297
  // blocking the dashboard. Must be done BEFORE the `start` command below.
@@ -3308,9 +3317,20 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3308
3317
  arr.push('echo(}');
3309
3318
  arr.push(')');
3310
3319
  arr.push(')');
3311
- // NOTE: -l (stdin listen mode) intentionally omitted causes hangs in non-TTY cmd windows.
3312
- // DATA_DIR passed via env so 9router reads from the project-local data folder.
3313
- arr.push('start "9Router Dashboard" /min cmd /c "set DATA_DIR=%DATA_DIR%&& 9router -n -H 0.0.0.0 -p 20128 --skip-update"');
3320
+ // Launch 9Router as a fully detached process via PowerShell Start-Process -WindowStyle Hidden.
3321
+ // Using `start ... cmd /c "...--tray"` caused 9Router to die when the CMD window was closed
3322
+ // because cmd /c is a child of the started CMD, which is killed when that window closes.
3323
+ // Start-Process with WindowStyle Hidden creates a truly independent process that survives
3324
+ // even if all visible terminal windows are closed.
3325
+ // Write a temp .ps1 launcher to avoid CMD->PS quoting issues.
3326
+ // Start-Process cannot run .cmd files directly — cmd.exe must be the FilePath.
3327
+ arr.push('echo Khoi dong 9Router (background)...');
3328
+ arr.push('echo $env:DATA_DIR = \'%DATA_DIR%\' > "%TEMP%\\oc-start9r.ps1"');
3329
+ arr.push('echo $b = Join-Path $env:APPDATA \'npm\\9router.cmd\' >> "%TEMP%\\oc-start9r.ps1"');
3330
+ arr.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\9router\' } >> "%TEMP%\\oc-start9r.ps1"');
3331
+ arr.push(`echo Start-Process 'cmd.exe' -WindowStyle Hidden -WorkingDirectory '${projectDir}' -ArgumentList ^('/c "' + $b + '" -n -H 0.0.0.0 -p 20128 --skip-update'^) >> "%TEMP%\\oc-start9r.ps1"`);
3332
+ arr.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-start9r.ps1"');
3333
+ arr.push('del "%TEMP%\\oc-start9r.ps1" >nul 2>&1');
3314
3334
  arr.push('timeout /t 8 /nobreak >nul');
3315
3335
  } else {
3316
3336
  arr.push('npm install -g 9router');
@@ -3319,8 +3339,8 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3319
3339
  arr.push('if [ ! -f ".9router/db.json" ]; then cat > ".9router/db.json" << \'DBJSON\'\n{\n "providerConnections": [],\n "providerNodes": [],\n "proxyPools": [],\n "modelAliases": {},\n "mitmAlias": {},\n "combos": [],\n "apiKeys": [],\n "settings": {\n "requireLogin": false,\n "cloudEnabled": false,\n "tunnelEnabled": false,\n "comboStrategy": "fallback",\n "mitmRouterBaseUrl": "http://localhost:20128"\n },\n "pricing": {}\n}\nDBJSON\nfi');
3320
3340
  arr.push('NINE_ROUTER_BIN="$(command -v 9router)"');
3321
3341
  // NOTE: -l (stdin listen mode) intentionally omitted — causes hangs in non-TTY environments
3322
- arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 DATA_DIR="$PWD/.9router" "$NINE_ROUTER_BIN" -n -H 0.0.0.0 -p 20128 --skip-update >/tmp/9router.log 2>&1 &');
3323
- arr.push('nohup env DATA_DIR="$PWD/.9router" node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
3342
+ arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 DATA_DIR="$PWD/.9router" "$NINE_ROUTER_BIN" -n -H 0.0.0.0 -p 20128 --skip-update > /tmp/9router.log 2>&1 &');
3343
+ arr.push('nohup env DATA_DIR="$PWD/.9router" node ./.9router/9router-smart-route-sync.js > /tmp/9router-sync.log 2>&1 &');
3324
3344
  arr.push('sleep 5');
3325
3345
  }
3326
3346
  } else if (isOllama) {
@@ -3437,7 +3457,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3437
3457
  list: multiBotAgentMetas.map((meta) => ({
3438
3458
  id: meta.agentId,
3439
3459
  name: meta.name,
3440
- workspace: meta.workspaceDir,
3460
+ workspace: '.openclaw/' + meta.workspaceDir,
3441
3461
  agentDir: `agents/${meta.agentId}/agent`,
3442
3462
  model: { primary: state.config.model, fallbacks: [] },
3443
3463
  })),
@@ -3504,7 +3524,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3504
3524
  '.openclaw/auth-profiles.json': sharedNativeAuthProfilesContent(),
3505
3525
  'TELEGRAM-POST-INSTALL.md': buildTelegramPostInstallChecklist(),
3506
3526
  };
3507
- if (is9Router) files['.openclaw/9router-smart-route-sync.js'] = native9RouterSyncScriptContent();
3527
+ if (is9Router) files['.9router/9router-smart-route-sync.js'] = native9RouterSyncScriptContent();
3508
3528
  const teamMd = isVi
3509
3529
  ? `# Doi Bot\n\n${multiBotAgentMetas.map((meta) => `## ${meta.name}\n- Vai tro: ${meta.desc}\n- Agent ID: \`${meta.agentId}\`\n- Telegram accountId: \`${meta.accountId}\`\n- Slash command: ${meta.slashCmd || '_(chua co)_'}\n- Tinh cach: ${meta.persona || '_(khong ghi ro)_'}`).join('\n\n')}\n\n## Quy uoc phoi hop\n- Tat ca bot trong doi biet ro vai tro cua nhau.\n- Neu user bao ban hoi mot bot khac, hay dung agent-to-agent noi bo thay vi doi Telegram chuyen tin cua bot.\n- Bot mo loi chi noi 1 cau ngan, sau do chuyen turn noi bo cho bot dich.\n- Bot dich phai tra loi cong khai bang chinh Telegram account cua minh trong cung chat/thread hien tai.\n- Neu can fallback, chi bot mo loi moi duoc phep tom tat thay.`
3510
3530
  : `# Bot Team\n\n${multiBotAgentMetas.map((meta) => `## ${meta.name}\n- Role: ${meta.desc}\n- Agent ID: \`${meta.agentId}\`\n- Telegram accountId: \`${meta.accountId}\`\n- Slash command: ${meta.slashCmd || '_(not set)_'}\n- Persona: ${meta.persona || '_(not specified)_'}`).join('\n\n')}\n\n## Coordination Rules\n- Every bot knows the full roster.\n- If the user asks you to consult another bot, use internal agent-to-agent handoff instead of waiting for Telegram bot-to-bot delivery.\n- The caller bot only sends one short opener, then hands off internally.\n- The target bot must publish the real answer with its own Telegram account in the same chat/thread.\n- If a fallback is needed, only the caller bot may summarize on behalf of the target.`;
@@ -3598,7 +3618,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3598
3618
  },
3599
3619
  list: [{
3600
3620
  id: agentId,
3601
- workspace: 'workspace',
3621
+ workspace: `.openclaw/workspace-${agentId}`,
3602
3622
  agentDir: `agents/${agentId}/agent`,
3603
3623
  model: { primary: actualModel }
3604
3624
  }],
@@ -3870,6 +3890,7 @@ You are **${botName}**, ${botDesc.toLowerCase()}.
3870
3890
  - Prefer English unless user uses another language
3871
3891
  - When asked your name: _"I'm ${botName}"_
3872
3892
  - Never fabricate information`;
3893
+ const _secRules = state.config.securityRules || DEFAULT_SECURITY_RULES[isVi ? 'vi' : 'en'];
3873
3894
  const extraAgentsMd = isVi
3874
3895
  ? `\n\n## Khi nao nen tra loi\n- Trong group, chi tra loi khi tin nhan co alias cua ban: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')} hoac username Telegram cua ban.\n- Neu tin nhan khong goi ban, hay im lang hoan toan.\n- Neu tin nhan chi goi ro bot khac ${otherBotNames.length ? otherBotNames.map((name) => `\`${name}\``).join(', ') : '`bot khac`'} thi khong cuop loi.\n- Khi da biet user dang goi ban, hay tha reaction co dinh \`👍\` truoc roi moi tra loi bang text. Khong dung emoji khac.\n- Khi can phoi hop noi bo, dung dung agent id ky thuat trong \`TEAM.md\`, khong dung ten hien thi.\n- Khi hoi ve vai tro cac bot, dung \`TEAM.md\` lam nguon su that.`
3875
3896
  : `\n\n## When To Reply\n- In group chats, only reply when the message contains one of your aliases: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')} or your Telegram username.\n- If the message is not calling you, stay completely silent.\n- If the message is clearly calling another bot such as ${otherBotNames.length ? otherBotNames.map((name) => `\`${name}\``).join(', ') : '`another bot`'}, do not hijack it.\n- Once you know the user is calling you, add the fixed reaction \`👍\` first, then send the text reply. Do not use any other reaction emoji.\n- When you need internal coordination, use the exact technical agent id from \`TEAM.md\`, not the display name.\n- Use \`TEAM.md\` as the source of truth for team roles.`;
@@ -3898,7 +3919,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(Chưa có ski
3898
3919
  ## Quy ước
3899
3920
  - Ưu tiên dùng tool thay vì đoán
3900
3921
  - Browser: dùng khi user yêu cầu thao tác web
3901
- - Memory: cập nhật khi biết thông tin quan trọng`
3922
+ - Memory: cập nhật khi biết thông tin quan trọng\n\n## Ghi chú thiết lập của bạn\n\nGhi lại cấu hình riêng của môi trường bạn, ví dụ:\n- Tên thiết bị, camera, SSH hosts\n- Giọng nói ưa thích (TTS)\n- Alias và shortcut\n\n---\n\nThêm ghi chú nào giúp ích cho công việc của bạn.`
3902
3923
  : `# Tool Usage Guide
3903
3924
 
3904
3925
  ## Installed Skills
@@ -3907,7 +3928,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3907
3928
  ## Conventions
3908
3929
  - Prefer tools over guessing
3909
3930
  - Use Browser for explicit web tasks
3910
- - Update Memory when important user info appears`;
3931
+ - Update Memory when important user info appears\n\n## Your Setup Notes\n\nRecord environment-specific config, e.g.:\n- Device names, cameras, SSH hosts\n- Preferred TTS voice\n- Aliases and shortcuts\n\n---\n\nAdd whatever helps you do your job.`;
3911
3932
  const memoryMd = isVi
3912
3933
  ? `# Bộ nhớ dài hạn
3913
3934
 
@@ -3920,7 +3941,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3920
3941
  const files = {
3921
3942
  'IDENTITY.md': identityMd,
3922
3943
  'SOUL.md': soulMd,
3923
- 'AGENTS.md': agentsMd + extraAgentsMd,
3944
+ 'AGENTS.md': agentsMd + extraAgentsMd + '\n\n' + _secRules,
3924
3945
  'TEAM.md': teamMd,
3925
3946
  'USER.md': userMd,
3926
3947
  'TOOLS.md': toolsMd,
@@ -3945,11 +3966,11 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3945
3966
  files[`${base}/.openclaw/openclaw.json`] = botConfigContent(botIndex);
3946
3967
  files[`${base}/.openclaw/exec-approvals.json`] = botExecApprovalsContent(botIndex);
3947
3968
  files[`${base}/.openclaw/auth-profiles.json`] = botAuthProfilesContent(botIndex);
3948
- if (is9Router) files[`${base}/.openclaw/9router-smart-route-sync.js`] = native9RouterSyncScriptContent();
3969
+ if (is9Router) files[`${base}/.9router/9router-smart-route-sync.js`] = native9RouterSyncScriptContent();
3949
3970
  files[`${base}/.openclaw/agents/${agentId}.yaml`] = botAgentYamlContent(botIndex);
3950
3971
  files[`${base}/.openclaw/agents/${agentId}/agent/auth-profiles.json`] = botAuthProfilesContent(botIndex);
3951
3972
  Object.entries(botWorkspaceFiles(botIndex)).forEach(([name, content]) => {
3952
- files[`${base}/.openclaw/workspace/${name}`] = content;
3973
+ files[`${base}/.openclaw/workspace-${agentId}/${name}`] = content;
3953
3974
  });
3954
3975
  return files;
3955
3976
  }
@@ -3991,6 +4012,9 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3991
4012
  return Object.fromEntries(Object.entries(files).map(([relPath, content]) => {
3992
4013
  const normalized = relPath.replace(/\\/g, '/');
3993
4014
  if (normalized === '.env') return ['%PROJECT_DIR%\\.env', content];
4015
+ if (normalized.startsWith('.9router/')) {
4016
+ return [`%DATA_DIR%\\${normalized.slice('.9router/'.length).replace(/\//g, '\\')}`, content];
4017
+ }
3994
4018
  if (normalized.startsWith('.openclaw/')) {
3995
4019
  return [`%OPENCLAW_HOME%\\${normalized.slice('.openclaw/'.length).replace(/\//g, '\\')}`, content];
3996
4020
  }
@@ -4024,6 +4048,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4024
4048
  'where node >nul 2>&1 || (echo ERROR: Node.js chua cai! Tai tai: https://nodejs.org && pause && exit /b 1)',
4025
4049
  'echo [2/5] Cai OpenClaw CLI...',
4026
4050
  `call npm install -g openclaw@2026.4.5 ${openClawRuntimePackages} || goto :fail`,
4051
+ 'echo [OK] OpenClaw da duoc cai dat thanh cong.',
4027
4052
  ];
4028
4053
  providerLines(lines, 'bat');
4029
4054
  if (hasBrowser) {
@@ -4044,7 +4069,10 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4044
4069
  if (isMultiBot) {
4045
4070
  lines.push('echo [4/5] Tao runtime multi-agent dung chung...');
4046
4071
  appendBatWriteCommands(lines, mapWindowsNativeFiles(sharedNativeFileMap()));
4047
- if (is9Router) lines.push(windowsHiddenNodeLaunch('%OPENCLAW_HOME%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
4072
+ // Write uninstall script to project folder
4073
+ const _uninstallWinMulti = generateUninstallScript();
4074
+ if (_uninstallWinMulti) appendBatWriteCommands(lines, mapWindowsNativeFiles({ [_uninstallWinMulti.name]: _uninstallWinMulti.content }));
4075
+ if (is9Router) lines.push(windowsHiddenNodeLaunch('%DATA_DIR%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
4048
4076
  lines.push('if not exist "%OPENCLAW_HOME%\\openclaw.json" (echo ERROR: Khong tim thay "%OPENCLAW_HOME%\\openclaw.json" && goto :fail)');
4049
4077
  lines.push('echo.');
4050
4078
  lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
@@ -4063,7 +4091,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4063
4091
  lines.push('echo Cho gateway khoi dong (15 giay)...');
4064
4092
  lines.push('timeout /t 15 /nobreak >nul');
4065
4093
  lines.push('echo [6/6] Dang nhap Zalo - dang tao ma QR...');
4066
- lines.push('openclaw channels login --channel zalouser --verbose');
4094
+ lines.push('openclaw channels login --channel zalouser --instance default --verbose');
4067
4095
  lines.push('echo.');
4068
4096
  // Copy QR PNG from TEMP to project dir so user can open it easily
4069
4097
  lines.push('set "QR_TMP=%TEMP%\\openclaw\\openclaw-zalouser-qr-default.png"');
@@ -4082,12 +4110,158 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4082
4110
  lines.push('echo De khoi dong lai: openclaw gateway run');
4083
4111
  } else {
4084
4112
  lines.push('echo [5/5] Khoi dong gateway multi-bot...');
4085
- lines.push('call openclaw gateway run');
4113
+ lines.push(':: Khoi dong OpenClaw Gateway trong cua so moi');
4114
+ lines.push('echo $env:OPENCLAW_HOME = \'%OPENCLAW_HOME%\' > "%TEMP%\\oc-startgw.ps1"');
4115
+ lines.push('echo $env:OPENCLAW_STATE_DIR = \'%OPENCLAW_HOME%\' >> "%TEMP%\\oc-startgw.ps1"');
4116
+ lines.push('echo $b = Join-Path $env:APPDATA \'npm\\openclaw.cmd\' >> "%TEMP%\\oc-startgw.ps1"');
4117
+ lines.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\openclaw\' } >> "%TEMP%\\oc-startgw.ps1"');
4118
+ lines.push("echo Start-Process 'cmd.exe' -WindowStyle Normal -WorkingDirectory '%PROJECT_DIR%' -ArgumentList ^('/c \"' + $b + '\" gateway run'^) >> \"%TEMP%\\oc-startgw.ps1\"");
4119
+ lines.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-startgw.ps1"');
4120
+ lines.push('del "%TEMP%\\oc-startgw.ps1" >nul 2>&1');
4121
+ lines.push('timeout /t 5 /nobreak >nul');
4122
+ lines.push('echo.');
4123
+ lines.push('echo [OK] OpenClaw Gateway dang khoi dong trong cua so moi!');
4124
+ lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
4125
+ lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
4126
+ }
4127
+ } else if (isComboChannel) {
4128
+ // ── Combo: Telegram + Zalo Personal — 2 bots, 1 gateway ─────────────
4129
+ lines.push('echo [4/5] Tao file cau hinh cho 2 bot (Telegram + Zalo Personal)...');
4130
+ // Bot 0 = Telegram bot
4131
+ const bot0Files = botFiles(0);
4132
+ // Bot 1 = Zalo bot (same workspace root, separate agent/workspace dirs)
4133
+ const bot1 = state.bots[1] || {};
4134
+ const zaloName = bot1.name || 'Zalo Bot';
4135
+ const zaloDesc = bot1.desc || (isVi ? 'Tro ly Zalo ca nhan' : 'Personal Zalo assistant');
4136
+ const zaloPersona = bot1.persona || '';
4137
+ const zaloAgentId = zaloName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'zalo-bot';
4138
+ const bot0Name = (state.bots[0] || {}).name || 'Bot 1';
4139
+ const bot0AgentId = bot0Name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'bot-1';
4140
+ // Merge bot0's config to also include zalo agent in agents.list + bindings
4141
+ const mergedConfig = JSON.parse(bot0Files['./.openclaw/openclaw.json'] || '{}');
4142
+ if (!mergedConfig.agents) mergedConfig.agents = { defaults: {}, list: [] };
4143
+ if (!Array.isArray(mergedConfig.agents.list)) mergedConfig.agents.list = [];
4144
+ // Add Zalo agent to list if not already there
4145
+ const hasZaloInList = mergedConfig.agents.list.some(a => a.id === zaloAgentId);
4146
+ if (!hasZaloInList) {
4147
+ mergedConfig.agents.list.push({
4148
+ id: zaloAgentId,
4149
+ name: zaloName,
4150
+ workspace: `.openclaw/workspace-${zaloAgentId}`,
4151
+ agentDir: `agents/${zaloAgentId}/agent`,
4152
+ model: { primary: (bot1.model || state.config.model) },
4153
+ });
4154
+ }
4155
+ // Ensure bindings exist
4156
+ if (!Array.isArray(mergedConfig.bindings)) mergedConfig.bindings = [];
4157
+ // Bind Telegram bot to bot0
4158
+ const hasTelegramBinding = mergedConfig.bindings.some(b => b && b.match && b.match.channel === 'telegram');
4159
+ if (!hasTelegramBinding) {
4160
+ mergedConfig.bindings.push({ agentId: bot0AgentId, match: { channel: 'telegram', accountId: 'default' } });
4086
4161
  }
4162
+ // Bind Zalo channel to zalo agent
4163
+ const hasZaloBinding = mergedConfig.bindings.some(b => b && b.match && b.match.channel === 'zalouser');
4164
+ if (!hasZaloBinding) {
4165
+ mergedConfig.bindings.push({ agentId: zaloAgentId, match: { channel: 'zalouser', accountId: 'default' } });
4166
+ }
4167
+ // Ensure zalouser channel is in config
4168
+ if (!mergedConfig.channels) mergedConfig.channels = {};
4169
+ if (!mergedConfig.channels.zalouser) {
4170
+ mergedConfig.channels.zalouser = { enabled: true, dmPolicy: 'open', autoReply: true };
4171
+ }
4172
+ bot0Files['./.openclaw/openclaw.json'] = JSON.stringify(mergedConfig, null, 2);
4173
+ appendBatWriteCommands(lines, mapWindowsNativeFiles(bot0Files));
4174
+ // Zalo agent YAML
4175
+ const zaloAgentYaml = `name: ${zaloAgentId}\ndescription: "${zaloDesc}"\n\nmodel:\n primary: ${bot1.model || state.config.model}`;
4176
+ const zaloWorkspaceDir = `workspace-${zaloAgentId}`;
4177
+ const _zaloSecRules = state.config.securityRules || DEFAULT_SECURITY_RULES[isVi ? 'vi' : 'en'];
4178
+ const zaloFiles = {
4179
+ [`.openclaw/agents/${zaloAgentId}.yaml`]: zaloAgentYaml,
4180
+ [`.openclaw/agents/${zaloAgentId}/agent/auth-profiles.json`]: sharedNativeAuthProfilesContent(),
4181
+ [`.openclaw/${zaloWorkspaceDir}/IDENTITY.md`]: isVi
4182
+ ? `# Danh tinh\n\n- **Ten:** ${zaloName}\n- **Vai tro:** ${zaloDesc}\n\n---\n\nMinh la **${zaloName}**. Khi ai hoi ten, minh tra loi: _"Minh la ${zaloName}"_.`
4183
+ : `# Identity\n\n- **Name:** ${zaloName}\n- **Role:** ${zaloDesc}\n\n---\n\nI am **${zaloName}**. When asked my name, I answer: _"I'm ${zaloName}"_.`,
4184
+ [`.openclaw/${zaloWorkspaceDir}/SOUL.md`]: isVi
4185
+ ? `# Tinh cach\n\n**Huu ich that su.** Bo qua cau ne, cu giup thang.\n**Co ca tinh.** Tro ly khong co ca tinh thi chi la cong cu.\n\n## Phong cach\n- Tu nhien, gan gui\n- Truc tiep, ngan gon${zaloPersona ? `\n\n## Custom Rules\n${zaloPersona}` : ''}`
4186
+ : `# Soul\n\n**Be genuinely helpful.** Skip filler and just help.\n**Have personality.** An assistant with no personality is just a tool.\n\n## Style\n- Natural and concise\n- Direct and practical${zaloPersona ? `\n\n## Custom Rules\n${zaloPersona}` : ''}`,
4187
+ [`.openclaw/${zaloWorkspaceDir}/AGENTS.md`]: isVi
4188
+ ? `# Huong dan van hanh\n\n## Vai tro\nBan la **${zaloName}**, ${zaloDesc.toLowerCase()}.\n\n## Kenh Zalo Personal\n- Ban hoat dong tren kenh Zalo Personal (zca-js).\n- Tra loi moi tin nhan DM theo chinh sach dmPolicy: open.\n- Khong can duoc goi ten moi tra loi (DM la rieng tu).\n\n## Quy tac tra loi\n- Tra loi ngan gon, suc tich\n- Uu tien tieng Viet\n- Khi hoi ten: _"Minh la ${zaloName}"_\n- Khong bia thong tin\n\n${_zaloSecRules}`
4189
+ : `# Operating Manual\n\n## Role\nYou are **${zaloName}**, ${zaloDesc.toLowerCase()}.\n\n## Zalo Personal Channel\n- You operate on the Zalo Personal channel (zca-js).\n- Reply to all DMs with dmPolicy: open.\n- DMs are private — no need to be mentioned to reply.\n\n## Reply Rules\n- Be concise\n- Prefer Vietnamese\n- When asked your name: _"I'm ${zaloName}"_\n- Never fabricate information\n\n${_zaloSecRules}`,
4190
+ [`.openclaw/${zaloWorkspaceDir}/TEAM.md`]: isVi
4191
+ ? `# Doi Bot\n\n## ${bot0Name}\n- Vai tro: ${(state.bots[0] || {}).desc || 'Tro ly Telegram'}\n- Kenh: Telegram\n\n## ${zaloName}\n- Vai tro: ${zaloDesc}\n- Kenh: Zalo Personal`
4192
+ : `# Bot Team\n\n## ${bot0Name}\n- Role: ${(state.bots[0] || {}).desc || 'Telegram assistant'}\n- Channel: Telegram\n\n## ${zaloName}\n- Role: ${zaloDesc}\n- Channel: Zalo Personal`,
4193
+ [`.openclaw/${zaloWorkspaceDir}/USER.md`]: isVi
4194
+ ? `# Thong tin nguoi dung\n\n## Tong quan\n- **Ngon ngu uu tien:** Tieng Viet\n\n## Thong tin ca nhan\n${state.config.userInfo || '- _(Chua co gi)_'}`
4195
+ : `# User Profile\n\n## Overview\n- **Preferred language:** Vietnamese\n\n## Notes\n${state.config.userInfo || '- _(Nothing yet)_'}`,
4196
+ [`.openclaw/${zaloWorkspaceDir}/MEMORY.md`]: isVi
4197
+ ? `# Bo nho dai han\n\n## Ghi chu\n- _(Chua co gi)_`
4198
+ : `# Long-term Memory\n\n## Notes\n- _(Nothing yet)_`,
4199
+ [`.openclaw/${zaloWorkspaceDir}/TOOLS.md`]: isVi
4200
+ ? `# Hướng dẫn sử dụng Tools\n\n## Skills đã cài\n${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(Chưa có skill nào)_'}\n\n## Quy ước\n- Ưu tiên dùng tool thay vì đoán\n- Browser: dùng khi user yêu cầu thao tác web\n- Memory: cập nhật khi biết thông tin quan trọng\n\n## Ghi chú thiết lập của bạn\n\nGhi lại cấu hình riêng của môi trường bạn, ví dụ:\n- Tên thiết bị, camera, SSH hosts\n- Giọng nói ưa thích (TTS)\n\n---\n\nThêm ghi chú nào giúp ích cho công việc của bạn.`
4201
+ : `# Tool Usage Guide\n\n## Installed Skills\n${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills installed)_'}\n\n## Conventions\n- Prefer tools over guessing\n- Use Browser for explicit web tasks\n- Update Memory when important user info appears\n\n## Your Setup Notes\n\nRecord environment-specific config, e.g.:\n- Device names, cameras, SSH hosts\n- Preferred TTS voice\n\n---\n\nAdd whatever helps you do your job.`,
4202
+ };
4203
+ appendBatWriteCommands(lines, mapWindowsNativeFiles(zaloFiles));
4204
+ if (is9Router) lines.push(windowsHiddenNodeLaunch('%DATA_DIR%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
4205
+ lines.push('if not exist "%OPENCLAW_HOME%\\openclaw.json" (echo ERROR: Khong tim thay "%OPENCLAW_HOME%\\openclaw.json" && goto :fail)');
4206
+ lines.push('echo.');
4207
+ lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
4208
+ lines.push('echo Other reachable URLs: http://localhost:18791');
4209
+ lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
4210
+ if (is9Router) {
4211
+ lines.push('echo.');
4212
+ lines.push('echo 9Router Dashboard: http://127.0.0.1:20128/dashboard');
4213
+ lines.push('echo Other reachable URLs: http://localhost:20128/dashboard');
4214
+ }
4215
+ // Login Zalo trực tiếp (không cần gateway chạy trước — openclaw channels login standalone)
4216
+ // Sau khi login thành công, gateway sẽ tự nhận credentials khi khởi động.
4217
+ lines.push('echo [5/6] Dang nhap Zalo Personal...');
4218
+ lines.push('echo.');
4219
+ lines.push('echo === HUONG DAN DANG NHAP ZALO ===');
4220
+ lines.push('echo OpenClaw se hien duong dan file anh QR trong cua so nay.');
4221
+ lines.push('echo Hay mo file anh do, chon Quet QR trong app Zalo va quet.');
4222
+ lines.push('echo Neu het gio, nhap R de tao ma QR moi.');
4223
+ lines.push('echo ================================');
4224
+ lines.push('echo.');
4225
+ lines.push(':retry_zalo');
4226
+ lines.push('echo Dang tao ma QR Zalo moi...');
4227
+ lines.push('echo.');
4228
+ lines.push('call openclaw channels login --channel zalouser --verbose');
4229
+ lines.push('if %ERRORLEVEL% equ 0 goto :zalo_done');
4230
+ lines.push('echo.');
4231
+ lines.push('echo [WARN] Ma QR het han hoac chua duoc quet kip.');
4232
+ lines.push('echo R - Tao ma QR moi va thu lai');
4233
+ lines.push('echo Enter - Bo qua dang nhap Zalo (Zalo se khong hoat dong)');
4234
+ lines.push('set /p RETRY_ZALO=Ban chon: ');
4235
+ lines.push('if /i "%RETRY_ZALO%"=="R" goto :retry_zalo');
4236
+ lines.push('echo [SKIP] Bo qua. Zalo se khong hoat dong cho den khi dang nhap lai.');
4237
+ lines.push('goto :zalo_continue');
4238
+ lines.push(':zalo_done');
4239
+ lines.push('echo [OK] Dang nhap Zalo thanh cong!');
4240
+ lines.push(':zalo_continue');
4241
+ lines.push(':: Dong gateway cu (neu co lock file) truoc khi khoi dong lai');
4242
+ lines.push('call openclaw gateway stop 2>nul');
4243
+ lines.push('timeout /t 2 /nobreak >nul');
4244
+ lines.push('echo [6/6] Khoi dong bot (Telegram + Zalo)...');
4245
+ lines.push(':: Khoi dong OpenClaw Gateway trong cua so moi');
4246
+ lines.push('echo $env:OPENCLAW_HOME = \'%OPENCLAW_HOME%\' > "%TEMP%\\oc-startgw.ps1"');
4247
+ lines.push('echo $env:OPENCLAW_STATE_DIR = \'%OPENCLAW_HOME%\' >> "%TEMP%\\oc-startgw.ps1"');
4248
+ lines.push('echo $b = Join-Path $env:APPDATA \'npm\\openclaw.cmd\' >> "%TEMP%\\oc-startgw.ps1"');
4249
+ lines.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\openclaw\' } >> "%TEMP%\\oc-startgw.ps1"');
4250
+ lines.push("echo Start-Process 'cmd.exe' -WindowStyle Normal -WorkingDirectory '%PROJECT_DIR%' -ArgumentList ^('/c \"' + $b + '\" gateway run'^) >> \"%TEMP%\\oc-startgw.ps1\"");
4251
+ lines.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-startgw.ps1"');
4252
+ lines.push('del "%TEMP%\\oc-startgw.ps1" >nul 2>&1');
4253
+ lines.push('timeout /t 5 /nobreak >nul');
4254
+ lines.push('echo.');
4255
+ lines.push('echo [OK] OpenClaw Gateway dang khoi dong trong cua so moi!');
4256
+ lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
4257
+ lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
4087
4258
  } else {
4088
4259
  lines.push('echo [4/5] Tao file cau hinh...');
4089
4260
  appendBatWriteCommands(lines, mapWindowsNativeFiles(botFiles(0)));
4090
- if (is9Router) lines.push(windowsHiddenNodeLaunch('%OPENCLAW_HOME%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
4261
+ // Write uninstall script to project folder
4262
+ const _uninstallWin = generateUninstallScript();
4263
+ if (_uninstallWin) appendBatWriteCommands(lines, mapWindowsNativeFiles({ [_uninstallWin.name]: _uninstallWin.content }));
4264
+ if (is9Router) lines.push(windowsHiddenNodeLaunch('%DATA_DIR%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
4091
4265
  lines.push('if not exist "%OPENCLAW_HOME%\\openclaw.json" (echo ERROR: Khong tim thay "%OPENCLAW_HOME%\\openclaw.json" && goto :fail)');
4092
4266
  lines.push('echo.');
4093
4267
  lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
@@ -4098,27 +4272,54 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4098
4272
  lines.push('echo 9Router Dashboard: http://127.0.0.1:20128/dashboard');
4099
4273
  lines.push('echo Other reachable URLs: http://localhost:20128/dashboard');
4100
4274
  }
4101
- const needsZaloLogin = state.channel === 'zalo-personal' || state.channel === 'telegram+zalo-personal';
4275
+ const needsZaloLogin = state.channel === 'zalo-personal';
4102
4276
  if (needsZaloLogin) {
4103
- lines.push('echo [5/6] Khoi dong gateway (cua so moi) de chuan bi dang nhap Zalo...');
4104
- lines.push('start "OpenClaw Gateway" cmd /c "cd /d %PROJECT_DIR% && set OPENCLAW_HOME=%OPENCLAW_HOME% && set OPENCLAW_STATE_DIR=%OPENCLAW_HOME% && openclaw gateway run"');
4105
- lines.push('echo Cho gateway khoi dong (15 giay)...');
4106
- lines.push('timeout /t 15 /nobreak >nul');
4107
- lines.push('echo [6/6] Dang nhap Zalo...');
4108
- lines.push('echo.');
4109
- lines.push('echo ============================================================');
4110
- lines.push('echo Cua so CMD moi se mo de dang nhap Zalo.');
4111
- lines.push('echo Hay lam theo huong dan trong cua so do:');
4112
- lines.push('echo 1. Chon "Install Zalo Personal plugin?" (Enter)');
4113
- lines.push('echo 2. Doi QR hien ra - mo app Zalo quet ma QR');
4114
- lines.push('echo 3. Doi den khi thay "Login success" hoac token');
4115
- lines.push('echo ============================================================');
4116
- lines.push('start "Zalo Login" cmd /k "cd /d \"%PROJECT_DIR%\" && set OPENCLAW_HOME=%OPENCLAW_HOME% && set OPENCLAW_STATE_DIR=%OPENCLAW_HOME% && openclaw channels login --channel zalouser --verbose"');
4117
- lines.push('echo Gateway dang chay trong cua so rieng.');
4118
- lines.push('echo De khoi dong lai: openclaw gateway run');
4277
+ // Login Zalo trực tiếp (không cần gateway chạy trước)
4278
+ lines.push('echo [5/5] Dang nhap Zalo Personal...');
4279
+ lines.push('echo.');
4280
+ lines.push('echo === HUONG DAN DANG NHAP ZALO ===');
4281
+ lines.push('echo Cua so Zalo Login se mo. Hay:');
4282
+ lines.push('echo 1. Doi QR hien ra trong cua so Zalo Login');
4283
+ lines.push('echo 2. Mo app Zalo, chon Quet QR va quet ma');
4284
+ lines.push('echo 3. Doi thay chu "Login successful" trong cua so do');
4285
+ lines.push('echo 4. Dong cua so Zalo Login');
4286
+ lines.push('echo ================================');
4287
+ lines.push('echo.');
4288
+ lines.push('start "Zalo Login" cmd /k "cd /d \"%PROJECT_DIR%\" && set OPENCLAW_HOME=%OPENCLAW_HOME% && set OPENCLAW_STATE_DIR=%OPENCLAW_HOME% && openclaw channels login --channel zalouser --verbose"');
4289
+ lines.push('echo Nhan phim bat ky sau khi dong cua so Zalo Login...');
4290
+ lines.push('pause >nul');
4291
+ lines.push(':: Dong gateway cu (neu co lock file tu cua so Zalo Login) truoc khi khoi dong lai');
4292
+ lines.push('call openclaw gateway stop 2>nul');
4293
+ lines.push('timeout /t 2 /nobreak >nul');
4294
+ lines.push('echo [6/6] Khoi dong bot...');
4295
+ lines.push(':: Khoi dong OpenClaw Gateway trong cua so moi');
4296
+ lines.push('echo $env:OPENCLAW_HOME = \'%OPENCLAW_HOME%\' > "%TEMP%\\oc-startgw.ps1"');
4297
+ lines.push('echo $env:OPENCLAW_STATE_DIR = \'%OPENCLAW_HOME%\' >> "%TEMP%\\oc-startgw.ps1"');
4298
+ lines.push('echo $b = Join-Path $env:APPDATA \'npm\\openclaw.cmd\' >> "%TEMP%\\oc-startgw.ps1"');
4299
+ lines.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\openclaw\' } >> "%TEMP%\\oc-startgw.ps1"');
4300
+ lines.push("echo Start-Process 'cmd.exe' -WindowStyle Normal -WorkingDirectory '%PROJECT_DIR%' -ArgumentList ^('/c \"' + $b + '\" gateway run'^) >> \"%TEMP%\\oc-startgw.ps1\"");
4301
+ lines.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-startgw.ps1"');
4302
+ lines.push('del "%TEMP%\\oc-startgw.ps1" >nul 2>&1');
4303
+ lines.push('timeout /t 5 /nobreak >nul');
4304
+ lines.push('echo.');
4305
+ lines.push('echo [OK] OpenClaw Gateway dang khoi dong trong cua so moi!');
4306
+ lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
4307
+ lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
4119
4308
  } else {
4120
4309
  lines.push('echo [5/5] Khoi dong bot...');
4121
- lines.push('call openclaw gateway run');
4310
+ lines.push(':: Khoi dong OpenClaw Gateway trong cua so moi');
4311
+ lines.push('echo $env:OPENCLAW_HOME = \'%OPENCLAW_HOME%\' > "%TEMP%\\oc-startgw.ps1"');
4312
+ lines.push('echo $env:OPENCLAW_STATE_DIR = \'%OPENCLAW_HOME%\' >> "%TEMP%\\oc-startgw.ps1"');
4313
+ lines.push('echo $b = Join-Path $env:APPDATA \'npm\\openclaw.cmd\' >> "%TEMP%\\oc-startgw.ps1"');
4314
+ lines.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\openclaw\' } >> "%TEMP%\\oc-startgw.ps1"');
4315
+ lines.push("echo Start-Process 'cmd.exe' -WindowStyle Normal -WorkingDirectory '%PROJECT_DIR%' -ArgumentList ^('/c \"' + $b + '\" gateway run'^) >> \"%TEMP%\\oc-startgw.ps1\"");
4316
+ lines.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-startgw.ps1"');
4317
+ lines.push('del "%TEMP%\\oc-startgw.ps1" >nul 2>&1');
4318
+ lines.push('timeout /t 5 /nobreak >nul');
4319
+ lines.push('echo.');
4320
+ lines.push('echo [OK] OpenClaw Gateway dang khoi dong trong cua so moi!');
4321
+ lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
4322
+ lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
4122
4323
  }
4123
4324
  }
4124
4325
 
@@ -4184,10 +4385,14 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4184
4385
 
4185
4386
  if (isMultiBot) {
4186
4387
  appendShWriteCommands(sh, sharedNativeFileMap());
4388
+ const _uninstallMacMulti = generateUninstallScript();
4389
+ if (_uninstallMacMulti) appendShWriteCommands(sh, { [_uninstallMacMulti.name]: _uninstallMacMulti.content });
4187
4390
  sh.push('echo "Starting shared multi-bot gateway..."');
4188
4391
  sh.push('openclaw gateway run');
4189
4392
  } else {
4190
4393
  appendShWriteCommands(sh, botFiles(0));
4394
+ const _uninstallMac = generateUninstallScript();
4395
+ if (_uninstallMac) appendShWriteCommands(sh, { [_uninstallMac.name]: _uninstallMac.content });
4191
4396
  sh.push('openclaw gateway run');
4192
4397
  }
4193
4398
  scriptContent = sh.filter(Boolean).join('\n');
@@ -4223,11 +4428,13 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4223
4428
  if (isMultiBot) {
4224
4429
  vps.push('echo "--- Creating shared multi-agent runtime ---"');
4225
4430
  appendShWriteCommands(vps, sharedNativeFileMap());
4431
+ const _uninstallVpsMulti = generateUninstallScript();
4432
+ if (_uninstallVpsMulti) appendShWriteCommands(vps, { [_uninstallVpsMulti.name]: _uninstallVpsMulti.content });
4226
4433
  vps.push('echo "--- Starting shared gateway via PM2 ---"');
4227
4434
  if (is9Router) {
4228
4435
  vps.push(`NINE_ROUTER_ENTRY="$(${native9RouterServerEntryLookup()})"`);
4229
4436
  vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$NINE_ROUTER_ENTRY" --name openclaw-multibot-9router --interpreter "$(command -v node)"');
4230
- vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
4437
+ vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.9router/9router-smart-route-sync.js"');
4231
4438
  }
4232
4439
  vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
4233
4440
  vps.push('pm2 save && pm2 startup');
@@ -4238,10 +4445,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4238
4445
  vps.push(`echo " pm2 logs openclaw-multibot"`);
4239
4446
  } else {
4240
4447
  appendShWriteCommands(vps, botFiles(0));
4448
+ const _uninstallVps = generateUninstallScript();
4449
+ if (_uninstallVps) appendShWriteCommands(vps, { [_uninstallVps.name]: _uninstallVps.content });
4241
4450
  if (is9Router) {
4242
4451
  vps.push(`NINE_ROUTER_ENTRY="$(${native9RouterServerEntryLookup()})"`);
4243
4452
  vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$NINE_ROUTER_ENTRY" --name openclaw-9router --interpreter "$(command -v node)"');
4244
- vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
4453
+ vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.9router/9router-smart-route-sync.js"');
4245
4454
  }
4246
4455
  vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
4247
4456
  vps.push('pm2 save && pm2 startup');
@@ -4277,10 +4486,14 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4277
4486
 
4278
4487
  if (isMultiBot) {
4279
4488
  appendShWriteCommands(lnx, sharedNativeFileMap());
4489
+ const _uninstallLnxMulti = generateUninstallScript();
4490
+ if (_uninstallLnxMulti) appendShWriteCommands(lnx, { [_uninstallLnxMulti.name]: _uninstallLnxMulti.content });
4280
4491
  lnx.push('echo "Starting shared multi-bot gateway..."');
4281
4492
  lnx.push('openclaw gateway run');
4282
4493
  } else {
4283
4494
  appendShWriteCommands(lnx, botFiles(0));
4495
+ const _uninstallLnx = generateUninstallScript();
4496
+ if (_uninstallLnx) appendShWriteCommands(lnx, { [_uninstallLnx.name]: _uninstallLnx.content });
4284
4497
  lnx.push('openclaw gateway run');
4285
4498
  }
4286
4499
  scriptContent = lnx.filter(Boolean).join('\n');
@@ -4344,60 +4557,60 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
4344
4557
  if (os === 'win' && !isDocker) {
4345
4558
  return {
4346
4559
  name: 'uninstall-openclaw-win.bat',
4347
- content: `@echo off
4348
- setlocal EnableExtensions
4349
- chcp 65001 >nul
4350
- echo.
4351
- echo ============================================================
4352
- echo OpenClaw Uninstaller - Windows Native
4353
- echo Project: ${absWin}
4354
- echo ============================================================
4355
- echo.
4356
- echo [WARNING] This will:
4357
- echo 1. Kill openclaw and 9router background processes
4358
- echo 2. Uninstall global npm packages (openclaw, 9router)
4359
- echo 3. Delete the project folder and all its data
4360
- echo.
4361
- set /p CONFIRM=Nhap YES de xac nhan xoa toan bo:
4362
- if /i not "%CONFIRM%"=="YES" (
4363
- echo Huy bo. Khong xoa gi ca.
4364
- pause
4365
- exit /b 0
4366
- )
4367
- echo.
4368
- echo [1/4] Dang dung cac tien trinh openclaw va 9router...
4369
- taskkill /F /IM openclaw.exe >nul 2>&1
4370
- taskkill /F /IM 9router.exe >nul 2>&1
4371
- powershell -NoProfile -Command "Get-Process node -ErrorAction SilentlyContinue | Where-Object { $_.Path -like '*${absWin.replace(/\/g, '\\\\')}*' } | Stop-Process -Force" >nul 2>&1
4372
- powershell -NoProfile -Command "& { $p=@(18791,20128); foreach($port in $p){ $id=(netstat -ano | Select-String \":\$($port) \").Line -split ' +' | Select-Object -Last 1; if($id -and $id -ne '0'){ Stop-Process -Id $id -Force -ErrorAction SilentlyContinue } } }" >nul 2>&1
4373
- echo OK: Tien trinh da dung.
4374
- echo.
4375
- echo [2/4] Dang go cai npm packages toan cau...
4376
- set "PATH=%APPDATA%\npm;%PATH%"
4377
- call npm uninstall -g openclaw 9router grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>nul
4378
- echo OK: npm packages da duoc go cai.
4379
- echo.
4380
- echo [3/4] Xoa thu muc project...
4381
- set "TARGET=${absWin}"
4382
- if exist "%TARGET%" (
4383
- rd /s /q "%TARGET%"
4384
- echo OK: Da xoa %TARGET%
4385
- ) else (
4386
- echo INFO: Thu muc khong ton tai: %TARGET%
4387
- )
4388
- echo.
4389
- echo [4/4] Xoa thu muc .9router trong Home (neu co)...
4390
- if exist "%USERPROFILE%\.9router" (
4391
- set /p CLEAN_HOME=Xoa ca %USERPROFILE%\.9router? (YES/no):
4392
- if /i "%CLEAN_HOME%"=="YES" rd /s /q "%USERPROFILE%\.9router" >nul 2>&1
4393
- )
4394
- echo.
4395
- echo ============================================================
4396
- echo Go cai hoan tat!
4397
- echo De cai lai: chay lai file setup hoac npx create-openclaw-bot
4398
- echo ============================================================
4399
- pause
4400
- endlocal
4560
+ content: `@echo off
4561
+ setlocal EnableExtensions
4562
+ chcp 65001 >nul
4563
+ echo.
4564
+ echo ============================================================
4565
+ echo OpenClaw Uninstaller - Windows Native
4566
+ echo Project: ${absWin}
4567
+ echo ============================================================
4568
+ echo.
4569
+ echo [WARNING] This will:
4570
+ echo 1. Kill openclaw and 9router background processes
4571
+ echo 2. Uninstall global npm packages (openclaw, 9router)
4572
+ echo 3. Delete the project folder and all its data
4573
+ echo.
4574
+ set /p CONFIRM=Nhap YES de xac nhan xoa toan bo:
4575
+ if /i not "%CONFIRM%"=="YES" (
4576
+ echo Huy bo. Khong xoa gi ca.
4577
+ pause
4578
+ exit /b 0
4579
+ )
4580
+ echo.
4581
+ echo [1/4] Dang dung cac tien trinh openclaw va 9router...
4582
+ wmic process where "Name='node.exe' and CommandLine like '%%9router%%'" delete >nul 2>&1
4583
+ wmic process where "Name='cmd.exe' and CommandLine like '%%9router%%'" delete >nul 2>&1
4584
+ wmic process where "Name='node.exe' and CommandLine like '%%openclaw.mjs%%'" delete >nul 2>&1
4585
+ timeout /t 2 /nobreak >nul
4586
+ echo OK: Tien trinh da dung.
4587
+ echo.
4588
+ echo [2/4] Dang go cai npm packages toan cau...
4589
+ set "PATH=%APPDATA%\\npm;%PATH%"
4590
+ call npm uninstall -g openclaw 9router grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>nul
4591
+ echo OK: npm packages da duoc go cai.
4592
+ echo.
4593
+ echo [3/4] Xoa thu muc project...
4594
+ set "TARGET=${absWin}"
4595
+ if exist "%TARGET%" (
4596
+ rd /s /q "%TARGET%"
4597
+ echo OK: Da xoa %TARGET%
4598
+ ) else (
4599
+ echo INFO: Thu muc khong ton tai: %TARGET%
4600
+ )
4601
+ echo.
4602
+ echo [4/4] Xoa thu muc .9router trong Home (neu co)...
4603
+ if exist "%USERPROFILE%\\.9router" (
4604
+ set /p CLEAN_HOME=Xoa ca %USERPROFILE%\\.9router? [YES/no]:
4605
+ if /i "%CLEAN_HOME%"=="YES" rd /s /q "%USERPROFILE%\\.9router" >nul 2>&1
4606
+ )
4607
+ echo.
4608
+ echo ============================================================
4609
+ echo Go cai hoan tat!
4610
+ echo De cai lai: chay lai file setup hoac npx create-openclaw-bot
4611
+ echo ============================================================
4612
+ pause
4613
+ endlocal
4401
4614
  `
4402
4615
  };
4403
4616
  }
@@ -4406,42 +4619,42 @@ endlocal
4406
4619
  if (os === 'win' && isDocker) {
4407
4620
  return {
4408
4621
  name: 'uninstall-openclaw-docker.bat',
4409
- content: `@echo off
4410
- setlocal EnableExtensions
4411
- chcp 65001 >nul
4412
- echo.
4413
- echo ============================================================
4414
- echo OpenClaw Uninstaller - Docker (Windows)
4415
- echo Project: ${absWin}
4416
- echo ============================================================
4417
- echo.
4418
- echo [WARNING] This will stop Docker containers and delete the project folder.
4419
- echo.
4420
- set /p CONFIRM=Nhap YES de xac nhan xoa toan bo:
4421
- if /i not "%CONFIRM%"=="YES" (
4422
- echo Huy bo. Khong xoa gi ca.
4423
- pause
4424
- exit /b 0
4425
- )
4426
- echo.
4427
- echo [1/2] Dang dung Docker containers...
4428
- cd /d "${absWin}\docker\openclaw" 2>nul && (
4429
- docker compose down --volumes --remove-orphans 2>nul || docker-compose down --volumes --remove-orphans 2>nul
4430
- echo OK: Containers da dung.
4431
- ) || echo INFO: Khong tim thay docker compose.
4432
- echo.
4433
- echo [2/2] Xoa thu muc project...
4434
- cd /d "%USERPROFILE%"
4435
- if exist "${absWin}" (
4436
- rd /s /q "${absWin}"
4437
- echo OK: Da xoa ${absWin}
4438
- )
4439
- echo.
4440
- echo ============================================================
4441
- echo Go cai hoan tat! De cai lai: npx create-openclaw-bot@latest
4442
- echo ============================================================
4443
- pause
4444
- endlocal
4622
+ content: `@echo off
4623
+ setlocal EnableExtensions
4624
+ chcp 65001 >nul
4625
+ echo.
4626
+ echo ============================================================
4627
+ echo OpenClaw Uninstaller - Docker (Windows)
4628
+ echo Project: ${absWin}
4629
+ echo ============================================================
4630
+ echo.
4631
+ echo [WARNING] This will stop Docker containers and delete the project folder.
4632
+ echo.
4633
+ set /p CONFIRM=Nhap YES de xac nhan xoa toan bo:
4634
+ if /i not "%CONFIRM%"=="YES" (
4635
+ echo Huy bo. Khong xoa gi ca.
4636
+ pause
4637
+ exit /b 0
4638
+ )
4639
+ echo.
4640
+ echo [1/2] Dang dung Docker containers...
4641
+ cd /d "${absWin}\docker\openclaw" 2>nul && (
4642
+ docker compose down --volumes --remove-orphans 2>nul || docker-compose down --volumes --remove-orphans 2>nul
4643
+ echo OK: Containers da dung.
4644
+ ) || echo INFO: Khong tim thay docker compose.
4645
+ echo.
4646
+ echo [2/2] Xoa thu muc project...
4647
+ cd /d "%USERPROFILE%"
4648
+ if exist "${absWin}" (
4649
+ rd /s /q "${absWin}"
4650
+ echo OK: Da xoa ${absWin}
4651
+ )
4652
+ echo.
4653
+ echo ============================================================
4654
+ echo Go cai hoan tat! De cai lai: npx create-openclaw-bot@latest
4655
+ echo ============================================================
4656
+ pause
4657
+ endlocal
4445
4658
  `
4446
4659
  };
4447
4660
  }
@@ -4487,7 +4700,7 @@ echo "[4/5] Removing project directory..."
4487
4700
  echo "[5/5] Checking home-level .9router / .openclaw..."
4488
4701
  for dir in "\$HOME/.9router" "\$HOME/.openclaw"; do
4489
4702
  if [ -d "\$dir" ]; then
4490
- read -rp "Delete \$dir ? (YES/no): " CLEAN
4703
+ read -rp "Delete \$dir ? [YES/no]: " CLEAN
4491
4704
  [ "\$CLEAN" = "YES" ] && rm -rf "\$dir" && echo " OK: Deleted \$dir" || echo " Kept: \$dir"
4492
4705
  fi
4493
4706
  done
@@ -4539,7 +4752,7 @@ echo "[3/4] Removing project directory..."
4539
4752
  echo "[4/4] Checking home-level .9router / .openclaw..."
4540
4753
  for dir in "\$HOME/.9router" "\$HOME/.openclaw"; do
4541
4754
  if [ -d "\$dir" ]; then
4542
- read -rp "Delete \$dir ? (YES/no): " CLEAN
4755
+ read -rp "Delete \$dir ? [YES/no]: " CLEAN
4543
4756
  [ "\$CLEAN" = "YES" ] && rm -rf "\$dir" && echo " OK: Deleted \$dir" || echo " Kept: \$dir"
4544
4757
  fi
4545
4758
  done
@@ -4582,7 +4795,7 @@ echo "[2/3] Removing project directory..."
4582
4795
 
4583
4796
  echo "[3/3] Checking home-level .openclaw..."
4584
4797
  if [ -d "\$HOME/.openclaw" ]; then
4585
- read -rp "Delete \$HOME/.openclaw? (YES/no): " CLEAN
4798
+ read -rp "Delete \$HOME/.openclaw? [YES/no]: " CLEAN
4586
4799
  [ "\$CLEAN" = "YES" ] && rm -rf "\$HOME/.openclaw" && echo " OK." || echo " Kept."
4587
4800
  fi
4588
4801
 
@@ -4715,7 +4928,7 @@ Write-Host " 🎉 ${isVi ? 'Setup hoan tat!' : 'Setup complete!'}" -ForegroundC
4715
4928
  ps += `Write-Host " ${isVi ? 'Mo http://localhost:30128/dashboard de login OAuth' : 'Open http://localhost:30128/dashboard to login OAuth'}" -ForegroundColor White\n`;
4716
4929
  }
4717
4930
  if (state.channel === 'zalo-personal' || state.channel === 'telegram+zalo-personal') {
4718
- ps += `Write-Host " ${isVi ? 'Chay: docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose' : 'Run: docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose'}" -ForegroundColor White\n`;
4931
+ ps += `Write-Host " ${isVi ? 'Chay: docker compose exec -it ai-bot openclaw channels login --channel zalouser --instance default --verbose' : 'Run: docker compose exec -it ai-bot openclaw channels login --channel zalouser --instance default --verbose'}" -ForegroundColor White\n`;
4719
4932
  ps += `Write-Host " ${isVi ? 'QR se nam tai /tmp/openclaw/openclaw-zalouser-qr-default.png' : 'QR will be written to /tmp/openclaw/openclaw-zalouser-qr-default.png'}" -ForegroundColor DarkGray\n`;
4720
4933
  ps += `Write-Host " ${isVi ? 'Copy QR ra ngoai: docker compose cp ai-bot:/tmp/openclaw/openclaw-zalouser-qr-default.png ./zalo-login-qr.png' : 'Copy the QR out: docker compose cp ai-bot:/tmp/openclaw/openclaw-zalouser-qr-default.png ./zalo-login-qr.png'}" -ForegroundColor DarkGray\n`;
4721
4934
  }
@@ -4896,7 +5109,7 @@ echo ""
4896
5109
 
4897
5110
  function generateZaloOnboardGuide() {
4898
5111
  const lang = document.getElementById('cfg-language')?.value || 'vi';
4899
- setOutput('out-zalo-onboard-cmd', `docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose`);
5112
+ setOutput('out-zalo-onboard-cmd', `docker compose exec -it ai-bot openclaw channels login --channel zalouser --instance default --verbose`);
4900
5113
 
4901
5114
  if (lang === 'vi') {
4902
5115
  setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
@@ -391,12 +391,11 @@ checks.push(() => expect(
391
391
  && setup.includes('function native9RouterServerEntryLookup() {')
392
392
  && setup.includes('return "node -e ')
393
393
  && !setup.includes('return "node -p ')
394
- && setup.includes("start \"9Router Dashboard\" /min cmd /c")
395
- && setup.includes("9router -n -H 0.0.0.0 -p 20128 --skip-update")
394
+ && setup.includes("oc-start9r.ps1") // Windows: writes temp PS1 launcher to avoid CMD→PS quoting issues
396
395
  && setup.includes('NINE_ROUTER_BIN="$(command -v 9router)"')
397
396
  && setup.includes('"$NINE_ROUTER_BIN" -n -H 0.0.0.0 -p 20128 --skip-update')
398
397
  && setup.includes("const p=path.join(process.env.DATA_DIR||'.9router','db.json');")
399
- && setup.includes('nohup env DATA_DIR="$PWD/.9router" node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &')
398
+ && setup.includes('nohup env DATA_DIR="$PWD/.9router" node ./.9router/9router-smart-route-sync.js > /tmp/9router-sync.log 2>&1 &')
400
399
  && setup.includes('set "PROJECT_DIR=')
401
400
  && setup.includes('set "OPENCLAW_HOME=%PROJECT_DIR%\\\\.openclaw"')
402
401
  && setup.includes('set "OPENCLAW_STATE_DIR=%PROJECT_DIR%\\\\.openclaw"')
@@ -434,8 +433,8 @@ checks.push(() => expect(
434
433
  && setup.includes("lines.push('echo Cai skills...');")
435
434
  && setup.includes("const openClawRuntimePackages = 'grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api';")
436
435
  && setup.includes("memory: 'none'")
437
- && setup.includes("workspace: 'workspace'")
438
- && setup.includes("workspace: meta.workspaceDir")
436
+ && setup.includes("workspace-\${agentId}\`")
437
+ && setup.includes("workspace: '.openclaw/' + meta.workspaceDir")
439
438
  && !setup.includes("const authProviderName = provider.isProxy ? '9router' : provider.id;")
440
439
  && !setup.includes("const authProviderName = botProvider.isProxy ? '9router' : botProvider.id;"),
441
440
  'Wizard native config generation must keep gateway loopback-local, preserve concrete auth provider ids, disable memory search by default, and sync single-bot provider/model selections into bot state'
@@ -479,7 +478,7 @@ checks.push(() => expect(
479
478
 
480
479
  checks.push(() => expectMatch(
481
480
  setup,
482
- /\.openclaw\/9router-smart-route-sync\.js[\s\S]*pm2 start --name openclaw-9router-sync/s,
481
+ /\.9router\/9router-smart-route-sync\.js[\s\S]*pm2 start --name openclaw-9router-sync/s,
483
482
  'VPS native script generation must write and run the 9Router smart-route sync loop'
484
483
  ));
485
484
 
@@ -537,8 +536,8 @@ checks.push(() => expectMatch(
537
536
 
538
537
  checks.push(() => expectMatch(
539
538
  setup,
540
- /Native setup now auto-runs the login flow and copies the QR into the project folder[\s\S]*docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose[\s\S]*docker compose cp ai-bot:\/tmp\/openclaw\/openclaw-zalouser-qr-default\.png \.\/zalo-login-qr\.png/s,
541
- 'Wizard copy must mention native auto-login and still show the dedicated Docker QR login command'
539
+ /docker compose exec -it ai-bot openclaw channels login --channel zalouser[\s\S]*docker compose cp ai-bot:\/tmp\/openclaw\/openclaw-zalouser-qr-default\.png \.\/zalo-login-qr\.png/s,
540
+ 'Wizard must show dedicated Docker Zalo login and QR copy commands'
542
541
  ));
543
542
 
544
543
  checks.push(() => expect(
@@ -567,6 +566,17 @@ checks.push(() => expectMatch(
567
566
  'Web wizard Docker patch command must add interface-based control UI allowed origins'
568
567
  ));
569
568
 
569
+ checks.push(() => expect(
570
+ setup.includes("echo [OK] OpenClaw da duoc cai dat thanh cong.")
571
+ && setup.includes("echo [OK] 9Router da duoc cai dat thanh cong."),
572
+ 'Windows BAT must print install success messages after openclaw and 9router are installed'
573
+ ));
574
+
575
+ checks.push(() => expect(
576
+ setup.includes("openclaw gateway stop 2>nul"),
577
+ 'Windows Zalo flow must clear stale gateway lock (from channels login mini-runtime) before starting the main gateway'
578
+ ));
579
+
570
580
  for (const check of checks) {
571
581
  check();
572
582
  }
@@ -1,54 +0,0 @@
1
- # 1.0.0 - 2016-01-07
2
-
3
- - Removed: unused speed test
4
- - Added: Automatic routing between previously unsupported conversions
5
- ([#27](https://github.com/Qix-/color-convert/pull/27))
6
- - Removed: `xxx2xxx()` and `xxx2xxxRaw()` functions
7
- ([#27](https://github.com/Qix-/color-convert/pull/27))
8
- - Removed: `convert()` class
9
- ([#27](https://github.com/Qix-/color-convert/pull/27))
10
- - Changed: all functions to lookup dictionary
11
- ([#27](https://github.com/Qix-/color-convert/pull/27))
12
- - Changed: `ansi` to `ansi256`
13
- ([#27](https://github.com/Qix-/color-convert/pull/27))
14
- - Fixed: argument grouping for functions requiring only one argument
15
- ([#27](https://github.com/Qix-/color-convert/pull/27))
16
-
17
- # 0.6.0 - 2015-07-23
18
-
19
- - Added: methods to handle
20
- [ANSI](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors) 16/256 colors:
21
- - rgb2ansi16
22
- - rgb2ansi
23
- - hsl2ansi16
24
- - hsl2ansi
25
- - hsv2ansi16
26
- - hsv2ansi
27
- - hwb2ansi16
28
- - hwb2ansi
29
- - cmyk2ansi16
30
- - cmyk2ansi
31
- - keyword2ansi16
32
- - keyword2ansi
33
- - ansi162rgb
34
- - ansi162hsl
35
- - ansi162hsv
36
- - ansi162hwb
37
- - ansi162cmyk
38
- - ansi162keyword
39
- - ansi2rgb
40
- - ansi2hsl
41
- - ansi2hsv
42
- - ansi2hwb
43
- - ansi2cmyk
44
- - ansi2keyword
45
- ([#18](https://github.com/harthur/color-convert/pull/18))
46
-
47
- # 0.5.3 - 2015-06-02
48
-
49
- - Fixed: hsl2hsv does not return `NaN` anymore when using `[0,0,0]`
50
- ([#15](https://github.com/harthur/color-convert/issues/15))
51
-
52
- ---
53
-
54
- Check out commit logs for older releases