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/CHANGELOG.md +129 -119
- package/CHANGELOG.vi.md +127 -118
- package/README.md +11 -14
- package/README.vi.md +11 -14
- package/cli.js +43 -31
- package/docs/install-docker.md +3 -2
- package/docs/install-docker.vi.md +3 -2
- package/package.json +1 -1
- package/setup.js +139 -68
- package/tests/smoke-cli-logic.mjs +45 -7
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
|
-
//
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
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(
|
|
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@
|
|
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('
|
|
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
|
-
'
|
|
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@
|
|
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('
|
|
3573
|
-
lines.push('
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
lines
|
|
3578
|
-
lines.push('
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
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 (
|
|
3603
|
-
|
|
3604
|
-
sh
|
|
3605
|
-
|
|
3606
|
-
|
|
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('
|
|
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@
|
|
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@
|
|
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@
|
|
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 "
|
|
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 > "
|
|
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\(
|
|
56
|
-
'CLI must provide a shared helper that always installs or upgrades openclaw
|
|
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\(
|
|
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@
|
|
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@
|
|
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
|
-
/
|
|
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@
|
|
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
|
|