create-openclaw-bot 5.1.12 → 5.1.14

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
@@ -656,10 +656,12 @@
656
656
  if (labelEl) labelEl.style.display = 'none';
657
657
  if (slashGroup) slashGroup.style.display = 'none';
658
658
 
659
- // Update fields
660
- const bot = state.bots[0] || { name: 'Bot 1', desc: '', persona: '', slashCmd: '' };
661
- document.getElementById('cfg-bot-tab-name').value = bot.name || '';
662
- document.getElementById('cfg-bot-tab-desc').value = bot.desc || '';
659
+ // Restore single-bot fields — fall back to state.config.botName so Next button
660
+ // is never falsely disabled just because state.bots[0].name is empty yet.
661
+ const bot = state.bots[0] || { name: '', desc: '', persona: '', slashCmd: '' };
662
+ const resolvedName = bot.name || state.config.botName || '';
663
+ document.getElementById('cfg-bot-tab-name').value = resolvedName;
664
+ document.getElementById('cfg-bot-tab-desc').value = bot.desc || state.config.description || '';
663
665
  document.getElementById('cfg-bot-tab-persona').value = bot.persona || '';
664
666
  return;
665
667
  }
@@ -690,9 +692,11 @@
690
692
  const nameEl = document.getElementById('cfg-bot-tab-name');
691
693
  const slashEl = document.getElementById('cfg-bot-tab-slash');
692
694
  const descEl = document.getElementById('cfg-bot-tab-desc');
695
+ const personaEl = document.getElementById('cfg-bot-tab-persona');
693
696
  if (nameEl) nameEl.value = bot.name || '';
694
697
  if (slashEl) slashEl.value = bot.slashCmd || '';
695
698
  if (descEl) descEl.value = bot.desc || '';
699
+ if (personaEl) personaEl.value = bot.persona || '';
696
700
 
697
701
  // Also sync global config fields from active bot (provider/model carry over)
698
702
  if (bot.provider) {
@@ -733,9 +737,11 @@
733
737
  const nameEl = document.getElementById('cfg-bot-tab-name');
734
738
  const slashEl = document.getElementById('cfg-bot-tab-slash');
735
739
  const descEl = document.getElementById('cfg-bot-tab-desc');
740
+ const personaEl = document.getElementById('cfg-bot-tab-persona');
736
741
  if (nameEl) bot.name = nameEl.value;
737
742
  if (slashEl) bot.slashCmd = slashEl.value;
738
743
  if (descEl) bot.desc = descEl.value;
744
+ if (personaEl) bot.persona = personaEl.value;
739
745
  }
740
746
 
741
747
  window.__saveBotTabName = function(val) {
@@ -761,6 +767,12 @@
761
767
  }
762
768
  };
763
769
 
770
+ window.__saveBotTabPersona = function(val) {
771
+ if (state.bots[state.activeBotIndex]) {
772
+ state.bots[state.activeBotIndex].persona = val;
773
+ }
774
+ };
775
+
764
776
 
765
777
 
766
778
  // ========== Step 1: Deploy Mode + OS ==========
@@ -792,8 +804,8 @@
792
804
  icon: '🐧',
793
805
  titleVi: 'Ubuntu / VPS — Khuyên dùng Native (Không Docker)',
794
806
  titleEn: 'Ubuntu / VPS — Recommended: Native (No Docker)',
795
- descVi: 'Chạy thẳng trên máy, tiết kiệm RAM, khởi động nhanh. Script tự cài Node.js 20 LTS, OpenClaw CLI, PM2, 9Router/Ollama và giữ bot chạy liên tục sau reboot.',
796
- descEn: 'Run directly on machine — lower RAM, faster startup. Script auto-installs Node.js 20 LTS, OpenClaw CLI, PM2, 9Router/Ollama and keeps bot running across reboots.',
807
+ descVi: 'Chạy thẳng trên máy, tiết kiệm RAM, khởi động nhanh. Script tự cài Node.js 20 LTS, OpenClaw CLI, PM2, 9Router/Ollama và giữ bot chạy liên tục sau reboot. Tạm tránh Node 25.',
808
+ descEn: 'Run directly on machine — lower RAM, faster startup. Script auto-installs Node.js 20 LTS, OpenClaw CLI, PM2, 9Router/Ollama and keeps bot running across reboots. Avoid Node 25 for now.',
797
809
  deploy: 'native',
798
810
  badgeVi: '💻 Native + PM2',
799
811
  badgeEn: '💻 Native + PM2',
@@ -803,8 +815,8 @@
803
815
  icon: '🖥️',
804
816
  titleVi: 'Linux Desktop — Khuyên dùng Native',
805
817
  titleEn: 'Linux Desktop — Recommended: Native',
806
- descVi: 'Không cần Docker. Script tự cài Node.js 20 LTS nếu chưa có, cài OpenClaw CLI, rồi cài 9Router hoặc Ollama theo provider bạn chọn và khởi động bot ngay.',
807
- descEn: 'No Docker needed. Script auto-installs Node.js 20 LTS if missing, installs OpenClaw CLI, then installs 9Router or Ollama based on your provider choice and starts the bot immediately.',
818
+ descVi: 'Không cần Docker. Script tự cài Node.js 20 LTS nếu chưa có, cài OpenClaw CLI, rồi cài 9Router hoặc Ollama theo provider bạn chọn và khởi động bot ngay. Tạm tránh Node 25.',
819
+ descEn: 'No Docker needed. Script auto-installs Node.js 20 LTS if missing, installs OpenClaw CLI, then installs 9Router or Ollama based on your provider choice and starts the bot immediately. Avoid Node 25 for now.',
808
820
  deploy: 'native',
809
821
  badgeVi: '💻 Native',
810
822
  badgeEn: '💻 Native',
@@ -976,12 +988,17 @@
976
988
  if (state.currentStep === 3) {
977
989
  if (state.botCount > 1) {
978
990
  // Multi-bot: require name for the currently active bot tab
979
- const tabNameVal = document.getElementById('cfg-bot-tab-name')?.value?.trim();
991
+ // Fallback to state.bots to handle re-render cases where DOM may not yet have the value
992
+ const activeTab = state._activeBotTab || 0;
993
+ const tabNameVal = document.getElementById('cfg-bot-tab-name')?.value?.trim()
994
+ || state.bots[activeTab]?.name?.trim();
980
995
  if (!tabNameVal) isDisabled = true;
981
996
  } else {
982
997
  // Single bot: require cfg-name or the shared tab name field
998
+ // Fallback to state.config.botName for cases where the DOM field was cleared on re-render
983
999
  const nameVal = document.getElementById('cfg-name')?.value?.trim()
984
- || document.getElementById('cfg-bot-tab-name')?.value?.trim();
1000
+ || document.getElementById('cfg-bot-tab-name')?.value?.trim()
1001
+ || state.config.botName?.trim();
985
1002
  if (!nameVal) isDisabled = true;
986
1003
  }
987
1004
  }
@@ -1128,6 +1145,17 @@
1128
1145
  prompt.value = DEFAULT_PROMPTS[lang].replace('{BOT_NAME}', nameVal).replace('{BOT_DESC}', descVal);
1129
1146
  autoExpand(prompt);
1130
1147
  }
1148
+ // Sync single-bot name to state + re-check Next button
1149
+ if (e.target.id === 'cfg-name') {
1150
+ state.config.botName = e.target.value;
1151
+ if (state.bots[0]) state.bots[0].name = e.target.value;
1152
+ }
1153
+ updateNavButtons();
1154
+ }
1155
+ // Also re-check Next when typing directly in the tab name field
1156
+ if (e.target.id === 'cfg-bot-tab-name') {
1157
+ if (state.bots[state.activeBotIndex]) state.bots[state.activeBotIndex].name = e.target.value;
1158
+ updateNavButtons();
1131
1159
  }
1132
1160
  if (e.target.id === 'cfg-prompt') {
1133
1161
  e.target.dataset.userEdited = 'true';
@@ -1227,6 +1255,12 @@
1227
1255
  state.config.systemPrompt = document.getElementById('cfg-prompt')?.value || state.config.systemPrompt || DEFAULT_PROMPTS['vi'];
1228
1256
  state.config.userInfo = document.getElementById('cfg-user-info')?.value?.trim() || state.config.userInfo || '';
1229
1257
  state.config.securityRules = document.getElementById('cfg-security')?.value || state.config.securityRules || DEFAULT_SECURITY_RULES['vi'];
1258
+ // 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
+ }
1230
1264
  }
1231
1265
 
1232
1266
  // Save Step 4 credential inputs to state (persists across Back navigation)
@@ -1906,7 +1940,7 @@ model:
1906
1940
  ? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 & '
1907
1941
  : '';
1908
1942
  // Patch config on every startup to keep gateway settings stable
1909
- const patchCmd = `node -e \\"const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add(\\\`http://\\\${entry.address}:18791\\\`);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
1943
+ const patchCmd = `node -e \\"const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://' + entry.address + ':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
1910
1944
  // Auto-approve device pairing after gateway starts (required since v2026.3.x)
1911
1945
  const autoApproveCmd = '(while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done) & ';
1912
1946
  const finalCmd = `CMD sh -c "${pluginInstallCmd}${patchCmd}${browserPrefix}${autoApproveCmd}${gatewayCmd}"`;
@@ -1917,7 +1951,7 @@ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /v
1917
1951
 
1918
1952
 
1919
1953
  ARG CACHEBUST=${Date.now()}
1920
- RUN npm install -g openclaw@latest${skillLines}${browserInstallLines}
1954
+ RUN npm install -g openclaw@2026.4.5 grammy${skillLines}${browserInstallLines}
1921
1955
  RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const files=fs.readdirSync(dir).filter(n=>/\\.js$/.test(n));let patched=0;for(const file of files){const p=path.join(dir,file);let s='';try{s=fs.readFileSync(p,'utf8');}catch{continue;}if(s.includes(to)||!s.includes(from))continue;s=s.replace(from,to);fs.writeFileSync(p,s);patched++;}if(!patched){process.exit(0);}"
1922
1956
  WORKDIR /root/.openclaw
1923
1957
 
@@ -2945,25 +2979,24 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
2945
2979
  if (p) allPlugins.push(p.package);
2946
2980
  });
2947
2981
  if (isMultiBot && state.channel === 'telegram') allPlugins.push(relayPluginSpec);
2948
- const pluginCmd = allPlugins.length > 0 ? ('npm exec openclaw plugins install ' + allPlugins.join(' ')) : '';
2949
-
2950
- function native9RouterSyncScriptContent() {
2951
- return `const fs=require('fs');
2952
- const path=require('path');
2953
- const INTERVAL=30000;
2954
- const p=path.join(process.env.HOME||process.env.USERPROFILE||'.','.9router','db.json');
2955
- 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']};
2956
- 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);`;
2957
- }
2982
+ const pluginCmd = allPlugins.length > 0 ? ('call npm exec -- openclaw plugins install ' + allPlugins.join(' ') + ' || goto :fail') : '';
2983
+
2984
+ function native9RouterSyncScriptContent() {
2985
+ return `const fs=require('fs');
2986
+ const path=require('path');
2987
+ const INTERVAL=30000;
2988
+ const p=path.join(process.env.HOME||process.env.USERPROFILE||'.','.9router','db.json');
2989
+ 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);`;
2991
+ }
2958
2992
 
2959
2993
  // ─── Shared initializer (provider install) ───────────────────────────────
2960
2994
  function providerLines(arr, shell) {
2961
2995
  if (is9Router) {
2962
2996
  if (shell === 'bat') {
2963
- arr.push('npm install -g 9router');
2964
- arr.push('start "9Router" cmd /k "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
2965
- arr.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
2966
- arr.push('timeout /t 5 /nobreak >nul');
2997
+ 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"');
2999
+ arr.push('timeout /t 5 /nobreak >nul');
2967
3000
  } else {
2968
3001
  arr.push('npm install -g 9router');
2969
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 &');
@@ -3554,60 +3587,97 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3554
3587
  const isDocker = state.deployMode === 'docker';
3555
3588
  scriptName = isDocker ? 'setup-openclaw-docker-win.bat' : 'setup-openclaw-win.bat';
3556
3589
  const lines = [
3557
- '@echo off',
3558
- 'chcp 65001 >nul',
3590
+ '@echo off',
3591
+ 'setlocal EnableExtensions',
3592
+ 'chcp 65001 >nul',
3559
3593
  `echo === OpenClaw Setup — Windows${isDocker ? ' Docker' : ' Native'} ===`,
3560
3594
  'echo.',
3561
3595
  'echo [1/5] Kiem tra Node.js...',
3562
3596
  'where node >nul 2>&1 || (echo ERROR: Node.js chua cai! Tai tai: https://nodejs.org && pause && exit /b 1)',
3563
3597
  'echo [2/5] Cai OpenClaw CLI...',
3564
- 'npm install -g openclaw@latest',
3598
+ 'call npm install -g openclaw@2026.4.5 || goto :fail',
3565
3599
  ];
3566
3600
  providerLines(lines, 'bat');
3567
3601
  if (pluginCmd) { lines.push('echo Cai plugins...'); lines.push(pluginCmd); }
3568
3602
 
3569
- if (isMultiBot) {
3570
- lines.push('echo [4/5] Tao runtime multi-agent dung chung...');
3571
- appendBatWriteCommands(lines, sharedNativeFileMap());
3572
- lines.push('echo [5/5] Khoi dong gateway multi-bot...');
3573
- lines.push('openclaw gateway run');
3574
- } else {
3575
- lines.push('echo [4/5] Tao file cau hinh...');
3576
- appendBatWriteCommands(lines, botFiles(0));
3577
- lines.push('echo [5/5] Khoi dong bot...');
3578
- lines.push('openclaw gateway run');
3579
- }
3580
-
3581
- lines.push('pause');
3582
- scriptContent = lines.filter(Boolean).join('\r\n');
3603
+ if (isMultiBot) {
3604
+ 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"');
3607
+ lines.push('echo [5/5] Khoi dong gateway multi-bot...');
3608
+ lines.push('call openclaw gateway run');
3609
+ } else {
3610
+ 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"');
3613
+ lines.push('echo [5/5] Khoi dong bot...');
3614
+ lines.push('call openclaw gateway run');
3615
+ }
3616
+
3617
+ lines.push('goto :end');
3618
+ lines.push(':fail');
3619
+ lines.push('echo.');
3620
+ lines.push('echo Cai dat that bai. Kiem tra dong loi ngay phia tren.');
3621
+ lines.push('pause');
3622
+ lines.push('exit /b 1');
3623
+ lines.push(':end');
3624
+ lines.push('pause');
3625
+ lines.push('endlocal');
3626
+ scriptContent = lines.filter(Boolean).join('\r\n');
3583
3627
 
3584
3628
  // ─── macOS .SH ───────────────────────────────────────────────────────────
3585
3629
  } else if (state.nativeOs === 'linux') {
3586
3630
  const isDocker = state.deployMode === 'docker';
3587
3631
  scriptName = isDocker ? 'setup-openclaw-docker-macos.sh' : 'setup-openclaw-macos.sh';
3588
- const sh = [
3589
- '#!/usr/bin/env bash', 'set -e',
3590
- `echo "=== OpenClaw Setup — macOS${isDocker ? ' Docker' : ' Native'} ==="`,
3591
- 'command -v node > /dev/null 2>&1 || { echo "ERROR: Node.js chua cai! https://nodejs.org"; exit 1; }',
3592
- 'mkdir -p "$HOME/.local/bin"',
3593
- 'npm config set prefix "$HOME/.local"',
3594
- 'export PATH="$HOME/.local/bin:$PATH"',
3595
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.zshrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.zshrc"',
3596
- 'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3597
- 'npm install -g openclaw@latest',
3598
- ];
3599
- providerLines(sh, 'sh');
3600
- if (pluginCmd) sh.push(pluginCmd);
3601
3632
 
3602
- if (isMultiBot) {
3603
- appendShWriteCommands(sh, sharedNativeFileMap());
3604
- sh.push('echo "Starting shared multi-bot gateway..."');
3605
- sh.push('openclaw gateway run');
3606
- } else {
3633
+ if (isDocker) {
3634
+ // ── macOS Docker mode: write files then docker compose up ──────────────
3635
+ const sh = [
3636
+ '#!/usr/bin/env bash', 'set -e',
3637
+ 'echo "=== OpenClaw Setup \u2014 macOS Docker ==="',
3638
+ '# Check Docker Desktop is running',
3639
+ 'if ! docker info > /dev/null 2>&1; then',
3640
+ ' echo "\u274c Docker Desktop chua chay! Mo Docker Desktop roi chay lai script nay."',
3641
+ ' exit 1',
3642
+ 'fi',
3643
+ ];
3607
3644
  appendShWriteCommands(sh, botFiles(0));
3608
- sh.push('openclaw gateway run');
3645
+ sh.push('echo "Starting bot via Docker Compose..."');
3646
+ sh.push('if docker compose version > /dev/null 2>&1; then COMPOSE="docker compose"; else COMPOSE="docker-compose"; fi');
3647
+ sh.push('cd docker/openclaw');
3648
+ sh.push('$COMPOSE up --detach --build');
3649
+ sh.push('echo "\u2705 Bot dang chay via Docker. Xem log: docker logs -f openclaw-bot"');
3650
+ scriptContent = sh.filter(Boolean).join('\n');
3651
+ } else {
3652
+ // ── macOS Native mode: same approach as Ubuntu but no PM2, no apt ────────
3653
+ // Do NOT use 'npm config set prefix' on macOS — breaks Homebrew Node.
3654
+ // 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; }',
3659
+ '# 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)',
3666
+ 'npm install -g openclaw@2026.4.5 || sudo npm install -g openclaw@2026.4.5',
3667
+ ];
3668
+ providerLines(sh, 'sh');
3669
+ if (pluginCmd) sh.push(pluginCmd);
3670
+
3671
+ if (isMultiBot) {
3672
+ appendShWriteCommands(sh, sharedNativeFileMap());
3673
+ sh.push('echo "Starting shared multi-bot gateway..."');
3674
+ sh.push('openclaw gateway run');
3675
+ } else {
3676
+ appendShWriteCommands(sh, botFiles(0));
3677
+ sh.push('openclaw gateway run');
3678
+ }
3679
+ scriptContent = sh.filter(Boolean).join('\n');
3609
3680
  }
3610
- scriptContent = sh.filter(Boolean).join('\n');
3611
3681
 
3612
3682
  // ─── VPS/Ubuntu PM2 .SH ──────────────────────────────────────────────────
3613
3683
  } else if (state.nativeOs === 'vps') {
@@ -3625,7 +3695,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3625
3695
  'export PATH="$HOME/.local/bin:$PATH"',
3626
3696
  'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3627
3697
  'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3628
- 'npm install -g openclaw@latest pm2@latest',
3698
+ 'npm install -g openclaw@2026.4.5 pm2@latest',
3629
3699
  ];
3630
3700
  providerLines(vps, 'sh');
3631
3701
  if (pluginCmd) vps.push(pluginCmd);
@@ -3672,7 +3742,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3672
3742
  'export PATH="$HOME/.local/bin:$PATH"',
3673
3743
  'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.bashrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.bashrc"',
3674
3744
  'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
3675
- 'npm install -g openclaw@latest',
3745
+ 'npm install -g openclaw@2026.4.5',
3676
3746
  ];
3677
3747
  providerLines(lnx, 'sh');
3678
3748
  if (pluginCmd) lnx.push(pluginCmd);
@@ -3706,7 +3776,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
3706
3776
  if (stepsList) {
3707
3777
  const steps = [];
3708
3778
  steps.push(isVi ? '✅ Kiểm tra Node.js (cài tự động trên Ubuntu/VPS nếu chưa có)' : '✅ Check Node.js (auto-install on Ubuntu/VPS if missing)');
3709
- steps.push(isVi ? '📦 Cài OpenClaw CLI (<code>npm install -g openclaw@latest</code>)' : '📦 Install OpenClaw CLI (<code>npm install -g openclaw@latest</code>)');
3779
+ steps.push(isVi ? '📦 Cài OpenClaw CLI (<code>npm install -g openclaw@2026.4.5</code>)' : '📦 Install OpenClaw CLI (<code>npm install -g openclaw@2026.4.5</code>)');
3710
3780
  if (is9Router) {
3711
3781
  steps.push(isVi ? '🔀 Cài 9Router (<code>npm install -g 9router</code>) và khởi động tự động' : '🔀 Install 9Router (<code>npm install -g 9router</code>) and start automatically');
3712
3782
  } else if (isOllama) {
@@ -3957,14 +4027,14 @@ echo ""
3957
4027
 
3958
4028
  script += `# \${isVi ? 'Tạo thư mục' : 'Create directories'}\n`;
3959
4029
  Array.from(dirs).sort().forEach(dir => {
3960
- script += `mkdir -p "\${dir}"\n`;
4030
+ script += `mkdir -p "${dir}"\n`;
3961
4031
  });
3962
4032
  script += '\n';
3963
4033
 
3964
4034
  Object.entries(files).forEach(([path, content]) => {
3965
4035
  script += `# \${path}\n`;
3966
4036
  const contentStr = typeof content === 'string' ? content : '';
3967
- script += `cat > "\${path}" << 'CLAWEOF'\n`;
4037
+ script += `cat > "${path}" << 'CLAWEOF'\n`;
3968
4038
  script += contentStr;
3969
4039
  if (!contentStr.endsWith('\n')) script += '\n';
3970
4040
  script += `CLAWEOF\n\n`;
@@ -3975,6 +4045,7 @@ echo ""
3975
4045
  script += `echo ""\n`;
3976
4046
  script += `echo "\${isVi ? '🐳 Đang khởi động Docker (có thể mất vài phút)...' : '🐳 Starting Docker (may take a few minutes)...'}"\n`;
3977
4047
  script += `if docker compose version > /dev/null 2>&1; then\n COMPOSE_CMD="docker compose"\nelif docker-compose version > /dev/null 2>&1; then\n COMPOSE_CMD="docker-compose"\nelse\n echo "\${isVi ? '❌ Không tìm thấy Docker Compose! Cài bằng: sudo apt-get install docker-compose-plugin' : '❌ Docker Compose not found! Install: sudo apt-get install docker-compose-plugin'}"\n exit 1\nfi\n`;
4048
+ script += `# Check Docker daemon is actually running\nif ! docker info > /dev/null 2>&1; then\n echo "${isVi ? '❌ Docker daemon chưa chạy! Hãy mở Docker Desktop rồi chạy lại.' : '❌ Docker daemon is not running! Open Docker Desktop first, then re-run this script.'}"; exit 1\nfi\n`;
3978
4049
 
3979
4050
  if (isMultiBot) {
3980
4051
  script += `cd "docker/openclaw"\n`;
@@ -52,8 +52,8 @@ checks.push(() => expectMatch(
52
52
 
53
53
  checks.push(() => expectMatch(
54
54
  cli,
55
- /function installLatestOpenClaw\(\{ isVi, osChoice \}\) \{[\s\S]*installGlobalPackage\('openclaw@latest', \{ isVi, osChoice, displayName: 'openclaw' \}\)[\s\S]*process\.exit\(1\)/,
56
- 'CLI must provide a shared helper that always installs or upgrades openclaw@latest'
55
+ /function installLatestOpenClaw\(\{ isVi, osChoice \}\) \{[\s\S]*installGlobalPackage\(OPENCLAW_NPM_SPEC, \{ isVi, osChoice, displayName: 'openclaw' \}\)[\s\S]*process\.exit\(1\)/,
56
+ 'CLI must provide a shared helper that always installs or upgrades the pinned openclaw version'
57
57
  ));
58
58
 
59
59
  checks.push(() => expectMatch(
@@ -70,7 +70,7 @@ checks.push(() => expectMatch(
70
70
 
71
71
  checks.push(() => expectMatch(
72
72
  cli,
73
- /if \(!isOpenClawInstalled\(\)\) \{[\s\S]*installGlobalPackage\('openclaw@latest', \{ isVi, osChoice, displayName: 'openclaw' \}\)/,
73
+ /if \(!isOpenClawInstalled\(\)\) \{[\s\S]*installGlobalPackage\(OPENCLAW_NPM_SPEC, \{ isVi, osChoice, displayName: 'openclaw' \}\)/,
74
74
  'Native branch must auto-install openclaw'
75
75
  ));
76
76
 
@@ -216,6 +216,25 @@ checks.push(() => expectMatch(
216
216
  'Native per-bot gateway config must seed control UI allowed origins for each port'
217
217
  ));
218
218
 
219
+ checks.push(() => expectMatch(
220
+ cli,
221
+ /const dockerDir = path\.join\(projectDir, 'docker', 'openclaw'\);\s*await fs\.ensureDir\(dockerDir\);\s*await fs\.writeFile\(path\.join\(dockerDir, 'Dockerfile'\), dockerfile\);[\s\S]*await fs\.ensureDir\(dockerDir\);\s*await fs\.writeFile\(path\.join\(dockerDir, 'docker-compose\.yml'\), compose\);/s,
222
+ 'Docker CLI flow must ensure docker/openclaw exists immediately before writing Dockerfile and docker-compose.yml'
223
+ ));
224
+
225
+ checks.push(() => expectMatch(
226
+ cli,
227
+ /RUN npm install -g \$\{OPENCLAW_NPM_SPEC\} grammy/,
228
+ 'Docker CLI image must install grammy alongside openclaw so Telegram runtime dependencies resolve'
229
+ ));
230
+
231
+ checks.push(() => expect(
232
+ cli.includes("a.add('http://' + entry.address + ':18791')")
233
+ && cli.includes('allowedOrigins:Array.from(a).filter(Boolean)')
234
+ && !cli.includes("a.add(`http://${entry.address}:18791`)"),
235
+ 'Docker CLI patch script must avoid shell-expanding ${entry.address} and must filter null origins'
236
+ ));
237
+
219
238
  checks.push(() => expectMatch(
220
239
  cli,
221
240
  /channelKey === 'zalo-personal'\) \{\s*botConfig\.channels\['zalouser'\] = \{\s*enabled: true,\s*dmPolicy: 'open',\s*autoReply: true/s,
@@ -303,19 +322,32 @@ checks.push(() => expectMatch(
303
322
 
304
323
  checks.push(() => expectMatch(
305
324
  setup,
306
- /if \(state\.nativeOs === 'win'\) \{[\s\S]*scriptName = isDocker \? 'setup-openclaw-docker-win\.bat' : 'setup-openclaw-win\.bat';[\s\S]*npm install -g openclaw@latest[\s\S]*openclaw gateway run/s,
325
+ /if \(state\.nativeOs === 'win'\) \{[\s\S]*scriptName = isDocker \? 'setup-openclaw-docker-win\.bat' : 'setup-openclaw-win\.bat';[\s\S]*npm install -g openclaw@2026\.4\.5[\s\S]*openclaw gateway run/s,
307
326
  'Windows native/docker script generation must use the correct file name and start command'
308
327
  ));
309
328
 
310
329
  checks.push(() => expectMatch(
311
330
  setup,
312
- /else if \(state\.nativeOs === 'linux'\) \{[\s\S]*scriptName = isDocker \? 'setup-openclaw-docker-macos\.sh' : 'setup-openclaw-macos\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*npm install -g openclaw@latest[\s\S]*openclaw gateway run/s,
331
+ /else if \(state\.nativeOs === 'linux'\) \{[\s\S]*scriptName = isDocker \? 'setup-openclaw-docker-macos\.sh' : 'setup-openclaw-macos\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*npm install -g openclaw@2026\.4\.5[\s\S]*openclaw gateway run/s,
313
332
  'macOS script generation must use the correct file name and start command'
314
333
  ));
315
334
 
316
335
  checks.push(() => expectMatch(
317
336
  setup,
318
- /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*scriptName = 'setup-openclaw-vps\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*npm install -g openclaw@latest pm2@latest[\s\S]*pm2 save && pm2 startup/s,
337
+ /RUN npm install -g openclaw@2026\.4\.5 grammy/,
338
+ 'Wizard Dockerfile generation must install grammy alongside openclaw so Telegram runtime dependencies resolve'
339
+ ));
340
+
341
+ checks.push(() => expect(
342
+ setup.includes("a.add('http://' + entry.address + ':18791')")
343
+ && setup.includes('allowedOrigins:Array.from(a).filter(Boolean)')
344
+ && !setup.includes("a.add(\\`http://\\${entry.address}:18791\\`)"),
345
+ 'Wizard Docker patch command must avoid shell-expanding ${entry.address} and must filter null origins'
346
+ ));
347
+
348
+ checks.push(() => expectMatch(
349
+ setup,
350
+ /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*scriptName = 'setup-openclaw-vps\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*npm install -g openclaw@2026\.4\.5 pm2@latest[\s\S]*pm2 save && pm2 startup/s,
319
351
  'VPS native script generation must install openclaw+pm2 and persist PM2 startup'
320
352
  ));
321
353
 
@@ -325,6 +357,12 @@ checks.push(() => expectMatch(
325
357
  'Native script generation must install and start a standalone 9Router dashboard on port 20128'
326
358
  ));
327
359
 
360
+ checks.push(() => expectMatch(
361
+ setup,
362
+ /} else if \(is9Router\) \{[\s\S]*container_name: openclaw-bot[\s\S]*depends_on:[\s\S]*- 9router[\s\S]*container_name: 9router[\s\S]*PORT=20128[\s\S]*HOSTNAME=0\.0\.0\.0[\s\S]*9router-data:/s,
363
+ 'Wizard single-bot Docker compose must include the 9Router sidecar service and named volume when provider is 9Router'
364
+ ));
365
+
328
366
  checks.push(() => expectMatch(
329
367
  setup,
330
368
  /function native9RouterSyncScriptContent\(\) \{[\s\S]*path\.join\(process\.env\.HOME\|\|process\.env\.USERPROFILE\|\|'\.'\,\'\.9router\'\,\'db\.json\'\)[\s\S]*providerConnections[\s\S]*smart-route/s,
@@ -381,7 +419,7 @@ checks.push(() => expectMatch(
381
419
 
382
420
  checks.push(() => expectMatch(
383
421
  setup,
384
- /else if \(state\.nativeOs === 'linux-desktop'\) \{[\s\S]*scriptName = 'setup-openclaw-linux\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*npm install -g openclaw@latest[\s\S]*openclaw gateway run/s,
422
+ /else if \(state\.nativeOs === 'linux-desktop'\) \{[\s\S]*scriptName = 'setup-openclaw-linux\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*npm install -g openclaw@2026\.4\.5[\s\S]*openclaw gateway run/s,
385
423
  'Linux Desktop native script generation must install openclaw and run the gateway'
386
424
  ));
387
425