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/CHANGELOG.md +7 -0
- package/CHANGELOG.vi.md +7 -0
- package/README.md +3 -3
- package/README.vi.md +3 -3
- package/cli.js +261 -199
- package/package.json +1 -1
- package/setup.js +360 -178
- package/tests/smoke-cli-logic.mjs +87 -18
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
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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 = '
|
|
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: '
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
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
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
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
|
-
//
|
|
1973
|
-
//
|
|
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: '
|
|
2767
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
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(
|
|
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(
|
|
3003
|
-
arr.push('nohup
|
|
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
|
|
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:
|
|
3121
|
-
agentDir:
|
|
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: '
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
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
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
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' :
|
|
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
|
-
`
|
|
3594
|
-
'
|
|
3595
|
-
'
|
|
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 (
|
|
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('
|
|
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('
|
|
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
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
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
|
-
|
|
3697
|
-
'
|
|
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(
|
|
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(
|
|
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
|
-
|
|
3744
|
-
'
|
|
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
|
-
|
|
3806
|
-
|
|
3807
|
-
const
|
|
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';
|