create-openclaw-bot 5.7.10 → 5.8.0

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/dist/setup.js CHANGED
@@ -1,13 +1,13 @@
1
1
  /* ============================================
2
- OpenClaw Setup Wizard — Logic v2
2
+ OpenClaw Setup Wizard — Logic v2
3
3
  Multi-model, Multi-plugin, Multi-channel
4
4
  ============================================ */
5
- // AUTO-GENERATED by build.mjs — edit files in src/setup/ instead
5
+ // AUTO-GENERATED by build.mjs — edit files in src/setup/ instead
6
6
 
7
7
  (function () {
8
8
  'use strict';
9
9
 
10
- // ── Globals: CDN logos, state, shared utils (setup/data/header.js) ─
10
+ // ── Globals: CDN logos, state, shared utils (setup/data/header.js) ─
11
11
  // @ts-nocheck
12
12
  /* eslint-disable no-undef, no-unused-vars */
13
13
  /**
@@ -76,7 +76,7 @@
76
76
  || 'grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api';
77
77
 
78
78
  function getGatewayAllowedOrigins(port) {
79
- const normalizedPort = Number(port) || 18791;
79
+ const normalizedPort = Number(port) || 18789;
80
80
  const origins = new Set([
81
81
  `http://localhost:${normalizedPort}`,
82
82
  `http://127.0.0.1:${normalizedPort}`,
@@ -89,7 +89,7 @@
89
89
  return Array.from(origins);
90
90
  }
91
91
 
92
- // ── PROVIDERS object (setup/data/providers.js) ─────────────────────
92
+ // ── PROVIDERS object (setup/data/providers.js) ─────────────────────
93
93
  // @ts-nocheck
94
94
  /* eslint-disable no-undef, no-unused-vars */
95
95
  /**
@@ -212,7 +212,7 @@
212
212
  };
213
213
 
214
214
 
215
- // ── CHANNELS, system prompts, security rules (setup/data/channels.js)
215
+ // ── CHANNELS, system prompts, security rules (setup/data/channels.js)
216
216
  // @ts-nocheck
217
217
  /* eslint-disable no-undef, no-unused-vars */
218
218
  /**
@@ -383,7 +383,7 @@
383
383
  - ✅ Limit exposed ports (only 38789)`,
384
384
  };
385
385
 
386
- // ── PLUGINS list (setup/data/plugins.js) ───────────────────────────
386
+ // ── PLUGINS list (setup/data/plugins.js) ───────────────────────────
387
387
  // @ts-nocheck
388
388
  /* eslint-disable no-undef, no-unused-vars */
389
389
  /**
@@ -445,7 +445,7 @@
445
445
  ];
446
446
 
447
447
 
448
- // ── SKILLS list (setup/data/skills.js) ─────────────────────────────
448
+ // ── SKILLS list (setup/data/skills.js) ─────────────────────────────
449
449
  // @ts-nocheck
450
450
  /* eslint-disable no-undef, no-unused-vars */
451
451
  /**
@@ -616,10 +616,10 @@
616
616
  }
617
617
 
618
618
 
619
- // ── Shared runtime constants, relay helpers, auth profile builders (setup/shared/common-gen.js)
619
+ // ── Shared runtime constants, relay helpers, auth profile builders (setup/shared/common-gen.js)
620
620
  // @ts-nocheck
621
621
  (function (root) {
622
- const OPENCLAW_NPM_SPEC = 'openclaw@2026.5.4';
622
+ const OPENCLAW_NPM_SPEC = 'openclaw@latest';
623
623
  const OPENCLAW_RUNTIME_PACKAGES = 'grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api';
624
624
  const NINE_ROUTER_NPM_SPEC = '9router@latest';
625
625
  const NINE_ROUTER_PORT = 20128;
@@ -861,8 +861,9 @@ If setup reported a plugin install error, run this after the bot is running:
861
861
  return JSON.stringify(buildAuthProfilesJson(options), null, 2);
862
862
  }
863
863
 
864
- function get9RouterBaseUrl(deployMode = 'native') {
865
- return deployMode === 'docker' ? `${NINE_ROUTER_DOCKER_API_BASE_URL}/v1` : `${NINE_ROUTER_API_BASE_URL}/v1`;
864
+ function get9RouterBaseUrl(deployMode = 'native', routerPort) {
865
+ const port = routerPort || NINE_ROUTER_PORT;
866
+ return deployMode === 'docker' ? `http://9router:${port}/v1` : `http://localhost:${port}/v1`;
866
867
  }
867
868
 
868
869
  function build9RouterProviderConfig(baseUrl = `${NINE_ROUTER_API_BASE_URL}/v1`) {
@@ -870,6 +871,9 @@ If setup reported a plugin install error, run this after the bot is running:
870
871
  baseUrl,
871
872
  apiKey: NINE_ROUTER_PROXY_API_KEY,
872
873
  api: 'openai-completions',
874
+ request: {
875
+ allowPrivateNetwork: true,
876
+ },
873
877
  models: [
874
878
  {
875
879
  id: 'smart-route',
@@ -877,18 +881,12 @@ If setup reported a plugin install error, run this after the bot is running:
877
881
  contextWindow: 200000,
878
882
  maxTokens: 8192,
879
883
  },
880
- ...SUPPORTED_CODEX_MODELS.map((id) => ({
881
- id,
882
- name: `Codex ${id.slice(3).replace(/-/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase())}`,
883
- contextWindow: 200000,
884
- maxTokens: 8192,
885
- })),
886
884
  ],
887
885
  };
888
886
  }
889
887
 
890
- function buildGatewayConfig(port = 18791, deployMode = 'native', allowedOrigins = [], osChoice = '') {
891
- const normalizedPort = Number(port) || 18791;
888
+ function buildGatewayConfig(port = 18789, deployMode = 'native', allowedOrigins = [], osChoice = '') {
889
+ const normalizedPort = Number(port) || 18789;
892
890
  const cfg = {
893
891
  port: normalizedPort,
894
892
  mode: 'local',
@@ -933,7 +931,7 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
933
931
  Object.assign(exports, globalThis.__openclawCommon);
934
932
  }
935
933
 
936
- // ── Shared workspace file builders (IDENTITY, SOUL, AGENTS, TOOLS, TEAMS...) (setup/shared/workspace-gen.js)
934
+ // ── Shared workspace file builders (IDENTITY, SOUL, AGENTS, TOOLS, TEAMS...) (setup/shared/workspace-gen.js)
937
935
  /** @typedef {typeof globalThis & { __openclawWorkspace?: Record<string, Function> }} OpenClawWorkspaceRoot */
938
936
 
939
937
  const workspaceRoot = /** @type {OpenClawWorkspaceRoot} */ (
@@ -1187,8 +1185,9 @@ const CDP_URL = 'http://127.0.0.1:9222';
1187
1185
  const { isVi = true, variant = 'wizard', workspaceRoot = '' } = options;
1188
1186
  const wsRoot = workspaceRoot.replace(/\/+$/, '');
1189
1187
  const btPath = wsRoot ? `${wsRoot}/browser-tool.js` : 'browser-tool.js';
1188
+ const modeHeading = variant === 'cli-server' ? '# Headless Server Mode\n\n' : '';
1190
1189
 
1191
- return `# Navigation
1190
+ return `${modeHeading}# Navigation
1192
1191
  node ${btPath} status
1193
1192
  node ${btPath} open "https://google.com"
1194
1193
  node ${btPath} get_url
@@ -1460,7 +1459,7 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
1460
1459
  Object.assign(exports, workspaceRoot.__openclawWorkspace);
1461
1460
  }
1462
1461
 
1463
- // ── Centralized bot config builders (openclaw.json, exec-approvals, .env) (setup/shared/bot-config-gen.js)
1462
+ // ── Centralized bot config builders (openclaw.json, exec-approvals, .env) (setup/shared/bot-config-gen.js)
1464
1463
  // @ts-nocheck
1465
1464
  /**
1466
1465
  * @fileoverview Centralized bot configuration builders — single source of truth.
@@ -1514,7 +1513,7 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
1514
1513
  * @param {Array} opts.skills - Full SKILLS registry array
1515
1514
  * @param {boolean} opts.hasBrowserDesktop - Browser desktop mode
1516
1515
  * @param {boolean} opts.hasBrowserServer - Browser server mode
1517
- * @param {number} [opts.gatewayPort=18791]
1516
+ * @param {number} [opts.gatewayPort=18789]
1518
1517
  * @param {Array} [opts.gatewayAllowedOrigins]
1519
1518
  * @param {string} [opts.osChoice] - 'windows' | 'macos' | 'vps' | 'ubuntu'
1520
1519
  * @param {string} [opts.selectedModel] - For Ollama: specific model selected
@@ -1533,10 +1532,11 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
1533
1532
  skills = [],
1534
1533
  hasBrowserDesktop = false,
1535
1534
  hasBrowserServer = false,
1536
- gatewayPort = 18791,
1535
+ gatewayPort = 18789,
1537
1536
  gatewayAllowedOrigins = [],
1538
1537
  osChoice = '',
1539
1538
  selectedModel = '',
1539
+ routerPort,
1540
1540
  } = opts;
1541
1541
 
1542
1542
  const common = _common;
@@ -1547,7 +1547,7 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
1547
1547
  const agentsList = agentMetas.map((meta) => ({
1548
1548
  id: meta.agentId,
1549
1549
  ...(meta.name ? { name: meta.name } : {}),
1550
- workspace: `.openclaw/${meta.workspaceDir || 'workspace-' + meta.agentId}`,
1550
+ workspace: `/root/project/.openclaw/${meta.workspaceDir || 'workspace-' + meta.agentId}`,
1551
1551
  agentDir: `agents/${meta.agentId}/agent`,
1552
1552
  model: { primary: model, fallbacks: [] },
1553
1553
  }));
@@ -1571,7 +1571,7 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
1571
1571
  mode: 'merge',
1572
1572
  providers: {
1573
1573
  '9router': common.build9RouterProviderConfig(
1574
- common.get9RouterBaseUrl ? common.get9RouterBaseUrl(deployMode) : 'http://9router:20128/v1'
1574
+ common.get9RouterBaseUrl ? common.get9RouterBaseUrl(deployMode, routerPort) : `http://9router:${routerPort || 20128}/v1`
1575
1575
  ),
1576
1576
  },
1577
1577
  };
@@ -1597,6 +1597,9 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
1597
1597
 
1598
1598
  // ── commands ──────────────────────────────────────────────────────────────
1599
1599
  cfg.commands = { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' };
1600
+ if (selectedSkills.includes('scheduler')) {
1601
+ cfg.commands.ownerAllowFrom = ['*'];
1602
+ }
1600
1603
 
1601
1604
  // ── bindings (multi-bot or Zalo) ─────────────────────────────────────────
1602
1605
  if (isMultiBot && channelKey === 'telegram') {
@@ -1614,6 +1617,9 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
1614
1617
 
1615
1618
  // ── tools ────────────────────────────────────────────────────────────────
1616
1619
  cfg.tools = { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } };
1620
+ if (selectedSkills.includes('scheduler')) {
1621
+ cfg.tools.alsoAllow = ['group:automation'];
1622
+ }
1617
1623
  if (isMultiBot) {
1618
1624
  cfg.tools.agentToAgent = {
1619
1625
  enabled: true,
@@ -1924,7 +1930,7 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
1924
1930
  Object.assign(exports, globalThis.__openclawBotConfig);
1925
1931
  }
1926
1932
 
1927
- // ── Shared install artifacts: Chrome debug, uninstall, skill catalog (setup/shared/install-gen.js)
1933
+ // ── Shared install artifacts: Chrome debug, uninstall, skill catalog (setup/shared/install-gen.js)
1928
1934
  // @ts-nocheck
1929
1935
  // install-gen.js — Build install/runtime artifacts (Chrome debug, uninstall, skill catalog)
1930
1936
  // Workspace .md files are in workspace-gen.js (single source of truth).
@@ -2043,12 +2049,12 @@ fi
2043
2049
  }
2044
2050
 
2045
2051
  if (os === 'vps') {
2046
- return { name: 'uninstall-openclaw-vps.sh', content: `#!/usr/bin/env bash\nset -e\nPROJECT_DIR="${absUnix}"\nAPP_NAME="${appName}"\necho ""\necho "============================================================"\necho " OpenClaw Uninstaller - VPS / Ubuntu Server"\necho " Project: $PROJECT_DIR"\necho " PM2 app: $APP_NAME"\necho "============================================================"\necho ""\nread -rp "Type YES to confirm full removal: " CONFIRM\nif [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi\necho "[1/5] Stopping PM2 processes..."\nif command -v pm2 &>/dev/null; then\n pm2 delete "$APP_NAME" "$APP_NAME-9router" "$APP_NAME-9router-sync" openclaw openclaw-multibot 2>/dev/null || true\n pm2 save --force 2>/dev/null || true\nfi\necho "[2/5] Killing leftover processes on ports 18791 / 20128..."\nfor port in 18791 20128; do\n pid=$(lsof -ti tcp:$port 2>/dev/null || true)\n [ -n "$pid" ] && kill -9 $pid 2>/dev/null || true\ndone\necho "[3/5] Uninstalling npm packages..."\nnpm uninstall -g openclaw 9router pm2 grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>/dev/null || true\necho "[4/5] Removing project directory..."\n[ -d "$PROJECT_DIR" ] && rm -rf "$PROJECT_DIR" && echo " OK: Deleted $PROJECT_DIR" || echo " INFO: Not found."\necho "[5/5] Checking home-level .9router / .openclaw..."\nfor dir in "$HOME/.9router" "$HOME/.openclaw"; do\n if [ -d "$dir" ]; then\n read -rp "Delete $dir ? [YES/no]: " CLEAN\n [ "$CLEAN" = "YES" ] && rm -rf "$dir" && echo " OK: Deleted $dir" || echo " Kept: $dir"\n fi\ndone\n` };
2052
+ return { name: 'uninstall-openclaw-vps.sh', content: `#!/usr/bin/env bash\nset -e\nPROJECT_DIR="${absUnix}"\nAPP_NAME="${appName}"\necho ""\necho "============================================================"\necho " OpenClaw Uninstaller - VPS / Ubuntu Server"\necho " Project: $PROJECT_DIR"\necho " PM2 app: $APP_NAME"\necho "============================================================"\necho ""\nread -rp "Type YES to confirm full removal: " CONFIRM\nif [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi\necho "[1/5] Stopping PM2 processes..."\nif command -v pm2 &>/dev/null; then\n pm2 delete "$APP_NAME" "$APP_NAME-9router" "$APP_NAME-9router-sync" openclaw openclaw-multibot 2>/dev/null || true\n pm2 save --force 2>/dev/null || true\nfi\necho "[2/5] Killing leftover processes on ports 18789 / 20128..."\nfor port in 18789 20128; do\n pid=$(lsof -ti tcp:$port 2>/dev/null || true)\n [ -n "$pid" ] && kill -9 $pid 2>/dev/null || true\ndone\necho "[3/5] Uninstalling npm packages..."\nnpm uninstall -g openclaw 9router pm2 grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>/dev/null || true\necho "[4/5] Removing project directory..."\n[ -d "$PROJECT_DIR" ] && rm -rf "$PROJECT_DIR" && echo " OK: Deleted $PROJECT_DIR" || echo " INFO: Not found."\necho "[5/5] Checking home-level .9router / .openclaw..."\nfor dir in "$HOME/.9router" "$HOME/.openclaw"; do\n if [ -d "$dir" ]; then\n read -rp "Delete $dir ? [YES/no]: " CLEAN\n [ "$CLEAN" = "YES" ] && rm -rf "$dir" && echo " OK: Deleted $dir" || echo " Kept: $dir"\n fi\ndone\n` };
2047
2053
  }
2048
2054
 
2049
2055
  if (os === 'linux' || os === 'linux-desktop' || os === 'macos') {
2050
2056
  const label = os === 'macos' ? 'macOS' : 'Linux Desktop';
2051
- return { name: 'uninstall-openclaw.sh', content: `#!/usr/bin/env bash\nset -e\nPROJECT_DIR="${absUnix}"\necho ""\necho "============================================================"\necho " OpenClaw Uninstaller - ${label} Native"\necho " Project: $PROJECT_DIR"\necho "============================================================"\necho ""\nread -rp "Type YES to confirm full removal: " CONFIRM\nif [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi\necho "[1/4] Stopping openclaw and 9router processes..."\npkill -f "openclaw gateway run" 2>/dev/null || true\npkill -f "9router.*20128" 2>/dev/null || true\npkill -f "9router-smart-route" 2>/dev/null || true\npkill -f "$PROJECT_DIR" 2>/dev/null || true\nfor port in 18791 20128; do\n pid=$(lsof -ti tcp:$port 2>/dev/null || true)\n [ -n "$pid" ] && kill -9 $pid 2>/dev/null || true\ndone\necho "[2/4] Uninstalling npm packages..."\nnpm uninstall -g openclaw 9router grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>/dev/null || true\nsudo npm uninstall -g openclaw 9router 2>/dev/null || true\necho "[3/4] Removing project directory..."\n[ -d "$PROJECT_DIR" ] && rm -rf "$PROJECT_DIR" && echo " OK: Deleted $PROJECT_DIR" || echo " INFO: Not found."\necho "[4/4] Checking home-level .9router / .openclaw..."\nfor dir in "$HOME/.9router" "$HOME/.openclaw"; do\n if [ -d "$dir" ]; then\n read -rp "Delete $dir ? [YES/no]: " CLEAN\n [ "$CLEAN" = "YES" ] && rm -rf "$dir" && echo " OK: Deleted $dir" || echo " Kept: $dir"\n fi\ndone\n` };
2057
+ return { name: 'uninstall-openclaw.sh', content: `#!/usr/bin/env bash\nset -e\nPROJECT_DIR="${absUnix}"\necho ""\necho "============================================================"\necho " OpenClaw Uninstaller - ${label} Native"\necho " Project: $PROJECT_DIR"\necho "============================================================"\necho ""\nread -rp "Type YES to confirm full removal: " CONFIRM\nif [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi\necho "[1/4] Stopping openclaw and 9router processes..."\npkill -f "openclaw gateway run" 2>/dev/null || true\npkill -f "9router.*20128" 2>/dev/null || true\npkill -f "9router-smart-route" 2>/dev/null || true\npkill -f "$PROJECT_DIR" 2>/dev/null || true\nfor port in 18789 20128; do\n pid=$(lsof -ti tcp:$port 2>/dev/null || true)\n [ -n "$pid" ] && kill -9 $pid 2>/dev/null || true\ndone\necho "[2/4] Uninstalling npm packages..."\nnpm uninstall -g openclaw 9router grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>/dev/null || true\nsudo npm uninstall -g openclaw 9router 2>/dev/null || true\necho "[3/4] Removing project directory..."\n[ -d "$PROJECT_DIR" ] && rm -rf "$PROJECT_DIR" && echo " OK: Deleted $PROJECT_DIR" || echo " INFO: Not found."\necho "[4/4] Checking home-level .9router / .openclaw..."\nfor dir in "$HOME/.9router" "$HOME/.openclaw"; do\n if [ -d "$dir" ]; then\n read -rp "Delete $dir ? [YES/no]: " CLEAN\n [ "$CLEAN" = "YES" ] && rm -rf "$dir" && echo " OK: Deleted $dir" || echo " Kept: $dir"\n fi\ndone\n` };
2052
2058
  }
2053
2059
 
2054
2060
  return null;
@@ -2144,7 +2150,7 @@ fi
2144
2150
  L.push('echo.');
2145
2151
  L.push(isVi ? 'echo [OK] OpenClaw Gateway da khoi dong trong cua so moi!' : 'echo [OK] OpenClaw Gateway started in a new window!');
2146
2152
  L.push('echo.');
2147
- L.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
2153
+ L.push('echo OpenClaw Dashboard: http://127.0.0.1:18789');
2148
2154
  if (is9Router) L.push('echo 9Router Dashboard: http://127.0.0.1:20128/dashboard');
2149
2155
  L.push('echo.');
2150
2156
  L.push(isVi ? 'echo Ban co the dong cua so nay.' : 'echo You may close this window.');
@@ -2218,7 +2224,7 @@ fi
2218
2224
  }
2219
2225
  L.push('pm2 save >/dev/null 2>&1 || true');
2220
2226
  L.push('echo ""');
2221
- L.push('echo "OpenClaw Dashboard: http://127.0.0.1:18791"');
2227
+ L.push('echo "OpenClaw Dashboard: http://127.0.0.1:18789"');
2222
2228
  if (is9Router) L.push('echo "9Router Dashboard: http://127.0.0.1:20128/dashboard"');
2223
2229
  L.push('echo ""');
2224
2230
  L.push(isVi ? 'echo "Log gateway: pm2 logs $APP_NAME"' : 'echo "Gateway logs: pm2 logs $APP_NAME"');
@@ -2269,7 +2275,7 @@ fi
2269
2275
  L.push(isVi ? `echo "[OK] Gateway khoi dong (PID $GW_PID). Log: ${logFileGw}"` : `echo "[OK] Gateway started (PID $GW_PID). Log: ${logFileGw}"`);
2270
2276
  L.push('');
2271
2277
  L.push('echo ""');
2272
- L.push('echo "OpenClaw Dashboard: http://127.0.0.1:18791"');
2278
+ L.push('echo "OpenClaw Dashboard: http://127.0.0.1:18789"');
2273
2279
  if (is9Router) L.push('echo "9Router Dashboard: http://127.0.0.1:20128/dashboard"');
2274
2280
  L.push('echo ""');
2275
2281
  L.push(isVi ? 'echo "Bot dang chay background. Dung: openclaw gateway stop"' : 'echo "Bot running in background. Stop: openclaw gateway stop"');
@@ -2367,7 +2373,7 @@ fi
2367
2373
  "Write-Host \"\"",
2368
2374
  "if ($exitCode -eq 0) {",
2369
2375
  " Write-Host \" 🎉 Upgrade hoan tat!\" -ForegroundColor Green",
2370
- " Write-Host \" Dashboard: http://localhost:18791\" -ForegroundColor Cyan",
2376
+ " Write-Host \" Dashboard: http://localhost:18789\" -ForegroundColor Cyan",
2371
2377
  "} else {",
2372
2378
  " Write-Host \" ⚠️ Ma loi: $exitCode — xem log o tren.\" -ForegroundColor Yellow",
2373
2379
  "}",
@@ -2456,7 +2462,7 @@ fi
2456
2462
  "echo \"\"",
2457
2463
  "if [ $EXIT_CODE -eq 0 ]; then",
2458
2464
  " echo -e \"${GREEN} 🎉 Upgrade hoan tat!${NC}\"",
2459
- " echo -e \"${CYAN} Dashboard: http://localhost:18791${NC}\"",
2465
+ " echo -e \"${CYAN} Dashboard: http://localhost:18789${NC}\"",
2460
2466
  "else",
2461
2467
  " echo -e \"${YELLOW} ⚠️ Ma loi: $EXIT_CODE — xem log o tren.${NC}\"",
2462
2468
  "fi",
@@ -2492,7 +2498,7 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
2492
2498
  Object.assign(exports, globalThis.__openclawInstall);
2493
2499
  }
2494
2500
 
2495
- // ── Shared Docker artifact helpers for wizard + CLI (setup/shared/docker-gen.js)
2501
+ // ── Shared Docker artifact helpers for wizard + CLI (setup/shared/docker-gen.js)
2496
2502
  // @ts-nocheck
2497
2503
  (function (root) {
2498
2504
  const common = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon) || {};
@@ -2512,65 +2518,106 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
2512
2518
  return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
2513
2519
  }
2514
2520
 
2515
- function build9RouterSmartRouteSyncScript(dbPath) {
2516
- return `const fs=require('fs');const INTERVAL=30000;const p='${dbPath}';
2517
- const PM=${JSON.stringify(SMART_ROUTE_PROVIDER_MODELS)};
2518
- const PREF=${JSON.stringify(SMART_ROUTE_PROVIDER_ORDER)};
2519
- console.log('[sync-combo] 9Router sync loop started...');
2520
- const sync = async () => {
2521
- try {
2522
- let db = {};
2523
- try { db = JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e){}
2524
- if (!db.combos) db.combos = [];
2525
- const removeSmartRoute = () => {
2526
- const next = db.combos.filter(x => x.id !== 'smart-route');
2527
- if (next.length !== db.combos.length) {
2528
- db.combos = next;
2529
- fs.writeFileSync(p, JSON.stringify(db, null, 2));
2530
- console.log('[sync-combo] Removed smart-route (no active providers)');
2531
- }
2532
- };
2533
- const res = await fetch('http://localhost:20128/api/providers');
2534
- if (!res.ok) { console.log('[sync-combo] API not ready, retrying...'); return; }
2535
- const d = await res.json();
2536
- const rawConnections = Array.isArray(d.connections) ? d.connections : Array.isArray(d.providerConnections) ? d.providerConnections : [];
2537
- const activeConns = rawConnections.filter(c => c && c.provider && c.isActive !== false && !c.disabled);
2538
- const a = [...new Set(activeConns.map(c => c.provider))];
2539
- if (!a.length) { console.log('[sync-combo] No active providers reported; keeping existing smart-route'); return; }
2540
- a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
2541
- const m = [];
2542
- for (const pv of a) {
2543
- if (PM[pv]) m.push(...PM[pv]);
2544
- const conns = activeConns.filter(c => c.provider === pv);
2545
- for (const c of conns) {
2546
- if (Array.isArray(c.models)) {
2547
- for (const mdl of c.models) {
2548
- const mdlId = typeof mdl === 'string' ? mdl : mdl.id;
2549
- if (mdlId && !m.includes(mdlId) && !m.includes(pv + '/' + mdlId)) {
2550
- m.push(pv + '/' + mdlId);
2551
- }
2552
- }
2553
- }
2554
- }
2555
- }
2556
- if (!m.length) { console.log('[sync-combo] No mapped models for active providers; keeping existing smart-route'); return; }
2557
- const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
2558
- const i = db.combos.findIndex(x => x.id === 'smart-route');
2559
- if (i >= 0) {
2560
- if (JSON.stringify(db.combos[i].models) !== JSON.stringify(c.models)) {
2561
- db.combos[i] = c;
2562
- fs.writeFileSync(p, JSON.stringify(db, null, 2));
2563
- console.log('[sync-combo] Updated smart-route: ' + c.models.length + ' models');
2564
- }
2565
- } else {
2566
- db.combos.push(c);
2567
- fs.writeFileSync(p, JSON.stringify(db, null, 2));
2568
- console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models');
2569
- }
2570
- } catch (e) {}
2571
- };
2572
- setTimeout(sync, 5000);
2573
- setInterval(sync, INTERVAL);`;
2521
+ function build9RouterSmartRouteSyncScript() {
2522
+ const lines = [
2523
+ "const fs = require('fs');",
2524
+ "const INTERVAL = 30000;",
2525
+ "const DB_PATH = '/root/.9router/db/data.sqlite';",
2526
+ "const PORT = process.env.PORT || 20128;",
2527
+ "const COMBO_NAME = 'smart-route';",
2528
+ "const API_BASE = `http://localhost:${PORT}`;",
2529
+ "",
2530
+ "function ensureSettings() {",
2531
+ " try {",
2532
+ " let db = null;",
2533
+ " try {",
2534
+ " const { DatabaseSync } = require('node:sqlite');",
2535
+ " db = new DatabaseSync(DB_PATH);",
2536
+ " } catch {",
2537
+ " let Database;",
2538
+ " try { Database = require('/usr/local/lib/node_modules/better-sqlite3'); } catch {",
2539
+ " try { Database = require('better-sqlite3'); } catch { return; }",
2540
+ " }",
2541
+ " db = Database(DB_PATH);",
2542
+ " }",
2543
+ ' const existing = db.prepare("SELECT * FROM settings WHERE id = 1").get();',
2544
+ " if (!existing) {",
2545
+ ' db.prepare("INSERT INTO settings (id, data) VALUES (1, ?)").run(JSON.stringify({ requireLogin: false }));',
2546
+ " } else {",
2547
+ " try {",
2548
+ " const data = JSON.parse(existing.data || '{}');",
2549
+ " if (data.requireLogin !== false) {",
2550
+ " data.requireLogin = false;",
2551
+ ' db.prepare("UPDATE settings SET data = ? WHERE id = 1").run(JSON.stringify(data));',
2552
+ " }",
2553
+ " } catch {}",
2554
+ " }",
2555
+ " db.close();",
2556
+ " } catch (e) {}",
2557
+ "}",
2558
+ "",
2559
+ "const sync = async () => {",
2560
+ " try {",
2561
+ " if (!fs.existsSync(DB_PATH)) return;",
2562
+ "",
2563
+ " let existingCombo = null;",
2564
+ " try {",
2565
+ " const resp = await fetch(`${API_BASE}/api/combos`);",
2566
+ " if (resp.status === 401) {",
2567
+ " ensureSettings();",
2568
+ " return;",
2569
+ " }",
2570
+ " const data = await resp.json();",
2571
+ " if (data.combos) {",
2572
+ " existingCombo = data.combos.find(c => c.name === COMBO_NAME);",
2573
+ " }",
2574
+ " } catch (e) { return; }",
2575
+ "",
2576
+ " if (existingCombo) return;",
2577
+ "",
2578
+ " let activeProviders = [];",
2579
+ " try {",
2580
+ " const resp = await fetch(`${API_BASE}/api/providers`);",
2581
+ " const data = await resp.json();",
2582
+ " const conns = data.connections || data.providerConnections || [];",
2583
+ " activeProviders = [...new Set(",
2584
+ " conns.filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider)",
2585
+ " )];",
2586
+ " } catch (e) { return; }",
2587
+ "",
2588
+ " if (!activeProviders.length) return;",
2589
+ "",
2590
+ " let models = [];",
2591
+ " try {",
2592
+ " const resp = await fetch(`${API_BASE}/api/models`);",
2593
+ " const data = await resp.json();",
2594
+ " if (data.models && Array.isArray(data.models)) {",
2595
+ " models = data.models",
2596
+ " .filter(m => activeProviders.includes(m.provider))",
2597
+ " .filter(m => !/(embedding|image|tts|stt|audio|vision)/i.test(m.model))",
2598
+ " .map(m => m.fullModel);",
2599
+ " }",
2600
+ " models = [...new Set(models)];",
2601
+ " } catch (e) { return; }",
2602
+ "",
2603
+ " if (!models.length) return;",
2604
+ "",
2605
+ " try {",
2606
+ " await fetch(`${API_BASE}/api/combos`, {",
2607
+ " method: 'POST',",
2608
+ " headers: { 'Content-Type': 'application/json' },",
2609
+ " body: JSON.stringify({ name: COMBO_NAME, models })",
2610
+ " });",
2611
+ " console.log('[sync-combo] Created smart-route with ' + models.length + ' models');",
2612
+ " } catch (e) {}",
2613
+ " } catch (e) {}",
2614
+ "};",
2615
+ "",
2616
+ "if (fs.existsSync(DB_PATH)) ensureSettings();",
2617
+ "setTimeout(sync, 10000);",
2618
+ "setInterval(sync, INTERVAL);",
2619
+ ];
2620
+ return lines.join('\n');
2574
2621
  }
2575
2622
 
2576
2623
  function build9RouterPatchScript() {
@@ -2635,20 +2682,20 @@ for(const root of roots){if(!root||!fs.existsSync(root))continue;touched+=patchP
2635
2682
  if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}else{console.log('[patch-9router] No compatible 9router source files found to patch.');}`;
2636
2683
  }
2637
2684
 
2638
- function build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64) {
2685
+ function build9RouterComposeEntrypointScript(routerPort) {
2686
+ const port = routerPort || 20128;
2639
2687
  const nineRouterSpec = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon && globalThis.__openclawCommon.NINE_ROUTER_NPM_SPEC) || '9router@latest';
2640
2688
  return [
2641
- `npm install -g ${nineRouterSpec}`,
2642
- `node -e "require('fs').writeFileSync('/tmp/patch-9router.js',Buffer.from('${patchScriptBase64}','base64').toString())"`,
2643
- `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
2689
+ `npm install -g ` + nineRouterSpec + ` better-sqlite3`,
2644
2690
  'node /tmp/patch-9router.js || true',
2691
+ 'node -e "const fs=require(\'fs\'),path=require(\'path\'); const DB_PATH=\'/root/.9router/db/data.sqlite\'; const dir=path.dirname(DB_PATH); if(!fs.existsSync(dir))fs.mkdirSync(dir,{recursive:true}); try{ const {DatabaseSync}=require(\'node:sqlite\'); const db=new DatabaseSync(DB_PATH); db.prepare(\'CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)\').run(); const existing=db.prepare(\'SELECT * FROM settings WHERE id = 1\').get(); if(!existing){ db.prepare(\'INSERT INTO settings (id, data) VALUES (1, ?)\').run(JSON.stringify({requireLogin:false})); } db.close(); }catch(e){}" || true',
2645
2692
  'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
2646
- 'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
2693
+ `exec 9router -n -l -H 0.0.0.0 -p ${port} --skip-update`
2647
2694
  ].join('\n');
2648
2695
  }
2649
2696
 
2650
2697
  function buildGatewayPatchCmd() {
2651
- return `node -e \\"const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://' + entry.address + ':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\"`;
2698
+ return `node -e \\"const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const gp=Number(process.env.OPENCLAW_GATEWAY_PORT||process.env.OPENCLAW_PORT)||c.gateway?.port||18789;const a=new Set(['http://localhost:'+gp,'http://127.0.0.1:'+gp,'http://0.0.0.0:'+gp]);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://' + entry.address + ':'+gp);}}const p9=c.models&&c.models.providers&&c.models.providers['9router'];if(p9){p9.request=Object.assign({},p9.request,{allowPrivateNetwork:true});}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:gp,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\"`;
2652
2699
  }
2653
2700
 
2654
2701
  function buildDockerArtifacts(options) {
@@ -2665,7 +2712,7 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
2665
2712
  dockerfilePlugins = [],
2666
2713
  dockerfileSkillInstallMode = 'none',
2667
2714
  runtimeCommandParts = [],
2668
- volumeMount = '../../.openclaw:/root/project/.openclaw\\n - ../../:/mnt/project',
2715
+ volumeMount = '../../.openclaw:/root/project/.openclaw\n - ../../:/mnt/project',
2669
2716
  singleComposeName = 'oc-bot',
2670
2717
  multiComposeName = 'oc-multibot',
2671
2718
  singleAppContainerName = 'openclaw-bot',
@@ -2678,7 +2725,8 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
2678
2725
  multiOllamaNumParallel = 1,
2679
2726
  singleOllamaNumParallel = 1,
2680
2727
  emitBrowserInstall = true,
2681
-
2728
+ gatewayPort = 18789,
2729
+ routerPort = 20128,
2682
2730
  } = options;
2683
2731
 
2684
2732
  const browserAptExtra = hasBrowser ? ' xvfb socat' : '';
@@ -2704,10 +2752,9 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
2704
2752
  // Dynamic runtime configuration: backup config before any first-run install, restore after.
2705
2753
  // Missing plugin install may touch openclaw.json, so preserve critical fields.
2706
2754
  const backupConfigScript = `const fs=require('fs'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)){fs.copyFileSync(p,b);}`;
2707
- const backupConfigB64 = encodeBase64Utf8(backupConfigScript);
2708
2755
 
2709
- const restoreConfigScript = `const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)&&fs.existsSync(b)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const bk=JSON.parse(fs.readFileSync(b,'utf8'));const keep=['agents','channels','bindings','commands','models','browser','skills'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://'+entry.address+':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',mode:c.gateway?.mode||bk.gateway?.mode||'local',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));fs.unlinkSync(b);}`;
2710
- const restoreConfigB64 = encodeBase64Utf8(restoreConfigScript);
2756
+ const restoreConfigScript = `const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)&&fs.existsSync(b)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const bk=JSON.parse(fs.readFileSync(b,'utf8'));const keep=['agents','channels','bindings','commands','models','browser','skills','plugins','tools'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const gp=Number(process.env.OPENCLAW_GATEWAY_PORT||process.env.OPENCLAW_PORT)||c.gateway?.port||bk.gateway?.port||18789;const a=new Set(['http://localhost:'+gp,'http://127.0.0.1:'+gp,'http://0.0.0.0:'+gp]);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://'+entry.address+':'+gp);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:gp,bind:'custom',customBindHost:'0.0.0.0',mode:c.gateway?.mode||bk.gateway?.mode||'local',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));fs.unlinkSync(b);}`;
2757
+ const securityCompatScript = `const fs=require('fs'),path=require('path');const scopes=['operator.admin','operator.pairing','operator.approvals'];function uniq(a){return Array.from(new Set([...(Array.isArray(a)?a:[]),...scopes]));}function walk(v){if(!v||typeof v!=='object')return;if(Array.isArray(v)){v.forEach(walk);return;}if(Array.isArray(v.scopes)||Array.isArray(v.approvedScopes)){v.scopes=uniq(v.scopes);v.approvedScopes=uniq(v.approvedScopes);}Object.values(v).forEach(walk);}const home=process.env.OPENCLAW_HOME||path.join(process.cwd(),'.openclaw');const state=process.env.OPENCLAW_STATE_DIR||home;const cfgPath=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(cfgPath)){const c=JSON.parse(fs.readFileSync(cfgPath,'utf8'));const p=c.models&&c.models.providers&&c.models.providers['9router'];if(p){p.request=Object.assign({},p.request,{allowPrivateNetwork:true});}fs.writeFileSync(cfgPath,JSON.stringify(c,null,2));}for(const root of Array.from(new Set([home,state]))){const f=path.join(root,'devices','paired.json');if(fs.existsSync(f)){const d=JSON.parse(fs.readFileSync(f,'utf8'));walk(d);fs.writeFileSync(f,JSON.stringify(d,null,2));}}`;
2711
2758
 
2712
2759
  const runtimeParts = runtimeCommandParts.filter(Boolean);
2713
2760
  const runtimePrelude = [
@@ -2746,9 +2793,34 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
2746
2793
  ];
2747
2794
  runtimeParts.unshift(...runtimePrelude);
2748
2795
  // Backup config BEFORE plugin installs (runtimeCommandParts may contain plugin install commands)
2749
- runtimeParts.unshift(`node -e 'eval(Buffer.from("${backupConfigB64}","base64").toString())'`);
2796
+ runtimeParts.unshift(`node - <<'NODE'\n${backupConfigScript}\nNODE`);
2750
2797
  // Restore config AFTER plugin installs (which may clobber openclaw.json)
2751
- runtimeParts.push(`node -e 'eval(Buffer.from("${restoreConfigB64}","base64").toString())'`);
2798
+ runtimeParts.push(`node - <<'NODE'\n${restoreConfigScript}\nNODE`);
2799
+ runtimeParts.push(`node - <<'NODE'\n${securityCompatScript}\nNODE`);
2800
+ // Zalouser stability: patch watchdog tolerance and add auto-restart monitor
2801
+ runtimeParts.push([
2802
+ '# Patch zalouser watchdog tolerance (35s -> 90s) to survive provider auth pre-warming',
2803
+ 'ZALO_JS=$(find "$OPENCLAW_HOME" -path "*/zalouser/dist/zalo-js-*.js" -type f 2>/dev/null | head -1)',
2804
+ 'if [ -n "$ZALO_JS" ] && grep -q "35e3" "$ZALO_JS" 2>/dev/null; then',
2805
+ ' sed -i "s/LISTENER_WATCHDOG_MAX_GAP_MS\\\\s*=\\\\s*35e3/LISTENER_WATCHDOG_MAX_GAP_MS = 90e3/" "$ZALO_JS"',
2806
+ ' echo "[entrypoint] patched zalouser watchdog gap: 35s -> 90s"',
2807
+ 'fi',
2808
+ ].join('\\n'));
2809
+ runtimeParts.push([
2810
+ '# Zalo channel auto-restart monitor (background)',
2811
+ '(',
2812
+ ' sleep 180',
2813
+ ' while true; do',
2814
+ ' sleep 60',
2815
+ ' STATUS=$(openclaw channels status 2>/dev/null | grep -i "Zalo Personal" || true)',
2816
+ ' if echo "$STATUS" | grep -qi "stopped"; then',
2817
+ ' echo "[zalo-monitor] Zalo channel stopped - restarting container in 5s"',
2818
+ ' sleep 5',
2819
+ ' kill 1 2>/dev/null || true',
2820
+ ' fi',
2821
+ ' done',
2822
+ ') &',
2823
+ ].join('\\n'));
2752
2824
  if (hasBrowser) {
2753
2825
  runtimeParts.push('socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &');
2754
2826
  runtimeParts.push('Xvfb :99 -screen 0 1280x720x24 > /dev/null 2>&1 & DISPLAY=:99 openclaw gateway run');
@@ -2756,7 +2828,6 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
2756
2828
  runtimeParts.push('openclaw gateway run');
2757
2829
  }
2758
2830
  const runtimeScript = ['#!/bin/sh', 'set -e', ...runtimeParts].join('\n');
2759
- const runtimeScriptB64 = encodeBase64Utf8(runtimeScript);
2760
2831
  const dockerfile = `FROM node:22-slim
2761
2832
 
2762
2833
  RUN apt-get update && apt-get install -y git curl python3${browserAptExtra} && rm -rf /var/lib/apt/lists/*
@@ -2765,21 +2836,20 @@ ARG OPENCLAW_VER="${openClawNpmSpec}"
2765
2836
  ARG CACHE_BUST=""
2766
2837
  RUN echo "CACHE_BUST=$CACHE_BUST" && npm install -g $OPENCLAW_VER ${openClawRuntimePackages}${skillLines}${pluginLines}
2767
2838
  ${patchLine}
2768
- RUN node -e "require('fs').writeFileSync('/usr/local/bin/openclaw-entrypoint.sh', Buffer.from('${runtimeScriptB64}','base64').toString())" && chmod +x /usr/local/bin/openclaw-entrypoint.sh
2839
+ COPY entrypoint.sh /usr/local/bin/openclaw-entrypoint.sh
2840
+ RUN chmod +x /usr/local/bin/openclaw-entrypoint.sh
2769
2841
  WORKDIR /root/project
2770
2842
 
2771
- EXPOSE 18791
2843
+ EXPOSE ${gatewayPort}
2772
2844
 
2773
2845
  CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
2774
2846
 
2775
- const syncScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
2776
- const syncScriptBase64 = encodeBase64Utf8(syncScript);
2777
- const patchScript = build9RouterPatchScript();
2778
- const patchScriptBase64 = encodeBase64Utf8(patchScript);
2779
- const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64);
2847
+ const syncScript = build9RouterSmartRouteSyncScript();
2848
+ const patchScript = build9RouterPatchScript();
2849
+ const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(routerPort);
2780
2850
  const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
2781
2851
 
2782
- const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/root/project/.openclaw\n - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1\n tmpfs:\n - /root/project/.openclaw/plugin-runtime-deps\n';
2852
+ const appEnvironmentBlock = ` environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/root/project/.openclaw\n - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1\n - OPENCLAW_GATEWAY_PORT=${gatewayPort}\n - OPENCLAW_PORT=${gatewayPort}\n tmpfs:\n - /root/project/.openclaw/plugin-runtime-deps\n`;
2783
2853
 
2784
2854
  let compose;
2785
2855
  if (isMultiBot) {
@@ -2797,11 +2867,11 @@ services:
2797
2867
  container_name: ${multiAppContainerName}
2798
2868
  restart: always
2799
2869
  env_file:
2800
- - .env
2870
+ - ../../.env
2801
2871
  ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
2802
2872
  - ${volumeMount}
2803
2873
  ports:
2804
- - "18791:18791"
2874
+ - "${gatewayPort}:${gatewayPort}"
2805
2875
 
2806
2876
  9router:
2807
2877
  image: node:22-slim
@@ -2813,13 +2883,15 @@ ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
2813
2883
  - |
2814
2884
  ${indentBlock(docker9RouterEntrypointScript, 8)}
2815
2885
  environment:
2816
- - PORT=20128
2886
+ - PORT=${routerPort}
2817
2887
  - HOSTNAME=0.0.0.0
2818
2888
  - CI=true
2819
2889
  volumes:
2820
2890
  - 9router-data:/root/.9router
2891
+ - ./sync.js:/tmp/sync.js:ro
2892
+ - ./patch-9router.js:/tmp/patch-9router.js:ro
2821
2893
  ports:
2822
- - "20128:20128"
2894
+ - "${routerPort}:${routerPort}"
2823
2895
 
2824
2896
  volumes:
2825
2897
  9router-data:`;
@@ -2832,11 +2904,11 @@ services:
2832
2904
  container_name: ${multiAppContainerName}
2833
2905
  restart: always
2834
2906
  env_file:
2835
- - .env
2907
+ - ../../.env
2836
2908
  ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
2837
2909
  - ${volumeMount}
2838
2910
  ports:
2839
- - "18791:18791"
2911
+ - "${gatewayPort}:${gatewayPort}"
2840
2912
 
2841
2913
  ollama:
2842
2914
  image: ollama/ollama:latest
@@ -2872,11 +2944,11 @@ services:
2872
2944
  container_name: ${multiAppContainerName}
2873
2945
  restart: always
2874
2946
  env_file:
2875
- - .env
2947
+ - ../../.env
2876
2948
  ${appEnvironmentBlock}${extraHosts} volumes:
2877
2949
  - ${volumeMount}
2878
2950
  ports:
2879
- - "18791:18791"`;
2951
+ - "${gatewayPort}:${gatewayPort}"`;
2880
2952
  }
2881
2953
  } else if (is9Router) {
2882
2954
  compose = `name: ${singleComposeName}
@@ -2886,13 +2958,14 @@ services:
2886
2958
  container_name: ${singleAppContainerName}
2887
2959
  restart: always
2888
2960
  env_file:
2889
- - .env
2961
+ - ../../.env
2890
2962
  depends_on:
2891
2963
  - 9router
2892
2964
  ${appEnvironmentBlock}${hasBrowser ? `${extraHostsBlock}\n` : ''} volumes:
2893
2965
  - ${volumeMount}
2966
+ - openclaw-plugins:/root/project/.openclaw/npm
2894
2967
  ports:
2895
- - "18791:18791"
2968
+ - "${gatewayPort}:${gatewayPort}"
2896
2969
 
2897
2970
  9router:
2898
2971
  image: node:22-slim
@@ -2904,16 +2977,19 @@ ${appEnvironmentBlock}${hasBrowser ? `${extraHostsBlock}\n` : ''} volumes:
2904
2977
  - |
2905
2978
  ${indentBlock(docker9RouterEntrypointScript, 8)}
2906
2979
  environment:
2907
- - PORT=20128
2980
+ - PORT=${routerPort}
2908
2981
  - HOSTNAME=0.0.0.0
2909
2982
  - CI=true
2910
2983
  volumes:
2911
2984
  - 9router-data:/root/.9router
2985
+ - ./sync.js:/tmp/sync.js:ro
2986
+ - ./patch-9router.js:/tmp/patch-9router.js:ro
2912
2987
  ports:
2913
- - "20128:20128"
2988
+ - "${routerPort}:${routerPort}"
2914
2989
 
2915
2990
  volumes:
2916
- 9router-data:`;
2991
+ 9router-data:
2992
+ openclaw-plugins:`;
2917
2993
  } else if (isLocal) {
2918
2994
  const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
2919
2995
  compose = `name: ${singleComposeName}
@@ -2922,12 +2998,12 @@ services:
2922
2998
  build: .
2923
2999
  container_name: ${singleAppContainerName}
2924
3000
  restart: always
2925
- env_file: .env
3001
+ env_file: ../../.env
2926
3002
  ${appEnvironmentBlock} depends_on:
2927
3003
  ollama:
2928
3004
  condition: service_healthy
2929
3005
  ${hasBrowser ? `${extraHostsBlock}\n` : ''} ports:
2930
- - "18791:18791"
3006
+ - "${gatewayPort}:${gatewayPort}"
2931
3007
  volumes:
2932
3008
  - ${volumeMount}
2933
3009
 
@@ -2965,17 +3041,19 @@ services:
2965
3041
  container_name: ${singleAppContainerName}
2966
3042
  restart: always
2967
3043
  env_file:
2968
- - .env
3044
+ - ../../.env
2969
3045
  ${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''} volumes:
2970
3046
  - ${volumeMount}
2971
3047
  ports:
2972
- - "18791:18791"`;
3048
+ - "${gatewayPort}:${gatewayPort}"`;
2973
3049
  }
2974
3050
 
2975
3051
  return {
2976
3052
  dockerfile,
2977
3053
  compose,
3054
+ entrypointScript: runtimeScript,
2978
3055
  syncScript,
3056
+ patchScript,
2979
3057
  docker9RouterEntrypointScript,
2980
3058
  gatewayPatchCmd: buildGatewayPatchCmd(),
2981
3059
  };
@@ -2996,7 +3074,9 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
2996
3074
  Object.assign(exports, globalThis.__openclawDockerGen);
2997
3075
  }
2998
3076
 
2999
- // ── buildNativeScriptCtx and shared native runtime helpers (setup/generators/native-helpers-gen.js)
3077
+
3078
+
3079
+ // ── buildNativeScriptCtx and shared native runtime helpers (setup/generators/native-helpers-gen.js)
3000
3080
  // @ts-nocheck
3001
3081
  /* eslint-disable no-undef, no-unused-vars */
3002
3082
  /**
@@ -3048,15 +3128,101 @@ function buildNativeScriptCtx(options) {
3048
3128
  });
3049
3129
 
3050
3130
  function native9RouterSyncScriptContent() {
3051
- return `const fs=require('fs');
3052
- const path=require('path');
3053
- const INTERVAL=30000;
3054
- const p=path.join(process.env.DATA_DIR||'.9router','db.json');
3055
- const ROUTER='${globalThis.__openclawCommon.NINE_ROUTER_API_BASE_URL}';
3056
- const PM=${JSON.stringify(SMART_ROUTE_PROVIDER_MODELS)};
3057
- const PREF=${JSON.stringify(SMART_ROUTE_PROVIDER_ORDER)};
3058
- console.log('[sync-combo] 9Router sync loop started...');
3059
- const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.ok){console.log('[sync-combo] API not ready, retrying...');return;}const d=await res.json();const rawConnections=Array.isArray(d.connections)?d.connections:Array.isArray(d.providerConnections)?d.providerConnections:[];const a=[...new Set(rawConnections.filter(c=>c&&c.provider&&c.isActive!==false&&!c.disabled).map(c=>c.provider))];let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catch{}if(!db.combos)db.combos=[];const removeSmartRoute=()=>{const next=db.combos.filter(x=>x.id!=='smart-route');if(next.length!==db.combos.length){db.combos=next;fs.writeFileSync(p,JSON.stringify(db,null,2));console.log('[sync-combo] Removed smart-route (no active providers)');}};if(!a.length){removeSmartRoute();return;}a.sort((x,y)=>(PREF.indexOf(x)===-1?99:PREF.indexOf(x))-(PREF.indexOf(y)===-1?99:PREF.indexOf(y)));const m=a.flatMap(provider=>PM[provider]||[]);if(!m.length){removeSmartRoute();return;}const c={id:'smart-route',name:'smart-route',alias:'smart-route',models:m};const i=db.combos.findIndex(x=>x.id==='smart-route');if(i>=0){if(JSON.stringify(db.combos[i].models)!==JSON.stringify(c.models)){db.combos[i]=c;fs.writeFileSync(p,JSON.stringify(db,null,2));console.log('[sync-combo] Updated smart-route: '+c.models.length+' models from: '+a.join(','));}}else{db.combos.push(c);fs.writeFileSync(p,JSON.stringify(db,null,2));console.log('[sync-combo] Created smart-route: '+c.models.length+' models from: '+a.join(','));}}catch(e){console.log('[sync-combo] Error:',e.message);}};setTimeout(sync,5000);setInterval(sync,INTERVAL);`;
3131
+ return `const fs = require('fs');
3132
+ const path = require('path');
3133
+ const INTERVAL = 30000;
3134
+ const DB_PATH = path.join(process.env.DATA_DIR || '.9router', 'db', 'data.sqlite');
3135
+ const PORT = process.env.PORT || 20128;
3136
+ const COMBO_NAME = 'smart-route';
3137
+ const API_BASE = \\\`http://localhost:\\\${PORT}\\\`;
3138
+
3139
+ function ensureSettings() {
3140
+ try {
3141
+ let Database;
3142
+ try {
3143
+ const cp = require('child_process');
3144
+ const npmRoot = cp.execSync('npm root -g').toString().trim();
3145
+ Database = require(path.join(npmRoot, '9router', 'node_modules', 'better-sqlite3'));
3146
+ } catch {
3147
+ try { Database = require('better-sqlite3'); } catch { return; }
3148
+ }
3149
+ const db = Database(DB_PATH);
3150
+ const existing = db.prepare("SELECT * FROM settings WHERE id = 1").get();
3151
+ if (!existing) {
3152
+ db.prepare("INSERT INTO settings (id, data) VALUES (1, ?)").run(JSON.stringify({ requireLogin: false }));
3153
+ } else {
3154
+ try {
3155
+ const data = JSON.parse(existing.data || '{}');
3156
+ if (data.requireLogin !== false) {
3157
+ data.requireLogin = false;
3158
+ db.prepare("UPDATE settings SET data = ? WHERE id = 1").run(JSON.stringify(data));
3159
+ }
3160
+ } catch {}
3161
+ }
3162
+ db.close();
3163
+ } catch (e) {}
3164
+ }
3165
+
3166
+ const sync = async () => {
3167
+ try {
3168
+ if (!fs.existsSync(DB_PATH)) return;
3169
+
3170
+ let existingCombo = null;
3171
+ try {
3172
+ const resp = await fetch(\\\`\\\${API_BASE}/api/combos\\\`);
3173
+ if (resp.status === 401) {
3174
+ ensureSettings();
3175
+ return;
3176
+ }
3177
+ const data = await resp.json();
3178
+ if (data.combos) {
3179
+ existingCombo = data.combos.find(c => c.name === COMBO_NAME);
3180
+ }
3181
+ } catch (e) { return; }
3182
+
3183
+ if (existingCombo) return;
3184
+
3185
+ let activeProviders = [];
3186
+ try {
3187
+ const resp = await fetch(\\\`\\\${API_BASE}/api/providers\\\`);
3188
+ const data = await resp.json();
3189
+ const conns = data.connections || data.providerConnections || [];
3190
+ activeProviders = [...new Set(
3191
+ conns.filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider)
3192
+ )];
3193
+ } catch (e) { return; }
3194
+
3195
+ if (!activeProviders.length) return;
3196
+
3197
+ let models = [];
3198
+ try {
3199
+ const resp = await fetch(\\\`\\\${API_BASE}/api/models\\\`);
3200
+ const data = await resp.json();
3201
+ if (data.models && Array.isArray(data.models)) {
3202
+ models = data.models
3203
+ .filter(m => activeProviders.includes(m.provider))
3204
+ .filter(m => !/(embedding|image|tts|stt|audio|vision)/i.test(m.model))
3205
+ .map(m => m.fullModel);
3206
+ }
3207
+ models = [...new Set(models)];
3208
+ } catch (e) { return; }
3209
+
3210
+ if (!models.length) return;
3211
+
3212
+ try {
3213
+ await fetch(\\\`\\\${API_BASE}/api/combos\\\`, {
3214
+ method: 'POST',
3215
+ headers: { 'Content-Type': 'application/json' },
3216
+ body: JSON.stringify({ name: COMBO_NAME, models })
3217
+ });
3218
+ console.log('[sync-combo] Created smart-route with ' + models.length + ' models');
3219
+ } catch (e) {}
3220
+ } catch (e) {}
3221
+ };
3222
+
3223
+ if (fs.existsSync(DB_PATH)) ensureSettings();
3224
+ setTimeout(sync, 10000);
3225
+ setInterval(sync, INTERVAL);`;
3060
3226
  }
3061
3227
 
3062
3228
  function native9RouterServerEntryLookup() {
@@ -3202,12 +3368,16 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3202
3368
  list: multiBotAgentMetas.map((meta) => ({
3203
3369
  id: meta.agentId,
3204
3370
  name: meta.name,
3205
- workspace: '.openclaw/' + meta.workspaceDir,
3206
- agentDir: `agents/${meta.agentId}/agent`,
3207
3371
  model: { primary: state.config.model, fallbacks: [] },
3208
3372
  })),
3209
3373
  },
3210
- commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
3374
+ commands: {
3375
+ native: 'auto',
3376
+ nativeSkills: 'auto',
3377
+ restart: true,
3378
+ ownerDisplay: 'raw',
3379
+ ...(state.config.skills.includes('scheduler') ? { ownerAllowFrom: ['*'] } : {}),
3380
+ },
3211
3381
  bindings: multiBotAgentMetas.map((meta) => ({
3212
3382
  agentId: meta.agentId,
3213
3383
  match: { channel: 'telegram', accountId: meta.accountId },
@@ -3234,6 +3404,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3234
3404
  },
3235
3405
  tools: {
3236
3406
  profile: 'full',
3407
+ ...(state.config.skills.includes('scheduler') ? { alsoAllow: ['group:automation'] } : {}),
3237
3408
  exec: { host: 'gateway', security: 'full', ask: 'off' },
3238
3409
  agentToAgent: {
3239
3410
  enabled: true,
@@ -3254,12 +3425,11 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3254
3425
  }
3255
3426
  } : {}),
3256
3427
  gateway: {
3257
- port: 18791,
3428
+ port: 18789,
3258
3429
  mode: 'local',
3259
- bind: state.nativeOs === 'vps' ? 'custom' : 'loopback',
3260
- ...(state.nativeOs === 'vps' ? { customBindHost: '0.0.0.0' } : {}),
3430
+ bind: state.nativeOs === 'vps' ? 'lan' : 'loopback',
3261
3431
  controlUi: {
3262
- allowedOrigins: getGatewayAllowedOrigins(18791),
3432
+ allowedOrigins: getGatewayAllowedOrigins(18789),
3263
3433
  },
3264
3434
  auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
3265
3435
  },
@@ -3324,7 +3494,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3324
3494
  };
3325
3495
  }
3326
3496
 
3327
- // ── botEnvContent, botConfigContent, botWorkspaceFiles, botFiles, helpers (setup/generators/config-gen.js)
3497
+ // ── botEnvContent, botConfigContent, botWorkspaceFiles, botFiles, helpers (setup/generators/config-gen.js)
3328
3498
  // @ts-nocheck
3329
3499
  /* eslint-disable no-undef, no-unused-vars */
3330
3500
  /**
@@ -3381,12 +3551,12 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3381
3551
  const bot = state.bots[botIndex] || {};
3382
3552
  const botName = bot.name || `Bot ${botIndex + 1}`;
3383
3553
  const agentId = botName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
3384
- const basePort = 18791 + botIndex;
3554
+ const basePort = 18789 + botIndex;
3385
3555
  const groupId = state.groupId || '';
3386
3556
 
3387
3557
  // Force use global provider if proxy mode is chosen globally, else use bot specific provider
3388
3558
  const botProvider = (provider && provider.isProxy) ? provider : (PROVIDERS[bot.provider] || provider);
3389
- const actualModel = botProvider.isProxy ? provider.models[0].id : (bot.model || state.config.model);
3559
+ const actualModel = botProvider.isProxy ? 'smart-route' : (bot.model || state.config.model);
3390
3560
  const bcfg = globalThis.__openclawBotConfig;
3391
3561
 
3392
3562
  const cfg = bcfg.buildOpenclawJson({
@@ -3554,7 +3724,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3554
3724
  }));
3555
3725
  }
3556
3726
 
3557
- // ── generateZaloLoginBat, generateZaloLoginSh (unified) (setup/generators/zalo-login-gen.js)
3727
+ // ── generateZaloLoginBat, generateZaloLoginSh (unified) (setup/generators/zalo-login-gen.js)
3558
3728
  // @ts-nocheck
3559
3729
  /* eslint-disable no-undef, no-unused-vars */
3560
3730
  /**
@@ -3591,15 +3761,13 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3591
3761
  * @param {string} opts.projectDirVar - BAT var for project dir e.g. '%PROJECT_DIR%'
3592
3762
  * @param {string} opts.label - Unique BAT label suffix (avoid duplicate labels)
3593
3763
  * e.g. 'win', 'multi', 'combo'
3594
- * @param {boolean} [opts.useInstance] - Use --instance default flag (for multi-bot flows)
3764
+ * @param {boolean} [opts.useInstance] - Reserved for QR file suffix only.
3595
3765
  * @returns {string[]} Lines to push into the bat script
3596
3766
  */
3597
3767
  function generateZaloLoginBat(opts) {
3598
3768
  const { homeVar, projectDirVar, label = 'default', useInstance = false } = opts;
3599
3769
  const credPath = `${homeVar}\\credentials\\zalouser\\credentials.json`;
3600
- const loginCmd = useInstance
3601
- ? 'openclaw channels login --channel zalouser --instance default --verbose'
3602
- : 'openclaw channels login --channel zalouser --verbose';
3770
+ const loginCmd = 'openclaw channels login --channel zalouser --verbose';
3603
3771
  const contLabel = `:zalo_continue_${label}`;
3604
3772
  const retryLabel = `:retry_zalo_${label}`;
3605
3773
 
@@ -3639,15 +3807,13 @@ function generateZaloLoginBat(opts) {
3639
3807
  * @param {object} opts
3640
3808
  * @param {string} opts.homeVar - Shell var for OPENCLAW_HOME e.g. '$OPENCLAW_HOME'
3641
3809
  * @param {string} opts.projectDirVar - Shell var for project dir e.g. '$PROJECT_DIR'
3642
- * @param {boolean} [opts.useInstance] - Use --instance default flag
3810
+ * @param {boolean} [opts.useInstance] - Reserved for QR file suffix only.
3643
3811
  * @returns {string[]} Lines to push into the sh script
3644
3812
  */
3645
3813
  function generateZaloLoginSh(opts) {
3646
3814
  const { homeVar, projectDirVar, useInstance = false } = opts;
3647
3815
  const credPath = `${homeVar}/credentials/zalouser/credentials.json`;
3648
- const loginCmd = useInstance
3649
- ? 'openclaw channels login --channel zalouser --instance default --verbose'
3650
- : 'openclaw channels login --channel zalouser --verbose';
3816
+ const loginCmd = 'openclaw channels login --channel zalouser --verbose';
3651
3817
 
3652
3818
  return [
3653
3819
  `# ── Zalo Personal Login (idempotent) ─────────────────────────────────`,
@@ -3671,7 +3837,7 @@ function generateZaloLoginSh(opts) {
3671
3837
  ];
3672
3838
  }
3673
3839
 
3674
- // ── generateStartScript wizard wrapper (delegates to install-gen) (setup/generators/gateway-start-gen.js)
3840
+ // ── generateStartScript wizard wrapper (delegates to install-gen) (setup/generators/gateway-start-gen.js)
3675
3841
  // @ts-nocheck
3676
3842
  /* eslint-disable no-undef, no-unused-vars */
3677
3843
  /**
@@ -3718,7 +3884,7 @@ function generateStartScript() {
3718
3884
  return null;
3719
3885
  }
3720
3886
 
3721
- // ── generateUninstallScript, setup script download helpers (setup/generators/download-gen.js)
3887
+ // ── generateUninstallScript, setup script download helpers (setup/generators/download-gen.js)
3722
3888
  // @ts-nocheck
3723
3889
  /* eslint-disable no-undef, no-unused-vars */
3724
3890
  /**
@@ -3975,7 +4141,7 @@ window.__downloadGen = {
3975
4141
  updateDockerDlLabel,
3976
4142
  };
3977
4143
 
3978
- // ── Windows .bat — if (state.nativeOs === "win") block (setup/os/win-bat.js)
4144
+ // ── Windows .bat — if (state.nativeOs === "win") block (setup/os/win-bat.js)
3979
4145
  // @ts-nocheck
3980
4146
  /* eslint-disable no-undef, no-unused-vars */
3981
4147
  /**
@@ -4027,8 +4193,8 @@ function generateWinBat(ctx) {
4027
4193
 
4028
4194
  function appendDashboardInfo(arr) {
4029
4195
  arr.push('echo.');
4030
- arr.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
4031
- arr.push('echo Other reachable URLs: http://localhost:18791');
4196
+ arr.push('echo OpenClaw Dashboard: http://127.0.0.1:18789');
4197
+ arr.push('echo Other reachable URLs: http://localhost:18789');
4032
4198
  arr.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
4033
4199
  if (is9Router) {
4034
4200
  arr.push('echo.');
@@ -4120,7 +4286,7 @@ function generateWinBat(ctx) {
4120
4286
  return { scriptName, scriptContent };
4121
4287
  }
4122
4288
 
4123
- // ── macOS .sh — if (state.nativeOs === "macos") block (setup/os/macos-sh.js)
4289
+ // ── macOS .sh — if (state.nativeOs === "macos") block (setup/os/macos-sh.js)
4124
4290
  // @ts-nocheck
4125
4291
  /* eslint-disable no-undef, no-unused-vars */
4126
4292
  /**
@@ -4238,7 +4404,7 @@ function generateMacOsSh(ctx) {
4238
4404
  return { scriptName, scriptContent };
4239
4405
  }
4240
4406
 
4241
- // ── VPS/PM2 .sh — if (state.nativeOs === "vps") block (setup/os/vps-sh.js)
4407
+ // ── VPS/PM2 .sh — if (state.nativeOs === "vps") block (setup/os/vps-sh.js)
4242
4408
  // @ts-nocheck
4243
4409
  /* eslint-disable no-undef, no-unused-vars */
4244
4410
  /**
@@ -4352,7 +4518,7 @@ GWEOF`);
4352
4518
  }
4353
4519
 
4354
4520
  vps.push('echo ""');
4355
- vps.push('echo "Dashboard: http://127.0.0.1:18791"');
4521
+ vps.push('echo "Dashboard: http://127.0.0.1:18789"');
4356
4522
  if (is9Router) vps.push('echo "9Router: http://127.0.0.1:20128/dashboard"');
4357
4523
  vps.push('echo ""');
4358
4524
  vps.push(`echo "Restart: bash start-bot.sh"`);
@@ -4363,7 +4529,7 @@ GWEOF`);
4363
4529
  return { scriptName, scriptContent };
4364
4530
  }
4365
4531
 
4366
- // ── Linux Desktop .sh — if (state.nativeOs === "linux-desktop") block (setup/os/linux-sh.js)
4532
+ // ── Linux Desktop .sh — if (state.nativeOs === "linux-desktop") block (setup/os/linux-sh.js)
4367
4533
  // @ts-nocheck
4368
4534
  /* eslint-disable no-undef, no-unused-vars */
4369
4535
  /**
@@ -4426,7 +4592,7 @@ function generateLinuxSh(ctx) {
4426
4592
  return { scriptName, scriptContent };
4427
4593
  }
4428
4594
 
4429
- // ── UI init, language/channel/deploy controllers, form rendering (setup/ui/controller.js)
4595
+ // ── UI init, language/channel/deploy controllers, form rendering (setup/ui/controller.js)
4430
4596
  // @ts-nocheck
4431
4597
  /* eslint-disable no-undef, no-unused-vars */
4432
4598
  /**
@@ -5075,7 +5241,7 @@ function generateLinuxSh(ctx) {
5075
5241
  envContent.textContent = lines.join('\n');
5076
5242
  }
5077
5243
 
5078
- // ── Multi-bot state + UI (setup/ui/multi-bot.js) ───────────────────
5244
+ // ── Multi-bot state + UI (setup/ui/multi-bot.js) ───────────────────
5079
5245
  // @ts-nocheck
5080
5246
  /* eslint-disable no-undef, no-unused-vars */
5081
5247
  /**
@@ -5424,7 +5590,7 @@ function generateLinuxSh(ctx) {
5424
5590
 
5425
5591
  // ========== Step 1: Deploy Mode + OS ==========
5426
5592
 
5427
- // ── Step navigation, validation (setup/ui/steps.js) ────────────────
5593
+ // ── Step navigation, validation (setup/ui/steps.js) ────────────────
5428
5594
  // @ts-nocheck
5429
5595
  /* eslint-disable no-undef, no-unused-vars */
5430
5596
  /**
@@ -5584,7 +5750,7 @@ function generateLinuxSh(ctx) {
5584
5750
 
5585
5751
  // ========== Step 2: Bot Config ==========
5586
5752
 
5587
- // ── generateOutput + generateNativeScript + clipboard (setup/ui/output.js)
5753
+ // ── generateOutput + generateNativeScript + clipboard (setup/ui/output.js)
5588
5754
  // @ts-nocheck
5589
5755
  /* eslint-disable no-undef, no-unused-vars */
5590
5756
  /**
@@ -5751,15 +5917,26 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
5751
5917
  },
5752
5918
  list: [{
5753
5919
  id: agentId,
5754
- workspace: `.openclaw/workspace-${agentId}`,
5920
+ name: botName,
5921
+ workspace: `/root/project/.openclaw/workspace-${agentId}`,
5755
5922
  agentDir: `agents/${agentId}/agent`,
5756
5923
  model: { primary: state.config.model, fallbacks: [] },
5757
5924
  }],
5758
5925
  },
5759
- commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
5926
+ commands: {
5927
+ native: 'auto',
5928
+ nativeSkills: 'auto',
5929
+ restart: true,
5930
+ ownerDisplay: 'raw',
5931
+ ...(state.config.skills.includes('scheduler') ? { ownerAllowFrom: ['*'] } : {}),
5932
+ },
5760
5933
  channels: ch.channelConfig,
5761
- tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
5762
- gateway: common.buildGatewayConfig(18791, 'native', getGatewayAllowedOrigins(18791)),
5934
+ tools: {
5935
+ profile: 'full',
5936
+ ...(state.config.skills.includes('scheduler') ? { alsoAllow: ['group:automation'] } : {}),
5937
+ exec: { host: 'gateway', security: 'full', ask: 'off' },
5938
+ },
5939
+ gateway: common.buildGatewayConfig(18789, state.deployMode, getGatewayAllowedOrigins(18789), state.nativeOs || ''),
5763
5940
  };
5764
5941
 
5765
5942
  // 9Router: add proxy endpoint config under models.providers
@@ -5849,7 +6026,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
5849
6026
  clawConfig.agents.list = multiBotAgentMetas.map((meta) => ({
5850
6027
  id: meta.agentId,
5851
6028
  name: meta.name,
5852
- workspace: '.openclaw/' + meta.workspaceDir,
6029
+ workspace: `/root/project/.openclaw/${meta.workspaceDir}`,
5853
6030
  agentDir: `agents/${meta.agentId}/agent`,
5854
6031
  model: { primary: state.config.model, fallbacks: [] },
5855
6032
  }));
@@ -5880,6 +6057,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
5880
6057
  };
5881
6058
  clawConfig.tools = {
5882
6059
  ...(clawConfig.tools || {}),
6060
+ ...(state.config.skills.includes('scheduler') ? { alsoAllow: ['group:automation'] } : {}),
5883
6061
  agentToAgent: {
5884
6062
  enabled: true,
5885
6063
  allow: multiBotAgentMetas.map((meta) => meta.agentId),
@@ -5974,6 +6152,9 @@ model:
5974
6152
  dockerfileSkillInstallMode: 'build',
5975
6153
  runtimeCommandParts: [
5976
6154
  pluginInstallCmd,
6155
+ // zalouser: use npm install (not openclaw plugins install) to avoid openclaw.json writes
6156
+ // ClawHub build gives error:not configured; npm version works correctly
6157
+ state.channel === 'zalo-personal' ? 'ensure_zalouser' : '',
5977
6158
  'while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done >/dev/null 2>&1 &'
5978
6159
  ].filter(Boolean),
5979
6160
  plainSingleExtraHosts: true,
@@ -5983,6 +6164,7 @@ model:
5983
6164
  });
5984
6165
  const dockerfile = dockerArtifacts.dockerfile;
5985
6166
  const compose = dockerArtifacts.compose;
6167
+ const entrypointScript = dockerArtifacts.entrypointScript;
5986
6168
  // isMultiBot => unified into isMultiBot above
5987
6169
  setOutput('out-dockerfile', dockerfile);
5988
6170
  setOutput('out-compose', compose);
@@ -6396,6 +6578,7 @@ fi
6396
6578
  if (!isNativeMode) {
6397
6579
  sharedFiles['docker/openclaw/Dockerfile'] = dockerfile;
6398
6580
  sharedFiles['docker/openclaw/docker-compose.yml'] = compose;
6581
+ sharedFiles['docker/openclaw/entrypoint.sh'] = entrypointScript;
6399
6582
  sharedFiles['docker/openclaw/.env'] = rootEnvContent;
6400
6583
  }
6401
6584
  sharedFiles[globalThis.__openclawCommon.TELEGRAM_SETUP_GUIDE_FILENAME] = buildTelegramPostInstallChecklist();
@@ -6467,6 +6650,7 @@ fi
6467
6650
  if (!isNativeMode) {
6468
6651
  singleFiles['docker/openclaw/Dockerfile'] = dockerfile;
6469
6652
  singleFiles['docker/openclaw/docker-compose.yml'] = compose;
6653
+ singleFiles['docker/openclaw/entrypoint.sh'] = entrypointScript;
6470
6654
  singleFiles['docker/openclaw/.env'] = rootEnvContent;
6471
6655
  }
6472
6656
  state._generatedFiles = singleFiles;
@@ -6635,38 +6819,46 @@ fi
6635
6819
  // ========== Zalo Personal Login Guide (post-setup) ==========
6636
6820
  function generateZaloOnboardGuide() {
6637
6821
  const lang = document.getElementById('cfg-language')?.value || 'vi';
6638
- setOutput('out-zalo-onboard-cmd', `docker compose stop ai-bot
6639
- docker compose run --rm --no-deps ai-bot openclaw channels login --channel zalouser --verbose
6640
- docker compose up -d --force-recreate ai-bot
6641
- docker compose exec ai-bot openclaw channels status --probe`);
6822
+ const isVi = lang === 'vi';
6823
+ const containerName = state.botCount > 1 ? 'openclaw-multibot' : 'openclaw-bot';
6642
6824
 
6643
- if (lang === 'vi') {
6825
+ setOutput('out-zalo-onboard-cmd', `# ${isVi ? 'Bước 1: Dọn dẹp session cũ' : 'Step 1: Clean up old session'}
6826
+ docker exec ${containerName} rm -f /root/project/.openclaw/credentials/zalouser/credentials.json
6827
+
6828
+ # ${isVi ? 'Bước 2: Kích hoạt màn hình login QR (Quét mã trên terminal)' : 'Step 2: Start login QR (Scan on terminal)'}
6829
+ docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose
6830
+
6831
+ # ${isVi ? 'Bước 3: Khởi động lại container sau khi login thành công' : 'Step 3: Restart container after successful login'}
6832
+ docker restart ${containerName}`);
6833
+
6834
+ if (isVi) {
6644
6835
  setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
6645
- │ Chạy lệnh bên trái để OpenClaw tạo QR đăng nhập.
6836
+ │ Chạy các lệnh bên trái để đăng nhập Zalo Personal
6837
+ │ theo quy trình chuẩn 4 bước. │
6646
6838
  ├─────────────────────────────────────────────────────┤
6647
- │ 1. Đảm bảo container/gateway đã chạy xong.
6648
- 2. Stop ai-bot, login bằng compose run one-shot.
6649
- 3. OpenClaw sẽ in ra đường dẫn file QR trong /tmp.
6650
- 4. Copy file QR ra ngoài nếu cần:
6651
- │ docker cp openclaw-bot:/tmp/openclaw/ │
6839
+ │ 1. Lệnh 1 xoá file credentials.json cũ để tránh
6840
+ lỗi xung đột "Already linked".
6841
+ 2. Lệnh 2 mở màn hình login. Quét QR hiện trên
6842
+ terminal hoặc lấy file từ container:
6843
+ │ docker cp ${containerName}:/tmp/openclaw/ │
6652
6844
  │ openclaw-zalouser-qr-default.png . │
6653
- 5. Mở ảnh QR → quét bằng app Zalo xác nhận.
6654
- 6. Start lại: docker compose up -d --force-recreate
6655
- 7. Chạy channels status --probe, phải thấy running.│
6845
+ 3. Sau khi quét xong terminal báo thành công,
6846
+ nhấn Ctrl+C để thoát.
6847
+ 4. Chạy Lệnh 3 để restart container giúp nhận tin. │
6656
6848
  └─────────────────────────────────────────────────────┘`);
6657
6849
  } else {
6658
6850
  setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
6659
- │ Run the command on the left to generate a Zalo QR.
6851
+ │ Run the commands on the left to login Zalo Personal
6852
+ │ following the standard 4-step workflow. │
6660
6853
  ├─────────────────────────────────────────────────────┤
6661
- │ 1. Make sure the container/gateway is already up.
6662
- 2. Stop ai-bot; login with compose run one-shot.
6663
- 3. OpenClaw prints the QR image path under /tmp.
6664
- 4. Copy the QR out if needed:
6665
- │ docker cp openclaw-bot:/tmp/openclaw/ │
6854
+ │ 1. Command 1 deletes the old credentials.json file
6855
+ to avoid "Already linked" conflicts.
6856
+ 2. Command 2 opens the login interface. Scan the
6857
+ QR on your terminal or copy the image file:
6858
+ │ docker cp ${containerName}:/tmp/openclaw/ │
6666
6859
  │ openclaw-zalouser-qr-default.png . │
6667
- 5. Open the image scan with Zalo mobile app.
6668
- 6. Start again: docker compose up -d --force-re...
6669
- │ 7. Run channels status --probe; it should run. │
6860
+ 3. Once scanned and successful, press Ctrl+C.
6861
+ 4. Run Command 3 to restart the container.
6670
6862
  └─────────────────────────────────────────────────────┘`);
6671
6863
  }
6672
6864
  }