create-openclaw-bot 5.1.14 β†’ 5.1.15

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/setup.js CHANGED
@@ -161,14 +161,14 @@
161
161
 
162
162
  // ========== Available Plugins (npm packages β€” runtime/channel extensions) ==========
163
163
  const PLUGINS = [
164
- {
165
- id: 'telegram-multibot-relay',
166
- name: 'Telegram Multi-Bot Relay',
167
- icon: '🀝',
168
- descVi: 'Điều phα»‘i nhiều bot Telegram trong cΓΉng group β€” tα»± Δ‘α»™ng khi chọn nhiều bot', descEn: 'Coordinate multiple Telegram bots in one group β€” auto-selected with multi-bot',
169
- package: 'telegram-multibot-relay',
170
- hidden: true, // hidden in UI, auto-selected programmatically
171
- },
164
+ {
165
+ id: 'telegram-multibot-relay',
166
+ name: 'Telegram Multi-Bot Relay',
167
+ icon: '🀝',
168
+ descVi: 'Điều phα»‘i nhiều bot Telegram trong cΓΉng group β€” tα»± Δ‘α»™ng khi chọn nhiều bot', descEn: 'Coordinate multiple Telegram bots in one group β€” auto-selected with multi-bot',
169
+ package: 'openclaw-telegram-multibot-relay',
170
+ hidden: true, // hidden in UI, auto-selected programmatically
171
+ },
172
172
  {
173
173
  id: 'voice-call',
174
174
  name: 'Voice Call',
@@ -1049,14 +1049,18 @@
1049
1049
  }).join('');
1050
1050
  }
1051
1051
 
1052
- window.__selectProvider = function (key) {
1053
- state.config.provider = key;
1054
- const p = PROVIDERS[key];
1055
- state.config.model = p.models[0].id;
1056
-
1057
- // Highlight card
1058
- document.querySelectorAll('.provider-card').forEach((c) => c.classList.remove('provider-card--selected'));
1059
- document.querySelector(`.provider-card[data-provider="${key}"]`)?.classList.add('provider-card--selected');
1052
+ window.__selectProvider = function (key) {
1053
+ state.config.provider = key;
1054
+ const p = PROVIDERS[key];
1055
+ state.config.model = p.models[0].id;
1056
+ if (state.bots[state.activeBotIndex]) {
1057
+ state.bots[state.activeBotIndex].provider = key;
1058
+ state.bots[state.activeBotIndex].model = p.models[0].id;
1059
+ }
1060
+
1061
+ // Highlight card
1062
+ document.querySelectorAll('.provider-card').forEach((c) => c.classList.remove('provider-card--selected'));
1063
+ document.querySelector(`.provider-card[data-provider="${key}"]`)?.classList.add('provider-card--selected');
1060
1064
 
1061
1065
  // Update model dropdown
1062
1066
  const modelSelect = document.getElementById('cfg-model');
@@ -1246,36 +1250,44 @@
1246
1250
  renderBotTabBar();
1247
1251
  }
1248
1252
 
1249
- function saveFormData() {
1250
- state.config.botName = document.getElementById('cfg-name')?.value || state.config.botName || 'Chat Bot';
1251
- state.config.description = document.getElementById('cfg-desc')?.value || state.config.description || 'Personal AI assistant';
1252
- state.config.emoji = document.getElementById('cfg-emoji')?.value || state.config.emoji || 'πŸ€–';
1253
- state.config.model = document.getElementById('cfg-model')?.value || state.config.model || 'google/gemini-2.5-flash';
1253
+ function saveFormData() {
1254
+ state.config.botName = document.getElementById('cfg-name')?.value || state.config.botName || 'Chat Bot';
1255
+ state.config.description = document.getElementById('cfg-desc')?.value || state.config.description || 'Personal AI assistant';
1256
+ state.config.emoji = document.getElementById('cfg-emoji')?.value || state.config.emoji || 'πŸ€–';
1257
+ state.config.model = document.getElementById('cfg-model')?.value || state.config.model || 'google/gemini-2.5-flash';
1254
1258
  state.config.language = document.getElementById('cfg-language')?.value || state.config.language || 'vi';
1255
1259
  state.config.systemPrompt = document.getElementById('cfg-prompt')?.value || state.config.systemPrompt || DEFAULT_PROMPTS['vi'];
1256
1260
  state.config.userInfo = document.getElementById('cfg-user-info')?.value?.trim() || state.config.userInfo || '';
1257
1261
  state.config.securityRules = document.getElementById('cfg-security')?.value || state.config.securityRules || DEFAULT_SECURITY_RULES['vi'];
1258
1262
  // Also save bot-tab-name β†’ bots[0].name so both state locations stay in sync
1259
- const tabName = document.getElementById('cfg-bot-tab-name')?.value?.trim();
1260
- if (tabName && state.bots[0]) state.bots[0].name = tabName;
1261
- else if (state.config.botName && state.bots[0] && !state.bots[0].name) {
1262
- state.bots[0].name = state.config.botName;
1263
- }
1264
- }
1263
+ const tabName = document.getElementById('cfg-bot-tab-name')?.value?.trim();
1264
+ if (tabName && state.bots[0]) state.bots[0].name = tabName;
1265
+ else if (state.config.botName && state.bots[0] && !state.bots[0].name) {
1266
+ state.bots[0].name = state.config.botName;
1267
+ }
1268
+ if (state.bots[state.activeBotIndex]) {
1269
+ state.bots[state.activeBotIndex].provider = state.config.provider;
1270
+ state.bots[state.activeBotIndex].model = state.config.model;
1271
+ }
1272
+ }
1265
1273
 
1266
1274
  // Save Step 4 credential inputs to state (persists across Back navigation)
1267
- function saveCredentials() {
1268
- const botTokenEl = document.getElementById('key-bot-token');
1269
- const apiKeyEl = document.getElementById('key-api-key');
1270
- const pathEl = document.getElementById('cfg-project-path');
1271
- if (botTokenEl) state.config.botToken = botTokenEl.value;
1272
- if (apiKeyEl) state.config.apiKey = apiKeyEl.value;
1273
- if (pathEl) state.config.projectPath = pathEl.value;
1274
-
1275
- // Also save multi-bot tokens individually
1276
- if (state.botCount > 1) {
1277
- for (let i = 0; i < state.botCount; i++) {
1278
- const el = document.getElementById(`key-bot-token-${i}`);
1275
+ function saveCredentials() {
1276
+ const botTokenEl = document.getElementById('key-bot-token');
1277
+ const apiKeyEl = document.getElementById('key-api-key');
1278
+ const pathEl = document.getElementById('cfg-project-path');
1279
+ if (botTokenEl) state.config.botToken = botTokenEl.value;
1280
+ if (apiKeyEl) state.config.apiKey = apiKeyEl.value;
1281
+ if (pathEl) state.config.projectPath = pathEl.value;
1282
+ if (state.botCount <= 1 && state.bots[state.activeBotIndex]) {
1283
+ if (botTokenEl) state.bots[state.activeBotIndex].token = botTokenEl.value;
1284
+ if (apiKeyEl) state.bots[state.activeBotIndex].apiKey = apiKeyEl.value;
1285
+ }
1286
+
1287
+ // Also save multi-bot tokens individually
1288
+ if (state.botCount > 1) {
1289
+ for (let i = 0; i < state.botCount; i++) {
1290
+ const el = document.getElementById(`key-bot-token-${i}`);
1279
1291
  if (el && state.bots[i]) state.bots[i].token = el.value;
1280
1292
  }
1281
1293
  }
@@ -1534,7 +1546,7 @@
1534
1546
  const is9Router = provider.isProxy;
1535
1547
  const isLocal = provider.isLocal;
1536
1548
  const isTelegramMultiBot = state.botCount > 1 && state.channel === 'telegram';
1537
- const relayPluginSpec = 'clawhub:openclaw-telegram-multibot-relay';
1549
+ const relayPluginSpec = 'openclaw-telegram-multibot-relay';
1538
1550
 
1539
1551
  function buildRelayPluginInstallCommand(prefix) {
1540
1552
  return `${prefix} plugins install ${relayPluginSpec} 2>/dev/null || true`;
@@ -1545,7 +1557,8 @@
1545
1557
  }
1546
1558
 
1547
1559
  function buildTelegramPostInstallChecklist() {
1548
- const groupId = state.groupId || '';
1560
+ const groupId = state.groupId || '';
1561
+ const nativeProjectOpenClawRoot = `${projectDir.replace(/\\/g, '/')}/.openclaw`;
1549
1562
  const botList = state.bots.slice(0, state.botCount).map((bot, idx) => `- **${bot?.name || `Bot ${idx + 1}`}**`).join('\n');
1550
1563
  const isVi = lang === 'vi';
1551
1564
  return isVi
@@ -1688,14 +1701,15 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
1688
1701
  commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
1689
1702
  channels: ch.channelConfig,
1690
1703
  tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
1691
- gateway: {
1692
- port: 18791,
1693
- mode: 'local',
1694
- bind: '0.0.0.0',
1695
- controlUi: {
1696
- allowedOrigins: getGatewayAllowedOrigins(18791),
1697
- },
1698
- auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
1704
+ gateway: {
1705
+ port: 18791,
1706
+ mode: 'local',
1707
+ bind: 'custom',
1708
+ customBindHost: '0.0.0.0',
1709
+ controlUi: {
1710
+ allowedOrigins: getGatewayAllowedOrigins(18791),
1711
+ },
1712
+ auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
1699
1713
  },
1700
1714
  };
1701
1715
 
@@ -1793,13 +1807,14 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
1793
1807
  botToken: meta.token || '<your_bot_token>',
1794
1808
  ackReaction: 'πŸ‘',
1795
1809
  }]));
1796
- clawConfig.agents.list = multiBotAgentMetas.map((meta) => ({
1797
- id: meta.agentId,
1798
- name: meta.name,
1799
- workspace: `/root/.openclaw/${meta.workspaceDir}`,
1800
- agentDir: `/root/.openclaw/agents/${meta.agentId}/agent`,
1801
- model: { primary: state.config.model, fallbacks: [] },
1802
- }));
1810
+ const nativeOpenClawRoot = '.openclaw';
1811
+ clawConfig.agents.list = multiBotAgentMetas.map((meta) => ({
1812
+ id: meta.agentId,
1813
+ name: meta.name,
1814
+ workspace: `${nativeOpenClawRoot}/${meta.workspaceDir}`,
1815
+ agentDir: `${nativeOpenClawRoot}/agents/${meta.agentId}/agent`,
1816
+ model: { primary: state.config.model, fallbacks: [] },
1817
+ }));
1803
1818
  clawConfig.bindings = multiBotAgentMetas.map((meta) => ({
1804
1819
  agentId: meta.agentId,
1805
1820
  match: { channel: 'telegram', accountId: meta.accountId },
@@ -1969,8 +1984,8 @@ ${finalCmd}`;
1969
1984
 
1970
1985
  // ─── Dynamic Smart Route Sync Script ────────────────────────────────────────
1971
1986
  // Background loop inside 9Router container every 30s.
1972
- // Read providerConnections directly from db.json so smart-route survives
1973
- // dashboard auth/response changes in newer 9Router builds.
1987
+ // Sync against the 9Router API so smart-route matches the current
1988
+ // active provider set instead of stale db-only state.
1974
1989
  const syncScript = `const fs=require('fs');const INTERVAL=30000;const p='/root/.9router/db.json';
1975
1990
  const PM={codex:['cx/gpt-5.4','cx/gpt-5.3-codex','cx/gpt-5.3-codex-high','cx/gpt-5.2-codex','cx/gpt-5.2','cx/gpt-5.1-codex-max','cx/gpt-5.1-codex','cx/gpt-5.1','cx/gpt-5-codex'],'claude-code':['cc/claude-opus-4-6','cc/claude-sonnet-4-6','cc/claude-opus-4-5-20251101','cc/claude-sonnet-4-5-20250929','cc/claude-haiku-4-5-20251001'],github:['gh/gpt-5.4','gh/gpt-5.3-codex','gh/gpt-5.2-codex','gh/gpt-5.2','gh/gpt-5.1-codex-max','gh/gpt-5.1-codex','gh/gpt-5.1','gh/gpt-5','gh/gpt-4.1','gh/gpt-4o','gh/claude-opus-4.6','gh/claude-sonnet-4.6','gh/claude-sonnet-4.5','gh/claude-opus-4.5','gh/claude-haiku-4.5','gh/gemini-3-pro-preview','gh/gemini-3-flash-preview','gh/gemini-2.5-pro'],cursor:['cu/default','cu/claude-4.6-opus-max','cu/claude-4.5-opus-high-thinking','cu/claude-4.5-sonnet-thinking','cu/claude-4.5-sonnet','cu/gpt-5.3-codex','cu/gpt-5.2-codex','cu/gemini-3-flash-preview'],kilo:['kc/anthropic/claude-sonnet-4-20250514','kc/anthropic/claude-opus-4-20250514','kc/google/gemini-2.5-pro','kc/google/gemini-2.5-flash','kc/openai/gpt-4.1','kc/deepseek/deepseek-chat'],cline:['cl/anthropic/claude-sonnet-4.6','cl/anthropic/claude-opus-4.6','cl/openai/gpt-5.3-codex','cl/openai/gpt-5.4','cl/google/gemini-3.1-pro-preview'],'gemini-cli':['gc/gemini-3-flash-preview','gc/gemini-3-pro-preview'],iflow:['if/qwen3-coder-plus','if/kimi-k2','if/kimi-k2-thinking','if/glm-4.7','if/deepseek-r1','if/deepseek-v3.2','if/deepseek-v3','if/qwen3-max','if/qwen3-235b','if/iflow-rome-30ba3b'],qwen:['qw/qwen3-coder-plus','qw/qwen3-coder-flash','qw/vision-model','qw/coder-model'],kiro:['kr/claude-sonnet-4.5','kr/claude-haiku-4.5','kr/deepseek-3.2','kr/deepseek-3.1','kr/qwen3-coder-next'],ollama:['ollama/gemma4:e2b','ollama/gemma4:e4b','ollama/gemma4:26b','ollama/gemma4:31b','ollama/qwen3.5','ollama/kimi-k2.5','ollama/glm-5','ollama/glm-4.7-flash','ollama/minimax-m2.5','ollama/gpt-oss:120b'],'kimi-coding':['kmc/kimi-k2.5','kmc/kimi-k2.5-thinking','kmc/kimi-latest'],glm:['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],'glm-cn':['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],minimax:['minimax/MiniMax-M2.7','minimax/MiniMax-M2.5','minimax/MiniMax-M2.1'],kimi:['kimi/kimi-k2.5','kimi/kimi-k2.5-thinking','kimi/kimi-latest'],deepseek:['deepseek/deepseek-chat','deepseek/deepseek-reasoner'],xai:['xai/grok-4','xai/grok-4-fast-reasoning','xai/grok-code-fast-1'],mistral:['mistral/mistral-large-latest','mistral/codestral-latest'],groq:['groq/llama-3.3-70b-versatile','groq/openai/gpt-oss-120b'],cerebras:['cerebras/gpt-oss-120b'],alicode:['alicode/qwen3.5-plus','alicode/qwen3-coder-plus'],openai:['openai/gpt-4o','openai/gpt-4.1'],anthropic:['anthropic/claude-sonnet-4','anthropic/claude-haiku-3.5'],gemini:['gemini/gemini-2.5-flash','gemini/gemini-2.5-pro']};
1976
1991
  console.log('[sync-combo] 9Router sync loop started...');
@@ -2759,13 +2774,14 @@ fi
2759
2774
  id: botAgentId,
2760
2775
  model: { primary: state.config.model, fallbacks: [] },
2761
2776
  }];
2762
- botConfig.gateway = {
2763
- ...(botConfig.gateway || {}),
2764
- port: 18791,
2765
- mode: 'local',
2766
- bind: '0.0.0.0',
2767
- auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
2768
- };
2777
+ botConfig.gateway = {
2778
+ ...(botConfig.gateway || {}),
2779
+ port: 18791,
2780
+ mode: 'local',
2781
+ bind: 'custom',
2782
+ customBindHost: '0.0.0.0',
2783
+ auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
2784
+ };
2769
2785
 
2770
2786
  const botAgentYaml = `name: ${botAgentId}
2771
2787
  description: "${botDesc}"
@@ -2967,40 +2983,62 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
2967
2983
  const ch = CHANNELS[state.channel];
2968
2984
  const is9Router = !!(provider && provider.isProxy);
2969
2985
  const isOllama = !!(provider && provider.isLocal);
2970
- const hasBrowser = state.config.skills.includes('browser');
2971
- const selectedModel = (state.config.model || 'ollama/gemma4:e2b').replace('ollama/', '');
2986
+ const hasBrowser = state.config.skills.includes('browser');
2987
+ const nativeSkillConfigs = state.config.skills
2988
+ .map((sid) => SKILLS.find((s) => s.id === sid))
2989
+ .filter((skill) => skill && skill.id !== 'scheduler' && skill.slug && skill.slug !== 'browser-automation');
2990
+ const selectedModel = (state.config.model || 'ollama/gemma4:e2b').replace('ollama/', '');
2972
2991
  const isMultiBot = state.botCount > 1 && state.channel === 'telegram';
2973
2992
  const projectDir = state.config.projectPath || '.';
2974
2993
 
2975
- const allPlugins = [];
2994
+ const allPlugins = [];
2976
2995
  if (ch && ch.pluginInstall) allPlugins.push(ch.pluginInstall);
2977
2996
  state.config.plugins.forEach(function(pid) {
2978
2997
  const p = PLUGINS.find((x) => x.id === pid);
2979
2998
  if (p) allPlugins.push(p.package);
2980
2999
  });
2981
- if (isMultiBot && state.channel === 'telegram') allPlugins.push(relayPluginSpec);
3000
+ if (isMultiBot && state.channel === 'telegram') allPlugins.push(relayPluginSpec);
2982
3001
  const pluginCmd = allPlugins.length > 0 ? ('call npm exec -- openclaw plugins install ' + allPlugins.join(' ') + ' || goto :fail') : '';
3002
+ const nativeSkillInstallCmds = nativeSkillConfigs.map((skill) => `call openclaw skills install ${skill.slug} || echo Warning: Failed to install skill ${skill.slug}`);
2983
3003
 
2984
3004
  function native9RouterSyncScriptContent() {
2985
3005
  return `const fs=require('fs');
2986
3006
  const path=require('path');
2987
3007
  const INTERVAL=30000;
2988
- const p=path.join(process.env.HOME||process.env.USERPROFILE||'.','.9router','db.json');
3008
+ const p=path.join(process.env.DATA_DIR||'.9router','db.json');
3009
+ const ROUTER='http://localhost:20128';
2989
3010
  const PM={'codex':['cx/gpt-5.4','cx/gpt-5.3-codex','cx/gpt-5.3-codex-high','cx/gpt-5.2-codex','cx/gpt-5.2','cx/gpt-5.1-codex-max','cx/gpt-5.1-codex','cx/gpt-5.1','cx/gpt-5-codex'],'claude-code':['cc/claude-opus-4-6','cc/claude-sonnet-4-6','cc/claude-opus-4-5-20251101','cc/claude-sonnet-4-5-20250929','cc/claude-haiku-4-5-20251001'],'github':['gh/gpt-5.4','gh/gpt-5.3-codex','gh/gpt-5.2-codex','gh/gpt-5.2','gh/gpt-5.1-codex-max','gh/gpt-5.1-codex','gh/gpt-5.1','gh/gpt-5','gh/gpt-4.1','gh/gpt-4o','gh/claude-opus-4.6','gh/claude-sonnet-4.6','gh/claude-sonnet-4.5','gh/claude-opus-4.5','gh/claude-haiku-4.5','gh/gemini-3-pro-preview','gh/gemini-3-flash-preview','gh/gemini-2.5-pro'],'cursor':['cu/default','cu/claude-4.6-opus-max','cu/claude-4.5-opus-high-thinking','cu/claude-4.5-sonnet-thinking','cu/claude-4.5-sonnet','cu/gpt-5.3-codex','cu/gpt-5.2-codex','cu/gemini-3-flash-preview'],'kilo':['kc/anthropic/claude-sonnet-4-20250514','kc/anthropic/claude-opus-4-20250514','kc/google/gemini-2.5-pro','kc/google/gemini-2.5-flash','kc/openai/gpt-4.1','kc/deepseek/deepseek-chat'],'cline':['cl/anthropic/claude-sonnet-4.6','cl/anthropic/claude-opus-4.6','cl/openai/gpt-5.3-codex','cl/openai/gpt-5.4','cl/google/gemini-3.1-pro-preview'],'gemini-cli':['gc/gemini-3-flash-preview','gc/gemini-3-pro-preview'],'iflow':['if/qwen3-coder-plus','if/kimi-k2','if/kimi-k2-thinking','if/glm-4.7','if/deepseek-r1','if/deepseek-v3.2','if/deepseek-v3','if/qwen3-max','if/qwen3-235b','if/iflow-rome-30ba3b'],'qwen':['qw/qwen3-coder-plus','qw/qwen3-coder-flash','qw/vision-model','qw/coder-model'],'kiro':['kr/claude-sonnet-4.5','kr/claude-haiku-4.5','kr/deepseek-3.2','kr/deepseek-3.1','kr/qwen3-coder-next'],'ollama':['ollama/gemma4:e2b','ollama/gemma4:e4b','ollama/gemma4:26b','ollama/gemma4:31b','ollama/qwen3.5','ollama/kimi-k2.5','ollama/glm-5','ollama/glm-4.7-flash','ollama/minimax-m2.5','ollama/gpt-oss:120b'],'kimi-coding':['kmc/kimi-k2.5','kmc/kimi-k2.5-thinking','kmc/kimi-latest'],'glm':['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],'glm-cn':['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],'minimax':['minimax/MiniMax-M2.7','minimax/MiniMax-M2.5','minimax/MiniMax-M2.1'],'kimi':['kimi/kimi-k2.5','kimi/kimi-k2.5-thinking','kimi/kimi-latest'],'deepseek':['deepseek/deepseek-chat','deepseek/deepseek-reasoner'],'xai':['xai/grok-4','xai/grok-4-fast-reasoning','xai/grok-code-fast-1'],'mistral':['mistral/mistral-large-latest','mistral/codestral-latest'],'groq':['groq/llama-3.3-70b-versatile','groq/openai/gpt-oss-120b'],'cerebras':['cerebras/gpt-oss-120b'],'alicode':['alicode/qwen3.5-plus','alicode/qwen3-coder-plus'],'openai':['openai/gpt-4o','openai/gpt-4.1'],'anthropic':['anthropic/claude-sonnet-4','anthropic/claude-haiku-3.5'],'gemini':['gemini/gemini-2.5-flash','gemini/gemini-2.5-pro']};
2990
- const sync=()=>{try{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));}};const a=(db.providerConnections||[]).filter(c=>c&&c.provider&&c.isActive!==false&&!c.disabled).map(c=>c.provider);if(!a.length){removeSmartRoute();return;}const PREF=['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];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));}}else{db.combos.push(c);fs.writeFileSync(p,JSON.stringify(db,null,2));}}catch{}};sync();setInterval(sync,INTERVAL);`;
3011
+ console.log('[sync-combo] 9Router sync loop started...');
3012
+ 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 a=(d.connections||[]).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;}const PREF=['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];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);`;
2991
3013
  }
2992
3014
 
2993
- // ─── Shared initializer (provider install) ───────────────────────────────
2994
- function providerLines(arr, shell) {
2995
- if (is9Router) {
2996
- if (shell === 'bat') {
3015
+ function native9RouterServerEntryLookup() {
3016
+ return "node -e \"const fs=require('fs'),path=require('path'),os=require('os'),cp=require('child_process');const home=os.homedir();const roots=[];try{const root=cp.execSync('npm root -g',{stdio:['ignore','pipe','ignore'],encoding:'utf8'}).trim();if(root)roots.push(root);}catch{}for(const prefix of [process.env.npm_config_prefix,process.env.NPM_CONFIG_PREFIX,process.env.PREFIX,process.env.NPM_PREFIX,path.join(home,'.local'),path.join(home,'.npm-global'),path.join(home,'.local','share','npm')].filter(Boolean)){roots.push(path.join(prefix,'lib','node_modules'));}roots.push(path.join(home,'.local','share','npm','lib','node_modules'));roots.push(path.join(home,'.local','lib','node_modules'));const seen=new Set();const found=roots.map(root=>path.join(root,'9router','app','server.js')).find(candidate=>{if(seen.has(candidate))return false;seen.add(candidate);return fs.existsSync(candidate);});if(!found)process.exit(1);console.log(found);\"";
3017
+ }
3018
+
3019
+ function windowsHiddenNodeLaunch(targetPath, extraEnv = {}) {
3020
+ function quotePowerShellSingle(value) {
3021
+ return `'${String(value).replace(/'/g, "''")}'`;
3022
+ }
3023
+ const envAssignments = Object.entries(extraEnv)
3024
+ .map(([key, value]) => `$env:${key}=${quotePowerShellSingle(String(value))}`)
3025
+ .join('; ');
3026
+ return `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${envAssignments ? `${envAssignments}; ` : ''}Start-Process -WindowStyle Hidden -FilePath (Get-Command node).Source -ArgumentList @('${targetPath.replace(/'/g, "''")}')"`;
3027
+ }
3028
+
3029
+ // ─── Shared initializer (provider install) ───────────────────────────────
3030
+ function providerLines(arr, shell) {
3031
+ if (is9Router) {
3032
+ if (shell === 'bat') {
2997
3033
  arr.push('call npm install -g 9router || goto :fail');
2998
- arr.push('start "9Router" cmd /k "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
3034
+ arr.push(`for /f "usebackq delims=" %%I in (\`${native9RouterServerEntryLookup()}\`) do set "NINE_ROUTER_ENTRY=%%I"`);
3035
+ arr.push(windowsHiddenNodeLaunch('%NINE_ROUTER_ENTRY%', { PORT: '20128', HOSTNAME: '0.0.0.0', DATA_DIR: '%DATA_DIR%' }));
2999
3036
  arr.push('timeout /t 5 /nobreak >nul');
3000
- } else {
3001
- arr.push('npm install -g 9router');
3002
- arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 node "$(npm root -g)/9router/app/server.js" >/tmp/9router.log 2>&1 &');
3003
- arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
3037
+ } else {
3038
+ arr.push('npm install -g 9router');
3039
+ arr.push(`NINE_ROUTER_ENTRY="$(${native9RouterServerEntryLookup()})"`);
3040
+ arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 DATA_DIR="$PWD/.9router" node "$NINE_ROUTER_ENTRY" >/tmp/9router.log 2>&1 &');
3041
+ arr.push('nohup env DATA_DIR="$PWD/.9router" node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
3004
3042
  arr.push('sleep 3');
3005
3043
  }
3006
3044
  } else if (isOllama) {
@@ -3061,7 +3099,7 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
3061
3099
  order: { ollama: ['ollama:default'] },
3062
3100
  };
3063
3101
  } else {
3064
- const authProviderName = provider.isProxy ? '9router' : provider.id;
3102
+ const authProviderName = provider.isProxy ? '9router' : state.config.provider;
3065
3103
  const authProfileId = provider.isProxy ? '9router-proxy' : `${authProviderName}:default`;
3066
3104
  const authKeyValue = provider.isProxy
3067
3105
  ? 'sk-no-key'
@@ -3117,8 +3155,8 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
3117
3155
  list: multiBotAgentMetas.map((meta) => ({
3118
3156
  id: meta.agentId,
3119
3157
  name: meta.name,
3120
- workspace: `./.openclaw/${meta.workspaceDir}`,
3121
- agentDir: `./.openclaw/agents/${meta.agentId}/agent`,
3158
+ workspace: `${nativeProjectOpenClawRoot}/${meta.workspaceDir}`,
3159
+ agentDir: `${nativeProjectOpenClawRoot}/agents/${meta.agentId}/agent`,
3122
3160
  model: { primary: state.config.model, fallbacks: [] },
3123
3161
  })),
3124
3162
  },
@@ -3160,14 +3198,15 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
3160
3198
  'telegram-multibot-relay': { enabled: true },
3161
3199
  },
3162
3200
  },
3163
- gateway: {
3164
- port: 18791,
3165
- mode: 'local',
3166
- bind: '0.0.0.0',
3167
- controlUi: {
3168
- allowedOrigins: getGatewayAllowedOrigins(18791),
3169
- },
3170
- auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
3201
+ gateway: {
3202
+ port: 18791,
3203
+ mode: 'local',
3204
+ bind: 'custom',
3205
+ customBindHost: '0.0.0.0',
3206
+ controlUi: {
3207
+ allowedOrigins: getGatewayAllowedOrigins(18791),
3208
+ },
3209
+ auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
3171
3210
  },
3172
3211
  };
3173
3212
  return JSON.stringify(cfg, null, 2);
@@ -3253,44 +3292,116 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
3253
3292
  }
3254
3293
 
3255
3294
  // ─── Per-bot openclaw.json (minimal β€” shared workspace) ──────────────────
3256
- function botConfigContent(botIndex) {
3257
- const bot = state.bots[botIndex] || {};
3258
- const botName = bot.name || `Bot ${botIndex + 1}`;
3259
- const agentId = botName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
3260
- const basePort = 18791 + botIndex;
3261
- const groupId = state.groupId || '';
3262
- const channelConfig = JSON.parse(JSON.stringify(ch.channelConfig || {}));
3263
- if (state.channel === 'telegram' && isMultiBot) {
3264
- channelConfig.groupPolicy = groupId ? 'allowlist' : 'open';
3265
- channelConfig.groupAllowFrom = ['*'];
3266
- channelConfig.groups = {
3267
- [groupId || '*']: {
3268
- enabled: true,
3269
- requireMention: false,
3270
- },
3271
- };
3272
- }
3273
- const cfg = {
3274
- meta: { lastTouchedVersion: '2026.3.24' },
3275
- agents: {
3276
- defaults: { model: { primary: bot.model || state.config.model }, compaction: { mode: 'safeguard' }, timeoutSeconds: 120 },
3277
- list: [{ id: agentId, model: { primary: bot.model || state.config.model } }],
3278
- },
3279
- commands: { native: 'auto', nativeSkills: 'auto', restart: true },
3280
- channels: channelConfig,
3281
- gateway: {
3282
- port: basePort,
3283
- mode: 'local',
3284
- bind: '0.0.0.0',
3285
- controlUi: {
3286
- allowedOrigins: getGatewayAllowedOrigins(basePort),
3287
- },
3288
- auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
3289
- },
3290
-
3291
- };
3292
- return JSON.stringify(cfg, null, 2);
3293
- }
3295
+ function botConfigContent(botIndex) {
3296
+ const bot = state.bots[botIndex] || {};
3297
+ const botName = bot.name || `Bot ${botIndex + 1}`;
3298
+ const agentId = botName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
3299
+ const basePort = 18791 + botIndex;
3300
+ const groupId = state.groupId || '';
3301
+ const botProvider = PROVIDERS[bot.provider] || provider;
3302
+ const cfg = {
3303
+ meta: { lastTouchedVersion: '2026.3.24' },
3304
+ agents: {
3305
+ defaults: {
3306
+ model: { primary: bot.model || state.config.model },
3307
+ compaction: { mode: 'safeguard' },
3308
+ timeoutSeconds: botProvider.isLocal ? 900 : 120,
3309
+ ...(botProvider.isLocal ? { llm: { idleTimeoutSeconds: 300 } } : {}),
3310
+ },
3311
+ list: [{ id: agentId, model: { primary: bot.model || state.config.model } }],
3312
+ },
3313
+ ...(botProvider.isProxy ? {
3314
+ models: {
3315
+ mode: 'merge',
3316
+ providers: {
3317
+ '9router': {
3318
+ baseUrl: 'http://localhost:20128/v1',
3319
+ apiKey: 'sk-no-key',
3320
+ api: 'openai-completions',
3321
+ models: [
3322
+ {
3323
+ id: 'smart-route',
3324
+ name: 'Smart Proxy (Auto Route)',
3325
+ contextWindow: 200000,
3326
+ maxTokens: 8192,
3327
+ }
3328
+ ]
3329
+ }
3330
+ }
3331
+ }
3332
+ } : {}),
3333
+ ...(botProvider.isLocal ? {
3334
+ models: {
3335
+ providers: {
3336
+ ollama: {
3337
+ baseUrl: 'http://localhost:11434',
3338
+ apiKey: 'ollama-local',
3339
+ api: 'ollama',
3340
+ models: [
3341
+ { id: selectedModel, name: selectedModel, contextWindow: 128000, maxTokens: 8192 }
3342
+ ]
3343
+ }
3344
+ }
3345
+ }
3346
+ } : {}),
3347
+ commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
3348
+ channels: {},
3349
+ tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
3350
+ gateway: {
3351
+ port: basePort,
3352
+ mode: 'local',
3353
+ bind: 'custom',
3354
+ customBindHost: '0.0.0.0',
3355
+ controlUi: {
3356
+ allowedOrigins: getGatewayAllowedOrigins(basePort),
3357
+ },
3358
+ auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
3359
+ },
3360
+ };
3361
+
3362
+ if (hasBrowser) {
3363
+ cfg.browser = { enabled: true };
3364
+ }
3365
+
3366
+ const skillEntries = {};
3367
+ state.config.skills.forEach((sid) => {
3368
+ const skill = SKILLS.find((s) => s.id === sid);
3369
+ if (!skill) return;
3370
+ if (skill.id === 'scheduler' || skill.slug === 'browser-automation' || !skill.slug) return;
3371
+ skillEntries[skill.slug] = { enabled: true };
3372
+ });
3373
+ if (Object.keys(skillEntries).length > 0) {
3374
+ cfg.skills = { entries: skillEntries };
3375
+ }
3376
+
3377
+ if (state.channel === 'telegram') {
3378
+ cfg.channels.telegram = {
3379
+ enabled: true,
3380
+ dmPolicy: 'open',
3381
+ allowFrom: ['*'],
3382
+ };
3383
+ if (isMultiBot) {
3384
+ cfg.channels.telegram.groupPolicy = groupId ? 'allowlist' : 'open';
3385
+ cfg.channels.telegram.groupAllowFrom = ['*'];
3386
+ cfg.channels.telegram.groups = {
3387
+ [groupId || '*']: {
3388
+ enabled: true,
3389
+ requireMention: false,
3390
+ },
3391
+ };
3392
+ }
3393
+ } else if (state.channel === 'zalo-personal') {
3394
+ cfg.channels.zalouser = {
3395
+ enabled: true,
3396
+ dmPolicy: 'open',
3397
+ autoReply: true,
3398
+ };
3399
+ } else if (state.channel === 'zalo-bot') {
3400
+ cfg.channels.zalo = { enabled: true, provider: 'official_account' };
3401
+ }
3402
+
3403
+ return JSON.stringify(cfg, null, 2);
3404
+ }
3294
3405
 
3295
3406
  function botAuthProfilesContent(botIndex) {
3296
3407
  const bot = state.bots[botIndex] || {};
@@ -3310,7 +3421,7 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
3310
3421
  order: { ollama: ['ollama:default'] },
3311
3422
  };
3312
3423
  } else {
3313
- const authProviderName = botProvider.isProxy ? '9router' : botProvider.id;
3424
+ const authProviderName = botProvider.isProxy ? '9router' : (bot.provider || state.config.provider);
3314
3425
  const authProfileId = botProvider.isProxy ? '9router-proxy' : `${authProviderName}:default`;
3315
3426
  const authKeyValue = botProvider.isProxy
3316
3427
  ? 'sk-no-key'
@@ -3566,18 +3677,29 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3566
3677
  .replace(/%/g, '%%');
3567
3678
  }
3568
3679
 
3569
- function appendBatWriteCommands(arr, files) {
3570
- Object.entries(files).forEach(([relPath, content]) => {
3571
- const winPath = relPath.replace(/\//g, '\\');
3572
- const dir = winPath.substring(0, winPath.lastIndexOf('\\'));
3573
- if (dir) arr.push(`if not exist "${dir}" mkdir "${dir}"`);
3680
+ function appendBatWriteCommands(arr, files) {
3681
+ Object.entries(files).forEach(([relPath, content]) => {
3682
+ const winPath = relPath.replace(/\//g, '\\');
3683
+ const dir = winPath.substring(0, winPath.lastIndexOf('\\'));
3684
+ if (dir) arr.push(`if not exist "${dir}" mkdir "${dir}"`);
3574
3685
  arr.push(`> "${winPath}" (`);
3575
3686
  content.split('\n').forEach((line) => {
3576
3687
  arr.push(line.length ? `echo(${batEscapeEchoLine(line)}` : 'echo(');
3577
3688
  });
3578
- arr.push(')');
3579
- });
3580
- }
3689
+ arr.push(')');
3690
+ });
3691
+ }
3692
+
3693
+ function mapWindowsNativeFiles(files) {
3694
+ return Object.fromEntries(Object.entries(files).map(([relPath, content]) => {
3695
+ const normalized = relPath.replace(/\\/g, '/');
3696
+ if (normalized === '.env') return ['%PROJECT_DIR%\\.env', content];
3697
+ if (normalized.startsWith('.openclaw/')) {
3698
+ return [`%OPENCLAW_HOME%\\${normalized.slice('.openclaw/'.length).replace(/\//g, '\\')}`, content];
3699
+ }
3700
+ return [`%PROJECT_DIR%\\${normalized.replace(/\//g, '\\')}`, content];
3701
+ }));
3702
+ }
3581
3703
 
3582
3704
  let scriptContent = '';
3583
3705
  let scriptName = '';
@@ -3586,30 +3708,68 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3586
3708
  if (state.nativeOs === 'win') {
3587
3709
  const isDocker = state.deployMode === 'docker';
3588
3710
  scriptName = isDocker ? 'setup-openclaw-docker-win.bat' : 'setup-openclaw-win.bat';
3589
- const lines = [
3711
+ const lines = [
3590
3712
  '@echo off',
3591
3713
  'setlocal EnableExtensions',
3592
3714
  'chcp 65001 >nul',
3593
- `echo === OpenClaw Setup β€” Windows${isDocker ? ' Docker' : ' Native'} ===`,
3594
- 'echo.',
3595
- 'echo [1/5] Kiem tra Node.js...',
3715
+ `set "PROJECT_DIR=${projectDir.replace(/\//g, '\\')}"`,
3716
+ 'if not exist "%PROJECT_DIR%" mkdir "%PROJECT_DIR%"',
3717
+ 'cd /d "%PROJECT_DIR%"',
3718
+ 'set "OPENCLAW_HOME=%PROJECT_DIR%\\.openclaw"',
3719
+ 'set "OPENCLAW_STATE_DIR=%PROJECT_DIR%\\.openclaw"',
3720
+ 'set "DATA_DIR=%PROJECT_DIR%\\.9router"',
3721
+ 'set "PATH=%APPDATA%\\npm;%PATH%"',
3722
+ `echo === OpenClaw Setup β€” Windows${isDocker ? ' Docker' : ' Native'} ===`,
3723
+ 'echo.',
3724
+ 'echo [1/5] Kiem tra Node.js...',
3596
3725
  'where node >nul 2>&1 || (echo ERROR: Node.js chua cai! Tai tai: https://nodejs.org && pause && exit /b 1)',
3597
3726
  'echo [2/5] Cai OpenClaw CLI...',
3598
3727
  'call npm install -g openclaw@2026.4.5 || goto :fail',
3599
- ];
3600
- providerLines(lines, 'bat');
3601
- if (pluginCmd) { lines.push('echo Cai plugins...'); lines.push(pluginCmd); }
3602
-
3728
+ ];
3729
+ providerLines(lines, 'bat');
3730
+ if (hasBrowser) {
3731
+ lines.push('echo Cai Browser Automation runtime...');
3732
+ lines.push('call npm install -g agent-browser playwright || goto :fail');
3733
+ lines.push('call npx playwright install chromium || goto :fail');
3734
+ }
3735
+ if (nativeSkillInstallCmds.length > 0) {
3736
+ lines.push('echo Cai skills...');
3737
+ lines.push(...nativeSkillInstallCmds);
3738
+ }
3739
+ if (pluginCmd) { lines.push('echo Cai plugins...'); lines.push(pluginCmd); }
3740
+ lines.push('if not exist "%OPENCLAW_HOME%" mkdir "%OPENCLAW_HOME%"');
3741
+ lines.push('if not exist "%DATA_DIR%" mkdir "%DATA_DIR%"');
3742
+
3603
3743
  if (isMultiBot) {
3604
3744
  lines.push('echo [4/5] Tao runtime multi-agent dung chung...');
3605
- appendBatWriteCommands(lines, sharedNativeFileMap());
3606
- if (is9Router) lines.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
3745
+ appendBatWriteCommands(lines, mapWindowsNativeFiles(sharedNativeFileMap()));
3746
+ if (is9Router) lines.push(windowsHiddenNodeLaunch('%OPENCLAW_HOME%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
3747
+ lines.push('if not exist "%OPENCLAW_HOME%\\openclaw.json" (echo ERROR: Khong tim thay "%OPENCLAW_HOME%\\openclaw.json" && goto :fail)');
3748
+ lines.push('echo.');
3749
+ lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
3750
+ lines.push('echo Other reachable URLs: http://localhost:18791');
3751
+ lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
3752
+ if (is9Router) {
3753
+ lines.push('echo.');
3754
+ lines.push('echo 9Router Dashboard: http://127.0.0.1:20128/dashboard');
3755
+ lines.push('echo Other reachable URLs: http://localhost:20128/dashboard');
3756
+ }
3607
3757
  lines.push('echo [5/5] Khoi dong gateway multi-bot...');
3608
3758
  lines.push('call openclaw gateway run');
3609
3759
  } else {
3610
3760
  lines.push('echo [4/5] Tao file cau hinh...');
3611
- appendBatWriteCommands(lines, botFiles(0));
3612
- if (is9Router) lines.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
3761
+ appendBatWriteCommands(lines, mapWindowsNativeFiles(botFiles(0)));
3762
+ if (is9Router) lines.push(windowsHiddenNodeLaunch('%OPENCLAW_HOME%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
3763
+ lines.push('if not exist "%OPENCLAW_HOME%\\openclaw.json" (echo ERROR: Khong tim thay "%OPENCLAW_HOME%\\openclaw.json" && goto :fail)');
3764
+ lines.push('echo.');
3765
+ lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
3766
+ lines.push('echo Other reachable URLs: http://localhost:18791');
3767
+ lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
3768
+ if (is9Router) {
3769
+ lines.push('echo.');
3770
+ lines.push('echo 9Router Dashboard: http://127.0.0.1:20128/dashboard');
3771
+ lines.push('echo Other reachable URLs: http://localhost:20128/dashboard');
3772
+ }
3613
3773
  lines.push('echo [5/5] Khoi dong bot...');
3614
3774
  lines.push('call openclaw gateway run');
3615
3775
  }
@@ -3652,17 +3812,23 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3652
3812
  // ── macOS Native mode: same approach as Ubuntu but no PM2, no apt ────────
3653
3813
  // Do NOT use 'npm config set prefix' on macOS β€” breaks Homebrew Node.
3654
3814
  // Use export npm_config_prefix per-session + sudo fallback.
3655
- const sh = [
3656
- '#!/usr/bin/env bash', 'set -e',
3657
- 'echo "=== OpenClaw Setup \u2014 macOS Native ==="',
3658
- 'command -v node > /dev/null 2>&1 || { echo "ERROR: Node.js chua cai! https://nodejs.org"; exit 1; }',
3815
+ const sh = [
3816
+ '#!/usr/bin/env bash', 'set -e',
3817
+ 'echo "=== OpenClaw Setup \u2014 macOS Native ==="',
3818
+ 'command -v node > /dev/null 2>&1 || { echo "ERROR: Node.js chua cai! https://nodejs.org"; exit 1; }',
3659
3819
  '# User-local npm prefix (Homebrew-safe β€” no global npmrc mutation)',
3660
- 'mkdir -p "$HOME/.local/bin"',
3661
- 'export npm_config_prefix="$HOME/.local"',
3662
- 'export PATH="$HOME/.local/bin:$PATH"',
3663
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.zshrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.zshrc"',
3664
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3665
- '# Install openclaw (user-local first, sudo fallback)',
3820
+ 'mkdir -p "$HOME/.local/bin"',
3821
+ 'export npm_config_prefix="$HOME/.local"',
3822
+ 'export PATH="$HOME/.local/bin:$PATH"',
3823
+ `PROJECT_DIR="${projectDir.replace(/"/g, '\\"')}"`,
3824
+ 'mkdir -p "$PROJECT_DIR"',
3825
+ 'cd "$PROJECT_DIR"',
3826
+ 'export OPENCLAW_HOME="$PROJECT_DIR/.openclaw"',
3827
+ 'export OPENCLAW_STATE_DIR="$PROJECT_DIR/.openclaw"',
3828
+ 'export DATA_DIR="$PROJECT_DIR/.9router"',
3829
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.zshrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.zshrc"',
3830
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3831
+ '# Install openclaw (user-local first, sudo fallback)',
3666
3832
  'npm install -g openclaw@2026.4.5 || sudo npm install -g openclaw@2026.4.5',
3667
3833
  ];
3668
3834
  providerLines(sh, 'sh');
@@ -3682,19 +3848,25 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3682
3848
  // ─── VPS/Ubuntu PM2 .SH ──────────────────────────────────────────────────
3683
3849
  } else if (state.nativeOs === 'vps') {
3684
3850
  scriptName = 'setup-openclaw-vps.sh';
3685
- const vps = [
3686
- '#!/usr/bin/env bash', 'set -e',
3851
+ const vps = [
3852
+ '#!/usr/bin/env bash', 'set -e',
3687
3853
  `echo "=== OpenClaw Setup β€” Ubuntu/VPS${isMultiBot ? ` Multi-Bot (${state.botCount} bots)` : ''} ==="`,
3688
3854
  '# Auto-install Node.js 20 LTS if missing',
3689
3855
  'if ! command -v node > /dev/null 2>&1; then',
3690
3856
  ' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
3691
3857
  ' sudo apt-get install -y nodejs',
3692
3858
  'fi',
3693
- 'mkdir -p "$HOME/.local/bin"',
3694
- 'npm config set prefix "$HOME/.local"',
3695
- 'export PATH="$HOME/.local/bin:$PATH"',
3696
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3697
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3859
+ 'mkdir -p "$HOME/.local/bin"',
3860
+ 'npm config set prefix "$HOME/.local"',
3861
+ 'export PATH="$HOME/.local/bin:$PATH"',
3862
+ `PROJECT_DIR="${projectDir.replace(/"/g, '\\"')}"`,
3863
+ 'mkdir -p "$PROJECT_DIR"',
3864
+ 'cd "$PROJECT_DIR"',
3865
+ 'export OPENCLAW_HOME="$PROJECT_DIR/.openclaw"',
3866
+ 'export OPENCLAW_STATE_DIR="$PROJECT_DIR/.openclaw"',
3867
+ 'export DATA_DIR="$PROJECT_DIR/.9router"',
3868
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3869
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3698
3870
  'npm install -g openclaw@2026.4.5 pm2@latest',
3699
3871
  ];
3700
3872
  providerLines(vps, 'sh');
@@ -3705,7 +3877,8 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3705
3877
  appendShWriteCommands(vps, sharedNativeFileMap());
3706
3878
  vps.push('echo "--- Starting shared gateway via PM2 ---"');
3707
3879
  if (is9Router) {
3708
- vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-multibot-9router --interpreter "$(command -v node)"');
3880
+ vps.push(`NINE_ROUTER_ENTRY="$(${native9RouterServerEntryLookup()})"`);
3881
+ vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$NINE_ROUTER_ENTRY" --name openclaw-multibot-9router --interpreter "$(command -v node)"');
3709
3882
  vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
3710
3883
  }
3711
3884
  vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
@@ -3718,7 +3891,8 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3718
3891
  } else {
3719
3892
  appendShWriteCommands(vps, botFiles(0));
3720
3893
  if (is9Router) {
3721
- vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-9router --interpreter "$(command -v node)"');
3894
+ vps.push(`NINE_ROUTER_ENTRY="$(${native9RouterServerEntryLookup()})"`);
3895
+ vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$NINE_ROUTER_ENTRY" --name openclaw-9router --interpreter "$(command -v node)"');
3722
3896
  vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
3723
3897
  }
3724
3898
  vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
@@ -3737,11 +3911,17 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3737
3911
  ' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
3738
3912
  ' sudo apt-get install -y nodejs',
3739
3913
  'fi',
3740
- 'mkdir -p "$HOME/.local/bin"',
3741
- 'npm config set prefix "$HOME/.local"',
3742
- 'export PATH="$HOME/.local/bin:$PATH"',
3743
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3744
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3914
+ 'mkdir -p "$HOME/.local/bin"',
3915
+ 'npm config set prefix "$HOME/.local"',
3916
+ 'export PATH="$HOME/.local/bin:$PATH"',
3917
+ `PROJECT_DIR="${projectDir.replace(/"/g, '\\"')}"`,
3918
+ 'mkdir -p "$PROJECT_DIR"',
3919
+ 'cd "$PROJECT_DIR"',
3920
+ 'export OPENCLAW_HOME="$PROJECT_DIR/.openclaw"',
3921
+ 'export OPENCLAW_STATE_DIR="$PROJECT_DIR/.openclaw"',
3922
+ 'export DATA_DIR="$PROJECT_DIR/.9router"',
3923
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3924
+ 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3745
3925
  'npm install -g openclaw@2026.4.5',
3746
3926
  ];
3747
3927
  providerLines(lnx, 'sh');
@@ -3801,10 +3981,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3801
3981
 
3802
3982
 
3803
3983
 
3804
- window.downloadNativeScript = function() {
3805
- const script = window._nativeScript;
3806
- if (!script) return;
3807
- const blob = new Blob([script.content], { type: 'text/plain;charset=utf-8' });
3984
+ window.downloadNativeScript = function() {
3985
+ // Regenerate output first so the downloaded script always matches the latest wizard state.
3986
+ generateOutput();
3987
+ const script = window._nativeScript;
3988
+ if (!script) return;
3989
+ const blob = new Blob([script.content], { type: 'text/plain;charset=utf-8' });
3808
3990
  const url = URL.createObjectURL(blob);
3809
3991
  const a = document.createElement('a');
3810
3992
  a.href = url; a.download = script.name; a.style.display = 'none';