create-openclaw-bot 5.1.11 → 5.1.13
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 +122 -109
- package/CHANGELOG.vi.md +120 -109
- package/README.md +2 -2
- package/README.vi.md +2 -2
- package/cli.js +13 -5
- package/package.json +1 -1
- package/setup.js +279 -142
- package/style.css +1 -1
package/setup.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
// ========== State ==========
|
|
27
|
-
const state = {
|
|
27
|
+
const state = {
|
|
28
28
|
currentStep: 1,
|
|
29
29
|
totalSteps: 5,
|
|
30
30
|
channel: null,
|
|
@@ -47,21 +47,21 @@
|
|
|
47
47
|
apiKey: '',
|
|
48
48
|
projectPath: '',
|
|
49
49
|
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function getGatewayAllowedOrigins(port) {
|
|
53
|
-
const normalizedPort = Number(port) || 18791;
|
|
54
|
-
const origins = new Set([
|
|
55
|
-
`http://localhost:${normalizedPort}`,
|
|
56
|
-
`http://127.0.0.1:${normalizedPort}`,
|
|
57
|
-
`http://0.0.0.0:${normalizedPort}`,
|
|
58
|
-
]);
|
|
59
|
-
const currentHost = (window.location && window.location.hostname) ? window.location.hostname.trim() : '';
|
|
60
|
-
if (currentHost) {
|
|
61
|
-
origins.add(`http://${currentHost}:${normalizedPort}`);
|
|
62
|
-
}
|
|
63
|
-
return Array.from(origins);
|
|
64
|
-
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function getGatewayAllowedOrigins(port) {
|
|
53
|
+
const normalizedPort = Number(port) || 18791;
|
|
54
|
+
const origins = new Set([
|
|
55
|
+
`http://localhost:${normalizedPort}`,
|
|
56
|
+
`http://127.0.0.1:${normalizedPort}`,
|
|
57
|
+
`http://0.0.0.0:${normalizedPort}`,
|
|
58
|
+
]);
|
|
59
|
+
const currentHost = (window.location && window.location.hostname) ? window.location.hostname.trim() : '';
|
|
60
|
+
if (currentHost) {
|
|
61
|
+
origins.add(`http://${currentHost}:${normalizedPort}`);
|
|
62
|
+
}
|
|
63
|
+
return Array.from(origins);
|
|
64
|
+
}
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
// ========== AI Providers & Models ==========
|
|
@@ -161,6 +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
172
|
{
|
|
165
173
|
id: 'voice-call',
|
|
166
174
|
name: 'Voice Call',
|
|
@@ -193,7 +201,6 @@
|
|
|
193
201
|
|
|
194
202
|
// ========== Available Skills (ClawHub registry — agent capabilities) ==========
|
|
195
203
|
const SKILLS = [
|
|
196
|
-
// Web Search removed — OpenClaw has native search built-in (no Tavily key needed)
|
|
197
204
|
{
|
|
198
205
|
id: 'browser',
|
|
199
206
|
name: 'Browser Automation ⭐(Khuyên dùng)',
|
|
@@ -227,7 +234,7 @@
|
|
|
227
234
|
id: 'image-gen',
|
|
228
235
|
name: 'Image Generation',
|
|
229
236
|
icon: '🎨',
|
|
230
|
-
descVi: 'Tạo ảnh bằng AI (DALL·E, Flux...)', descEn: 'Generate images
|
|
237
|
+
descVi: 'Tạo ảnh bằng AI (DALL·E, Flux, Midjourney...)', descEn: 'Generate images via AI (DALL-E, Flux, Midjourney...)',
|
|
231
238
|
slug: 'image-gen',
|
|
232
239
|
noteVi: 'Dùng chung OPENAI_API_KEY (DALL-E) hoặc thêm FLUX_API_KEY', noteEn: 'Uses OPENAI_API_KEY (DALL-E) or FLUX_API_KEY',
|
|
233
240
|
envVars: ['# FLUX_API_KEY=<your_flux_key> # chỉ cần nếu dùng Flux'],
|
|
@@ -243,11 +250,36 @@
|
|
|
243
250
|
id: 'email',
|
|
244
251
|
name: 'Email Assistant',
|
|
245
252
|
icon: '📧',
|
|
246
|
-
descVi: 'Quản lý, soạn, tóm tắt email', descEn: 'Manage, compose, summarize emails',
|
|
253
|
+
descVi: 'Quản lý, soạn, tóm tắt email (Gmail, Outlook...)', descEn: 'Manage, compose, summarize emails (Gmail, Outlook...)',
|
|
247
254
|
slug: 'email-assistant',
|
|
248
255
|
noteVi: 'Cần cấu hình SMTP trong .env', noteEn: 'Requires SMTP configuration in .env',
|
|
249
256
|
envVars: ['SMTP_HOST=smtp.gmail.com', 'SMTP_PORT=587', 'SMTP_USER=<your_email>', 'SMTP_PASS=<your_app_password>'],
|
|
250
257
|
},
|
|
258
|
+
{
|
|
259
|
+
id: 'web-search',
|
|
260
|
+
name: 'Web Search',
|
|
261
|
+
icon: '🔍',
|
|
262
|
+
descVi: 'Tìm kiếm web thời gian thực (DuckDuckGo) — không cần API key', descEn: 'Real-time web search (DuckDuckGo) — no API key needed',
|
|
263
|
+
slug: 'web-search',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: 'notion',
|
|
267
|
+
name: 'Notion',
|
|
268
|
+
icon: '📓',
|
|
269
|
+
descVi: 'Tạo, chỉnh sửa trang và database Notion', descEn: 'Create and edit Notion pages and databases',
|
|
270
|
+
slug: 'notion',
|
|
271
|
+
noteVi: 'Cần Notion Integration Token', noteEn: 'Requires Notion Integration Token',
|
|
272
|
+
envVars: ['NOTION_API_KEY=<your_notion_integration_token>'],
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: 'slack',
|
|
276
|
+
name: 'Slack',
|
|
277
|
+
icon: '🗨️',
|
|
278
|
+
descVi: 'Gửi tin, react, ghim tin nhắn trong Slack', descEn: 'Send messages, react, pin items in Slack',
|
|
279
|
+
slug: 'slack',
|
|
280
|
+
noteVi: 'Cần Slack Bot Token', noteEn: 'Requires Slack Bot Token',
|
|
281
|
+
envVars: ['SLACK_BOT_TOKEN=<your_slack_bot_token>'],
|
|
282
|
+
},
|
|
251
283
|
];
|
|
252
284
|
|
|
253
285
|
// ========== Channel definitions ==========
|
|
@@ -530,6 +562,24 @@
|
|
|
530
562
|
state.bots.push({ name: '', slashCmd: '', desc: '', provider: 'google', model: 'google/gemini-2.5-flash', token: '', apiKey: '' });
|
|
531
563
|
}
|
|
532
564
|
|
|
565
|
+
// Auto-select telegram-multibot-relay plugin when multi-bot, deselect when single
|
|
566
|
+
const relayId = 'telegram-multibot-relay';
|
|
567
|
+
if (count > 1) {
|
|
568
|
+
if (!state.config.plugins.includes(relayId)) {
|
|
569
|
+
state.config.plugins.push(relayId);
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
state.config.plugins = state.config.plugins.filter(p => p !== relayId);
|
|
573
|
+
}
|
|
574
|
+
// Sync relay card checkbox if already rendered
|
|
575
|
+
const relayCard = document.querySelector(`.plugin-card[data-plugin="${relayId}"]`);
|
|
576
|
+
if (relayCard) {
|
|
577
|
+
const isSelected = count > 1;
|
|
578
|
+
relayCard.classList.toggle('plugin-card--selected', isSelected);
|
|
579
|
+
const cb = relayCard.querySelector('input[type="checkbox"]');
|
|
580
|
+
if (cb) cb.checked = isSelected;
|
|
581
|
+
}
|
|
582
|
+
|
|
533
583
|
// Show/hide group option for 2+ bots
|
|
534
584
|
const groupOpt = document.getElementById('multibot-group-option');
|
|
535
585
|
if (groupOpt) groupOpt.style.display = count > 1 ? '' : 'none';
|
|
@@ -606,10 +656,12 @@
|
|
|
606
656
|
if (labelEl) labelEl.style.display = 'none';
|
|
607
657
|
if (slashGroup) slashGroup.style.display = 'none';
|
|
608
658
|
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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 || '';
|
|
613
665
|
document.getElementById('cfg-bot-tab-persona').value = bot.persona || '';
|
|
614
666
|
return;
|
|
615
667
|
}
|
|
@@ -640,9 +692,11 @@
|
|
|
640
692
|
const nameEl = document.getElementById('cfg-bot-tab-name');
|
|
641
693
|
const slashEl = document.getElementById('cfg-bot-tab-slash');
|
|
642
694
|
const descEl = document.getElementById('cfg-bot-tab-desc');
|
|
695
|
+
const personaEl = document.getElementById('cfg-bot-tab-persona');
|
|
643
696
|
if (nameEl) nameEl.value = bot.name || '';
|
|
644
697
|
if (slashEl) slashEl.value = bot.slashCmd || '';
|
|
645
698
|
if (descEl) descEl.value = bot.desc || '';
|
|
699
|
+
if (personaEl) personaEl.value = bot.persona || '';
|
|
646
700
|
|
|
647
701
|
// Also sync global config fields from active bot (provider/model carry over)
|
|
648
702
|
if (bot.provider) {
|
|
@@ -683,9 +737,11 @@
|
|
|
683
737
|
const nameEl = document.getElementById('cfg-bot-tab-name');
|
|
684
738
|
const slashEl = document.getElementById('cfg-bot-tab-slash');
|
|
685
739
|
const descEl = document.getElementById('cfg-bot-tab-desc');
|
|
740
|
+
const personaEl = document.getElementById('cfg-bot-tab-persona');
|
|
686
741
|
if (nameEl) bot.name = nameEl.value;
|
|
687
742
|
if (slashEl) bot.slashCmd = slashEl.value;
|
|
688
743
|
if (descEl) bot.desc = descEl.value;
|
|
744
|
+
if (personaEl) bot.persona = personaEl.value;
|
|
689
745
|
}
|
|
690
746
|
|
|
691
747
|
window.__saveBotTabName = function(val) {
|
|
@@ -711,6 +767,12 @@
|
|
|
711
767
|
}
|
|
712
768
|
};
|
|
713
769
|
|
|
770
|
+
window.__saveBotTabPersona = function(val) {
|
|
771
|
+
if (state.bots[state.activeBotIndex]) {
|
|
772
|
+
state.bots[state.activeBotIndex].persona = val;
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
714
776
|
|
|
715
777
|
|
|
716
778
|
// ========== Step 1: Deploy Mode + OS ==========
|
|
@@ -922,19 +984,35 @@
|
|
|
922
984
|
// Step 1 (env): always valid
|
|
923
985
|
// Step 2 (channel): require selection
|
|
924
986
|
if (state.currentStep === 2 && !state.channel) isDisabled = true;
|
|
925
|
-
// Step 3 (bot config): require bot name
|
|
987
|
+
// Step 3 (bot config): require at least one bot name
|
|
926
988
|
if (state.currentStep === 3) {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
989
|
+
if (state.botCount > 1) {
|
|
990
|
+
// Multi-bot: require name for the currently active bot tab
|
|
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();
|
|
995
|
+
if (!tabNameVal) isDisabled = true;
|
|
996
|
+
} else {
|
|
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
|
|
999
|
+
const nameVal = document.getElementById('cfg-name')?.value?.trim()
|
|
1000
|
+
|| document.getElementById('cfg-bot-tab-name')?.value?.trim()
|
|
1001
|
+
|| state.config.botName?.trim();
|
|
1002
|
+
if (!nameVal) isDisabled = true;
|
|
1003
|
+
}
|
|
930
1004
|
}
|
|
931
1005
|
// Step 4 (api keys): require token/key
|
|
932
1006
|
if (state.currentStep === 4) {
|
|
933
|
-
const botTokenEl = document.getElementById('key-bot-token');
|
|
934
1007
|
const apiKeyEl = document.getElementById('key-api-key');
|
|
935
1008
|
const provider = PROVIDERS[state.config.provider];
|
|
936
|
-
if (
|
|
937
|
-
|
|
1009
|
+
if (state.channel === 'telegram' && state.botCount > 1) {
|
|
1010
|
+
// Multi-bot Telegram: require at least the first bot's token
|
|
1011
|
+
const firstTokenEl = document.getElementById('key-bot-token-0');
|
|
1012
|
+
if (!firstTokenEl || !firstTokenEl.value.trim()) isDisabled = true;
|
|
1013
|
+
} else if (state.channel === 'telegram' || state.channel === 'zalo-bot') {
|
|
1014
|
+
const botTokenEl = document.getElementById('key-bot-token');
|
|
1015
|
+
if (!botTokenEl || !botTokenEl.value.trim()) isDisabled = true;
|
|
938
1016
|
}
|
|
939
1017
|
if (provider && !provider.isProxy && !provider.isLocal && provider.envKey && apiKeyEl) {
|
|
940
1018
|
if (!apiKeyEl.value.trim()) isDisabled = true;
|
|
@@ -1010,9 +1088,11 @@
|
|
|
1010
1088
|
}
|
|
1011
1089
|
|
|
1012
1090
|
// Plugins grid (npm packages — extra channels/extensions)
|
|
1091
|
+
// Filter out hidden plugins from user-facing grid
|
|
1092
|
+
const visiblePlugins = PLUGINS.filter((p) => !p.hidden);
|
|
1013
1093
|
const pluginGrid = document.getElementById('extra-plugin-grid');
|
|
1014
1094
|
if (pluginGrid) {
|
|
1015
|
-
pluginGrid.innerHTML =
|
|
1095
|
+
pluginGrid.innerHTML = visiblePlugins.map((p) => `
|
|
1016
1096
|
<label class="plugin-card" data-plugin="${p.id}">
|
|
1017
1097
|
<input type="checkbox" class="plugin-checkbox" value="${p.id}" onchange="window.__togglePlugin('${p.id}', this.checked)">
|
|
1018
1098
|
<div class="plugin-card__icon">${p.icon}</div>
|
|
@@ -1065,6 +1145,17 @@
|
|
|
1065
1145
|
prompt.value = DEFAULT_PROMPTS[lang].replace('{BOT_NAME}', nameVal).replace('{BOT_DESC}', descVal);
|
|
1066
1146
|
autoExpand(prompt);
|
|
1067
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();
|
|
1068
1159
|
}
|
|
1069
1160
|
if (e.target.id === 'cfg-prompt') {
|
|
1070
1161
|
e.target.dataset.userEdited = 'true';
|
|
@@ -1164,6 +1255,12 @@
|
|
|
1164
1255
|
state.config.systemPrompt = document.getElementById('cfg-prompt')?.value || state.config.systemPrompt || DEFAULT_PROMPTS['vi'];
|
|
1165
1256
|
state.config.userInfo = document.getElementById('cfg-user-info')?.value?.trim() || state.config.userInfo || '';
|
|
1166
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
|
+
}
|
|
1167
1264
|
}
|
|
1168
1265
|
|
|
1169
1266
|
// Save Step 4 credential inputs to state (persists across Back navigation)
|
|
@@ -1591,15 +1688,15 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
1591
1688
|
commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
|
|
1592
1689
|
channels: ch.channelConfig,
|
|
1593
1690
|
tools: { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } },
|
|
1594
|
-
gateway: {
|
|
1595
|
-
port: 18791,
|
|
1596
|
-
mode: 'local',
|
|
1597
|
-
bind: '0.0.0.0',
|
|
1598
|
-
controlUi: {
|
|
1599
|
-
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
1600
|
-
},
|
|
1601
|
-
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
1602
|
-
},
|
|
1691
|
+
gateway: {
|
|
1692
|
+
port: 18791,
|
|
1693
|
+
mode: 'local',
|
|
1694
|
+
bind: '0.0.0.0',
|
|
1695
|
+
controlUi: {
|
|
1696
|
+
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
1697
|
+
},
|
|
1698
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
1699
|
+
},
|
|
1603
1700
|
};
|
|
1604
1701
|
|
|
1605
1702
|
// 9Router: add proxy endpoint config under models.providers
|
|
@@ -1740,6 +1837,17 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
1740
1837
|
'telegram-multibot-relay': { enabled: true },
|
|
1741
1838
|
},
|
|
1742
1839
|
};
|
|
1840
|
+
} else if (state.config.plugins.length > 0) {
|
|
1841
|
+
// Non-multibot: write selected visible plugins into openclaw.json
|
|
1842
|
+
const pluginEntries = {};
|
|
1843
|
+
state.config.plugins.forEach((pid) => {
|
|
1844
|
+
const plugin = PLUGINS.find((p) => p.id === pid);
|
|
1845
|
+
if (!plugin || plugin.hidden) return;
|
|
1846
|
+
pluginEntries[plugin.package || pid] = { enabled: true };
|
|
1847
|
+
});
|
|
1848
|
+
if (Object.keys(pluginEntries).length > 0) {
|
|
1849
|
+
clawConfig.plugins = { entries: pluginEntries };
|
|
1850
|
+
}
|
|
1743
1851
|
}
|
|
1744
1852
|
|
|
1745
1853
|
setOutput('out-openclaw-json', JSON.stringify(clawConfig, null, 2));
|
|
@@ -1775,19 +1883,19 @@ model:
|
|
|
1775
1883
|
// 3. Dockerfile
|
|
1776
1884
|
const allPlugins = [];
|
|
1777
1885
|
if (ch.pluginInstall) allPlugins.push(ch.pluginInstall);
|
|
1778
|
-
const encodeBase64Utf8 = (value) => btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
|
|
1779
|
-
const indentBlock = (text, spaces) => {
|
|
1780
|
-
const prefix = ' '.repeat(spaces);
|
|
1781
|
-
return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
|
|
1782
|
-
};
|
|
1783
|
-
const build9RouterComposeEntrypointScript = (syncScriptBase64) => [
|
|
1784
|
-
'npm install -g 9router',
|
|
1785
|
-
`node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
|
|
1786
|
-
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
1787
|
-
'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
|
|
1788
|
-
].join('\n');
|
|
1789
|
-
|
|
1790
|
-
state.config.plugins.forEach((pid) => {
|
|
1886
|
+
const encodeBase64Utf8 = (value) => btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
|
|
1887
|
+
const indentBlock = (text, spaces) => {
|
|
1888
|
+
const prefix = ' '.repeat(spaces);
|
|
1889
|
+
return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
|
|
1890
|
+
};
|
|
1891
|
+
const build9RouterComposeEntrypointScript = (syncScriptBase64) => [
|
|
1892
|
+
'npm install -g 9router',
|
|
1893
|
+
`node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
|
|
1894
|
+
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
1895
|
+
'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
|
|
1896
|
+
].join('\n');
|
|
1897
|
+
|
|
1898
|
+
state.config.plugins.forEach((pid) => {
|
|
1791
1899
|
const plug = PLUGINS.find((p) => p.id === pid);
|
|
1792
1900
|
if (plug) allPlugins.push(plug.package);
|
|
1793
1901
|
});
|
|
@@ -1832,7 +1940,7 @@ model:
|
|
|
1832
1940
|
? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 & '
|
|
1833
1941
|
: '';
|
|
1834
1942
|
// Patch config on every startup to keep gateway settings stable
|
|
1835
|
-
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)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\" && `;
|
|
1836
1944
|
// Auto-approve device pairing after gateway starts (required since v2026.3.x)
|
|
1837
1945
|
const autoApproveCmd = '(while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done) & ';
|
|
1838
1946
|
const finalCmd = `CMD sh -c "${pluginInstallCmd}${patchCmd}${browserPrefix}${autoApproveCmd}${gatewayCmd}"`;
|
|
@@ -1844,7 +1952,7 @@ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /v
|
|
|
1844
1952
|
|
|
1845
1953
|
ARG CACHEBUST=${Date.now()}
|
|
1846
1954
|
RUN npm install -g openclaw@latest${skillLines}${browserInstallLines}
|
|
1847
|
-
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);}"
|
|
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);}"
|
|
1848
1956
|
WORKDIR /root/.openclaw
|
|
1849
1957
|
|
|
1850
1958
|
EXPOSE 18791
|
|
@@ -1863,7 +1971,7 @@ ${finalCmd}`;
|
|
|
1863
1971
|
// Background loop inside 9Router container every 30s.
|
|
1864
1972
|
// Read providerConnections directly from db.json so smart-route survives
|
|
1865
1973
|
// dashboard auth/response changes in newer 9Router builds.
|
|
1866
|
-
const syncScript = `const fs=require('fs');const INTERVAL=30000;const p='/root/.9router/db.json';
|
|
1974
|
+
const syncScript = `const fs=require('fs');const INTERVAL=30000;const p='/root/.9router/db.json';
|
|
1867
1975
|
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']};
|
|
1868
1976
|
console.log('[sync-combo] 9Router sync loop started...');
|
|
1869
1977
|
const sync = async () => {
|
|
@@ -1913,11 +2021,11 @@ const sync = async () => {
|
|
|
1913
2021
|
console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models');
|
|
1914
2022
|
}
|
|
1915
2023
|
} catch (e) { }
|
|
1916
|
-
};
|
|
1917
|
-
setTimeout(sync, 5000);
|
|
1918
|
-
setInterval(sync, INTERVAL);`;
|
|
1919
|
-
const syncScriptBase64 = encodeBase64Utf8(syncScript);
|
|
1920
|
-
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64);
|
|
2024
|
+
};
|
|
2025
|
+
setTimeout(sync, 5000);
|
|
2026
|
+
setInterval(sync, INTERVAL);`;
|
|
2027
|
+
const syncScriptBase64 = encodeBase64Utf8(syncScript);
|
|
2028
|
+
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64);
|
|
1921
2029
|
|
|
1922
2030
|
let compose;
|
|
1923
2031
|
if (isMultiBotWizard) {
|
|
@@ -1946,14 +2054,14 @@ ${dependsOn}${extraHosts} volumes:
|
|
|
1946
2054
|
image: node:22-slim
|
|
1947
2055
|
container_name: 9router-multibot
|
|
1948
2056
|
restart: always
|
|
1949
|
-
entrypoint:
|
|
1950
|
-
- /bin/sh
|
|
1951
|
-
- -c
|
|
1952
|
-
- |
|
|
1953
|
-
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
1954
|
-
environment:
|
|
1955
|
-
- PORT=20128
|
|
1956
|
-
- HOSTNAME=0.0.0.0
|
|
2057
|
+
entrypoint:
|
|
2058
|
+
- /bin/sh
|
|
2059
|
+
- -c
|
|
2060
|
+
- |
|
|
2061
|
+
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
2062
|
+
environment:
|
|
2063
|
+
- PORT=20128
|
|
2064
|
+
- HOSTNAME=0.0.0.0
|
|
1957
2065
|
- CI=true
|
|
1958
2066
|
volumes:
|
|
1959
2067
|
- 9router-data:/root/.9router
|
|
@@ -2039,14 +2147,14 @@ ${extraHostsBlock}
|
|
|
2039
2147
|
image: node:22-slim
|
|
2040
2148
|
container_name: 9router
|
|
2041
2149
|
restart: always
|
|
2042
|
-
entrypoint:
|
|
2043
|
-
- /bin/sh
|
|
2044
|
-
- -c
|
|
2045
|
-
- |
|
|
2046
|
-
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
2047
|
-
environment:
|
|
2048
|
-
- PORT=20128
|
|
2049
|
-
- HOSTNAME=0.0.0.0
|
|
2150
|
+
entrypoint:
|
|
2151
|
+
- /bin/sh
|
|
2152
|
+
- -c
|
|
2153
|
+
- |
|
|
2154
|
+
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
2155
|
+
environment:
|
|
2156
|
+
- PORT=20128
|
|
2157
|
+
- HOSTNAME=0.0.0.0
|
|
2050
2158
|
- CI=true
|
|
2051
2159
|
volumes:
|
|
2052
2160
|
- 9router-data:/root/.9router
|
|
@@ -2885,17 +2993,17 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
2885
2993
|
// ─── Shared initializer (provider install) ───────────────────────────────
|
|
2886
2994
|
function providerLines(arr, shell) {
|
|
2887
2995
|
if (is9Router) {
|
|
2888
|
-
if (shell === 'bat') {
|
|
2889
|
-
arr.push('npm install -g 9router');
|
|
2890
|
-
arr.push('start "9Router" cmd /k "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
2891
|
-
arr.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
|
|
2892
|
-
arr.push('timeout /t 5 /nobreak >nul');
|
|
2893
|
-
} else {
|
|
2894
|
-
arr.push('npm install -g 9router');
|
|
2895
|
-
arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 node "$(npm root -g)/9router/app/server.js" >/tmp/9router.log 2>&1 &');
|
|
2896
|
-
arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
|
|
2897
|
-
arr.push('sleep 3');
|
|
2898
|
-
}
|
|
2996
|
+
if (shell === 'bat') {
|
|
2997
|
+
arr.push('npm install -g 9router');
|
|
2998
|
+
arr.push('start "9Router" cmd /k "9router -n -l -H 0.0.0.0 -p 20128 --skip-update"');
|
|
2999
|
+
arr.push('start "9Router Smart Route Sync" cmd /k "node .\\.openclaw\\9router-smart-route-sync.js"');
|
|
3000
|
+
arr.push('timeout /t 5 /nobreak >nul');
|
|
3001
|
+
} else {
|
|
3002
|
+
arr.push('npm install -g 9router');
|
|
3003
|
+
arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 node "$(npm root -g)/9router/app/server.js" >/tmp/9router.log 2>&1 &');
|
|
3004
|
+
arr.push('nohup node ./.openclaw/9router-smart-route-sync.js >/tmp/9router-sync.log 2>&1 &');
|
|
3005
|
+
arr.push('sleep 3');
|
|
3006
|
+
}
|
|
2899
3007
|
} else if (isOllama) {
|
|
2900
3008
|
if (shell === 'bat') {
|
|
2901
3009
|
arr.push('where ollama >nul 2>&1 || (powershell -Command "Invoke-WebRequest -Uri https://ollama.com/download/OllamaSetup.exe -OutFile OllamaSetup.exe" && OllamaSetup.exe && del OllamaSetup.exe)');
|
|
@@ -3053,15 +3161,15 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
3053
3161
|
'telegram-multibot-relay': { enabled: true },
|
|
3054
3162
|
},
|
|
3055
3163
|
},
|
|
3056
|
-
gateway: {
|
|
3057
|
-
port: 18791,
|
|
3058
|
-
mode: 'local',
|
|
3059
|
-
bind: '0.0.0.0',
|
|
3060
|
-
controlUi: {
|
|
3061
|
-
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
3062
|
-
},
|
|
3063
|
-
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3064
|
-
},
|
|
3164
|
+
gateway: {
|
|
3165
|
+
port: 18791,
|
|
3166
|
+
mode: 'local',
|
|
3167
|
+
bind: '0.0.0.0',
|
|
3168
|
+
controlUi: {
|
|
3169
|
+
allowedOrigins: getGatewayAllowedOrigins(18791),
|
|
3170
|
+
},
|
|
3171
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3172
|
+
},
|
|
3065
3173
|
};
|
|
3066
3174
|
return JSON.stringify(cfg, null, 2);
|
|
3067
3175
|
}
|
|
@@ -3103,9 +3211,11 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
3103
3211
|
files[`.openclaw/${meta.workspaceDir}/SOUL.md`] = isVi
|
|
3104
3212
|
? `# Tinh cach\n\n${meta.persona || 'Huu ich that su, gan gui, ngan gon.'}`
|
|
3105
3213
|
: `# Soul\n\n${meta.persona || 'Helpful, concise, and practical.'}`;
|
|
3214
|
+
const _secRulesForBot = state.config.securityRules || DEFAULT_SECURITY_RULES[isVi ? 'vi' : 'en'];
|
|
3106
3215
|
files[`.openclaw/${meta.workspaceDir}/AGENTS.md`] = (isVi
|
|
3107
|
-
? `# Huong dan van hanh\n\n## Vai tro\nBan la **${meta.name}**, ${meta.desc.toLowerCase()}.\n\n## Khi nao nen tra loi\n- Trong group, xem user dang goi ban neu tin nhan co alias cua ban: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}\n- Neu user tag username Telegram cua ban thi luon tra loi.\n- Gateway se tu dong tha ack \`👍\` khi nhan message; khong can tu tha them neu ack da hien.\n- Neu user dang goi ro bot khac ${relayTargetNames} thi khong cuop loi.\n\n## Phoi hop\n- Dung \`TEAM.md\` lam nguon su that.\n- Neu user bao ban hoi hoac xin y kien tu ${otherBots.length ? otherBots.map((peer) => `\`${peer.name}\``).join(', ') : '`bot khac`'} thi dung agent-to-agent noi bo ngay trong turn hien tai.\n- Neu ban la bot mo loi, chi gui 1 cau mo dau ngan roi handoff ngay. Khong tu noi thay bot dich tru khi handoff that bai ro rang.\n- Khi handoff, phai goi dung agent id ky thuat ${relayTargetIds}, khong dung ten hien thi.\n- Neu ban la bot dich nhan handoff, hay tra loi cong khai ngay trong cung Telegram chat/thread bang chinh account cua minh. Uu tien tra loi co \`[[reply_to_current]]\`; neu can, dung Telegram send/sendMessage action thay vi chi output thuong.\n- Khong bao user phai tag lai bot kia neu ban co the hoi noi bo duoc
|
|
3108
|
-
: `# Operating Manual\n\n## Role\nYou are **${meta.name}**, ${meta.desc.toLowerCase()}.\n\n## When To Reply\n- In group chats, treat the message as addressed to you if it contains one of your aliases: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}\n- Always reply when your Telegram username is tagged.\n- The gateway auto-sends the \`👍\` ack reaction on inbound messages; do not duplicate it manually if it already appeared.\n- If the message is clearly calling another bot such as ${relayTargetNames}, do not hijack it.\n\n## Coordination\n- Use \`TEAM.md\` as the source of truth.\n- If the user asks you to consult ${otherBots.length ? otherBots.map((peer) => `\`${peer.name}\``).join(', ') : '`another bot`'}, use internal agent-to-agent handoff in the same turn.\n- If you are the caller bot, send only one short opener then hand off immediately. Do not speak for the target bot unless the handoff clearly fails.\n- When handing off, use the exact technical agent id ${relayTargetIds}, not the display name.\n- If you are the target bot receiving a handoff, publish the real answer into the same Telegram chat/thread from your own account. Prefer replying with \`[[reply_to_current]]\`; if needed, use the Telegram send/sendMessage action instead of plain assistant output.\n- Do not ask the user to tag the other bot again if you can consult internally
|
|
3216
|
+
? `# Huong dan van hanh\n\n## Vai tro\nBan la **${meta.name}**, ${meta.desc.toLowerCase()}.\n\n## Khi nao nen tra loi\n- Trong group, xem user dang goi ban neu tin nhan co alias cua ban: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}\n- Neu user tag username Telegram cua ban thi luon tra loi.\n- Gateway se tu dong tha ack \`👍\` khi nhan message; khong can tu tha them neu ack da hien.\n- Neu user dang goi ro bot khac ${relayTargetNames} thi khong cuop loi.\n\n## Phoi hop\n- Dung \`TEAM.md\` lam nguon su that.\n- Neu user bao ban hoi hoac xin y kien tu ${otherBots.length ? otherBots.map((peer) => `\`${peer.name}\``).join(', ') : '`bot khac`'} thi dung agent-to-agent noi bo ngay trong turn hien tai.\n- Neu ban la bot mo loi, chi gui 1 cau mo dau ngan roi handoff ngay. Khong tu noi thay bot dich tru khi handoff that bai ro rang.\n- Khi handoff, phai goi dung agent id ky thuat ${relayTargetIds}, khong dung ten hien thi.\n- Neu ban la bot dich nhan handoff, hay tra loi cong khai ngay trong cung Telegram chat/thread bang chinh account cua minh. Uu tien tra loi co \`[[reply_to_current]]\`; neu can, dung Telegram send/sendMessage action thay vi chi output thuong.\n- Khong bao user phai tag lai bot kia neu ban co the hoi noi bo duoc.\n\n${_secRulesForBot}`
|
|
3217
|
+
: `# Operating Manual\n\n## Role\nYou are **${meta.name}**, ${meta.desc.toLowerCase()}.\n\n## When To Reply\n- In group chats, treat the message as addressed to you if it contains one of your aliases: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}\n- Always reply when your Telegram username is tagged.\n- The gateway auto-sends the \`👍\` ack reaction on inbound messages; do not duplicate it manually if it already appeared.\n- If the message is clearly calling another bot such as ${relayTargetNames}, do not hijack it.\n\n## Coordination\n- Use \`TEAM.md\` as the source of truth.\n- If the user asks you to consult ${otherBots.length ? otherBots.map((peer) => `\`${peer.name}\``).join(', ') : '`another bot`'}, use internal agent-to-agent handoff in the same turn.\n- If you are the caller bot, send only one short opener then hand off immediately. Do not speak for the target bot unless the handoff clearly fails.\n- When handing off, use the exact technical agent id ${relayTargetIds}, not the display name.\n- If you are the target bot receiving a handoff, publish the real answer into the same Telegram chat/thread from your own account. Prefer replying with \`[[reply_to_current]]\`; if needed, use the Telegram send/sendMessage action instead of plain assistant output.\n- Do not ask the user to tag the other bot again if you can consult internally.\n\n${_secRulesForBot}`);
|
|
3218
|
+
|
|
3109
3219
|
files[`.openclaw/${meta.workspaceDir}/TEAM.md`] = teamMd;
|
|
3110
3220
|
files[`.openclaw/${meta.workspaceDir}/RELAY.md`] = isVi
|
|
3111
3221
|
? `# Telegram Relay Playbook\n\n## Muc tieu\n- Cho phep bot mo loi goi bot dich noi bo, sau do bot dich tra loi cong khai bang chinh account cua minh.\n\n## Protocol\n1. Bot mo loi gui 1 cau ngan xac nhan se hoi bot dich.\n2. Bot mo loi handoff noi bo bang dung agent id trong \`TEAM.md\`.\n3. Bot dich tra loi cong khai trong cung chat/thread hien tai.\n4. Neu thay \`[[reply_to_current]]\` hoac Telegram send/sendMessage action kha dung, uu tien dung de bam dung message goc.\n5. Neu handoff that bai ro rang, chi bot mo loi moi duoc fallback tom tat.\n`
|
|
@@ -3169,15 +3279,15 @@ const sync=()=>{try{let db={};try{db=JSON.parse(fs.readFileSync(p,'utf8'));}catc
|
|
|
3169
3279
|
},
|
|
3170
3280
|
commands: { native: 'auto', nativeSkills: 'auto', restart: true },
|
|
3171
3281
|
channels: channelConfig,
|
|
3172
|
-
gateway: {
|
|
3173
|
-
port: basePort,
|
|
3174
|
-
mode: 'local',
|
|
3175
|
-
bind: '0.0.0.0',
|
|
3176
|
-
controlUi: {
|
|
3177
|
-
allowedOrigins: getGatewayAllowedOrigins(basePort),
|
|
3178
|
-
},
|
|
3179
|
-
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3180
|
-
},
|
|
3282
|
+
gateway: {
|
|
3283
|
+
port: basePort,
|
|
3284
|
+
mode: 'local',
|
|
3285
|
+
bind: '0.0.0.0',
|
|
3286
|
+
controlUi: {
|
|
3287
|
+
allowedOrigins: getGatewayAllowedOrigins(basePort),
|
|
3288
|
+
},
|
|
3289
|
+
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3290
|
+
},
|
|
3181
3291
|
|
|
3182
3292
|
};
|
|
3183
3293
|
return JSON.stringify(cfg, null, 2);
|
|
@@ -3509,29 +3619,55 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3509
3619
|
} else if (state.nativeOs === 'linux') {
|
|
3510
3620
|
const isDocker = state.deployMode === 'docker';
|
|
3511
3621
|
scriptName = isDocker ? 'setup-openclaw-docker-macos.sh' : 'setup-openclaw-macos.sh';
|
|
3512
|
-
const sh = [
|
|
3513
|
-
'#!/usr/bin/env bash', 'set -e',
|
|
3514
|
-
`echo "=== OpenClaw Setup — macOS${isDocker ? ' Docker' : ' Native'} ==="`,
|
|
3515
|
-
'command -v node > /dev/null 2>&1 || { echo "ERROR: Node.js chua cai! https://nodejs.org"; exit 1; }',
|
|
3516
|
-
'mkdir -p "$HOME/.local/bin"',
|
|
3517
|
-
'npm config set prefix "$HOME/.local"',
|
|
3518
|
-
'export PATH="$HOME/.local/bin:$PATH"',
|
|
3519
|
-
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.zshrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.zshrc"',
|
|
3520
|
-
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
|
|
3521
|
-
'npm install -g openclaw@latest',
|
|
3522
|
-
];
|
|
3523
|
-
providerLines(sh, 'sh');
|
|
3524
|
-
if (pluginCmd) sh.push(pluginCmd);
|
|
3525
3622
|
|
|
3526
|
-
if (
|
|
3527
|
-
|
|
3528
|
-
sh
|
|
3529
|
-
|
|
3530
|
-
|
|
3623
|
+
if (isDocker) {
|
|
3624
|
+
// ── macOS Docker mode: write files then docker compose up ──────────────
|
|
3625
|
+
const sh = [
|
|
3626
|
+
'#!/usr/bin/env bash', 'set -e',
|
|
3627
|
+
'echo "=== OpenClaw Setup \u2014 macOS Docker ==="',
|
|
3628
|
+
'# Check Docker Desktop is running',
|
|
3629
|
+
'if ! docker info > /dev/null 2>&1; then',
|
|
3630
|
+
' echo "\u274c Docker Desktop chua chay! Mo Docker Desktop roi chay lai script nay."',
|
|
3631
|
+
' exit 1',
|
|
3632
|
+
'fi',
|
|
3633
|
+
];
|
|
3531
3634
|
appendShWriteCommands(sh, botFiles(0));
|
|
3532
|
-
sh.push('
|
|
3635
|
+
sh.push('echo "Starting bot via Docker Compose..."');
|
|
3636
|
+
sh.push('if docker compose version > /dev/null 2>&1; then COMPOSE="docker compose"; else COMPOSE="docker-compose"; fi');
|
|
3637
|
+
sh.push('cd docker/openclaw');
|
|
3638
|
+
sh.push('$COMPOSE up --detach --build');
|
|
3639
|
+
sh.push('echo "\u2705 Bot dang chay via Docker. Xem log: docker logs -f openclaw-bot"');
|
|
3640
|
+
scriptContent = sh.filter(Boolean).join('\n');
|
|
3641
|
+
} else {
|
|
3642
|
+
// ── macOS Native mode: same approach as Ubuntu but no PM2, no apt ────────
|
|
3643
|
+
// Do NOT use 'npm config set prefix' on macOS — breaks Homebrew Node.
|
|
3644
|
+
// Use export npm_config_prefix per-session + sudo fallback.
|
|
3645
|
+
const sh = [
|
|
3646
|
+
'#!/usr/bin/env bash', 'set -e',
|
|
3647
|
+
'echo "=== OpenClaw Setup \u2014 macOS Native ==="',
|
|
3648
|
+
'command -v node > /dev/null 2>&1 || { echo "ERROR: Node.js chua cai! https://nodejs.org"; exit 1; }',
|
|
3649
|
+
'# User-local npm prefix (Homebrew-safe — no global npmrc mutation)',
|
|
3650
|
+
'mkdir -p "$HOME/.local/bin"',
|
|
3651
|
+
'export npm_config_prefix="$HOME/.local"',
|
|
3652
|
+
'export PATH="$HOME/.local/bin:$PATH"',
|
|
3653
|
+
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.zshrc" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.zshrc"',
|
|
3654
|
+
'grep -Fqx \'export PATH="$HOME/.local/bin:$PATH"\' "$HOME/.profile" 2>/dev/null || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> "$HOME/.profile"',
|
|
3655
|
+
'# Install openclaw (user-local first, sudo fallback)',
|
|
3656
|
+
'npm install -g openclaw@latest || sudo npm install -g openclaw@latest',
|
|
3657
|
+
];
|
|
3658
|
+
providerLines(sh, 'sh');
|
|
3659
|
+
if (pluginCmd) sh.push(pluginCmd);
|
|
3660
|
+
|
|
3661
|
+
if (isMultiBot) {
|
|
3662
|
+
appendShWriteCommands(sh, sharedNativeFileMap());
|
|
3663
|
+
sh.push('echo "Starting shared multi-bot gateway..."');
|
|
3664
|
+
sh.push('openclaw gateway run');
|
|
3665
|
+
} else {
|
|
3666
|
+
appendShWriteCommands(sh, botFiles(0));
|
|
3667
|
+
sh.push('openclaw gateway run');
|
|
3668
|
+
}
|
|
3669
|
+
scriptContent = sh.filter(Boolean).join('\n');
|
|
3533
3670
|
}
|
|
3534
|
-
scriptContent = sh.filter(Boolean).join('\n');
|
|
3535
3671
|
|
|
3536
3672
|
// ─── VPS/Ubuntu PM2 .SH ──────────────────────────────────────────────────
|
|
3537
3673
|
} else if (state.nativeOs === 'vps') {
|
|
@@ -3556,12 +3692,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3556
3692
|
|
|
3557
3693
|
if (isMultiBot) {
|
|
3558
3694
|
vps.push('echo "--- Creating shared multi-agent runtime ---"');
|
|
3559
|
-
appendShWriteCommands(vps, sharedNativeFileMap());
|
|
3560
|
-
vps.push('echo "--- Starting shared gateway via PM2 ---"');
|
|
3561
|
-
if (is9Router) {
|
|
3562
|
-
vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-multibot-9router --interpreter "$(command -v node)"');
|
|
3563
|
-
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3564
|
-
}
|
|
3695
|
+
appendShWriteCommands(vps, sharedNativeFileMap());
|
|
3696
|
+
vps.push('echo "--- Starting shared gateway via PM2 ---"');
|
|
3697
|
+
if (is9Router) {
|
|
3698
|
+
vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-multibot-9router --interpreter "$(command -v node)"');
|
|
3699
|
+
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3700
|
+
}
|
|
3565
3701
|
vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
|
|
3566
3702
|
vps.push('pm2 save && pm2 startup');
|
|
3567
3703
|
vps.push(`echo ""`);
|
|
@@ -3569,12 +3705,12 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3569
3705
|
vps.push(`echo "Commands:"`);
|
|
3570
3706
|
vps.push(`echo " pm2 status # Status gateway"`);
|
|
3571
3707
|
vps.push(`echo " pm2 logs openclaw-multibot"`);
|
|
3572
|
-
} else {
|
|
3573
|
-
appendShWriteCommands(vps, botFiles(0));
|
|
3574
|
-
if (is9Router) {
|
|
3575
|
-
vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-9router --interpreter "$(command -v node)"');
|
|
3576
|
-
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3577
|
-
}
|
|
3708
|
+
} else {
|
|
3709
|
+
appendShWriteCommands(vps, botFiles(0));
|
|
3710
|
+
if (is9Router) {
|
|
3711
|
+
vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$(npm root -g)/9router/app/server.js" --name openclaw-9router --interpreter "$(command -v node)"');
|
|
3712
|
+
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.openclaw/9router-smart-route-sync.js"');
|
|
3713
|
+
}
|
|
3578
3714
|
vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
|
|
3579
3715
|
vps.push('pm2 save && pm2 startup');
|
|
3580
3716
|
vps.push('echo "Bot dang chay! Xem log: pm2 logs openclaw"');
|
|
@@ -3881,14 +4017,14 @@ echo ""
|
|
|
3881
4017
|
|
|
3882
4018
|
script += `# \${isVi ? 'Tạo thư mục' : 'Create directories'}\n`;
|
|
3883
4019
|
Array.from(dirs).sort().forEach(dir => {
|
|
3884
|
-
script += `mkdir -p "
|
|
4020
|
+
script += `mkdir -p "${dir}"\n`;
|
|
3885
4021
|
});
|
|
3886
4022
|
script += '\n';
|
|
3887
4023
|
|
|
3888
4024
|
Object.entries(files).forEach(([path, content]) => {
|
|
3889
4025
|
script += `# \${path}\n`;
|
|
3890
4026
|
const contentStr = typeof content === 'string' ? content : '';
|
|
3891
|
-
script += `cat > "
|
|
4027
|
+
script += `cat > "${path}" << 'CLAWEOF'\n`;
|
|
3892
4028
|
script += contentStr;
|
|
3893
4029
|
if (!contentStr.endsWith('\n')) script += '\n';
|
|
3894
4030
|
script += `CLAWEOF\n\n`;
|
|
@@ -3899,6 +4035,7 @@ echo ""
|
|
|
3899
4035
|
script += `echo ""\n`;
|
|
3900
4036
|
script += `echo "\${isVi ? '🐳 Đang khởi động Docker (có thể mất vài phút)...' : '🐳 Starting Docker (may take a few minutes)...'}"\n`;
|
|
3901
4037
|
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`;
|
|
4038
|
+
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`;
|
|
3902
4039
|
|
|
3903
4040
|
if (isMultiBot) {
|
|
3904
4041
|
script += `cd "docker/openclaw"\n`;
|