create-openclaw-bot 5.3.0 → 5.3.4
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 +36 -0
- package/CHANGELOG.vi.md +36 -0
- package/README.md +11 -11
- package/README.vi.md +12 -4
- package/cli.js +426 -7
- package/package.json +1 -1
- package/patch-tray.js +7 -0
- package/setup.js +725 -77
- package/test-path.bat +4 -0
- package/tests/smoke-cli-logic.mjs +24 -15
- package/node_modules/color-convert/CHANGELOG.md +0 -54
package/setup.js
CHANGED
|
@@ -614,11 +614,34 @@
|
|
|
614
614
|
step2.querySelectorAll('.channel-card[data-channel]').forEach((c) => c.classList.remove('channel-card--selected'));
|
|
615
615
|
card.classList.add('channel-card--selected');
|
|
616
616
|
|
|
617
|
-
// Show multi-bot panel for Telegram and Telegram+Zalo combo
|
|
618
617
|
const multibotPanel = document.getElementById('multibot-panel');
|
|
619
|
-
if (
|
|
620
|
-
|
|
621
|
-
multibotPanel.style.display =
|
|
618
|
+
if (state.channel === 'telegram+zalo-personal') {
|
|
619
|
+
// Combo: hide the bot-count selector (fixed 1 Telegram bot), show 2 per-channel tabs
|
|
620
|
+
if (multibotPanel) multibotPanel.style.display = 'none';
|
|
621
|
+
// Reset to single-bot and ensure 2 bots in array (index 0 = Telegram, index 1 = Zalo)
|
|
622
|
+
state.botCount = 1;
|
|
623
|
+
while (state.bots.length < 2) {
|
|
624
|
+
state.bots.push({ name: '', slashCmd: '', desc: '', provider: state.bots[0]?.provider || 'google', model: state.bots[0]?.model || 'google/gemini-2.5-flash', token: '', apiKey: '' });
|
|
625
|
+
}
|
|
626
|
+
// Hide global identity-grid (each tab has own name/desc)
|
|
627
|
+
const identityGrid = document.querySelector('.identity-grid');
|
|
628
|
+
if (identityGrid) {
|
|
629
|
+
const nameField = identityGrid.querySelector('.form-group:has(#cfg-name)');
|
|
630
|
+
const descField = identityGrid.querySelector('.form-group:has(#cfg-desc)');
|
|
631
|
+
if (nameField) nameField.style.display = 'none';
|
|
632
|
+
if (descField) descField.style.display = 'none';
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
// Not combo: show multibot panel for telegram only
|
|
636
|
+
if (multibotPanel) multibotPanel.style.display = state.channel === 'telegram' ? '' : 'none';
|
|
637
|
+
// Restore global identity-grid visibility if was hidden by combo
|
|
638
|
+
const identityGrid = document.querySelector('.identity-grid');
|
|
639
|
+
if (identityGrid) {
|
|
640
|
+
const nameField = identityGrid.querySelector('.form-group:has(#cfg-name)');
|
|
641
|
+
const descField = identityGrid.querySelector('.form-group:has(#cfg-desc)');
|
|
642
|
+
if (nameField) nameField.style.display = '';
|
|
643
|
+
if (descField) descField.style.display = '';
|
|
644
|
+
}
|
|
622
645
|
}
|
|
623
646
|
|
|
624
647
|
updateNavButtons();
|
|
@@ -737,8 +760,40 @@
|
|
|
737
760
|
const slashGroup = document.getElementById('slash-cmd-group');
|
|
738
761
|
if (!tabBar || !tabsEl) return;
|
|
739
762
|
|
|
763
|
+
const isCombo = state.channel === 'telegram+zalo-personal';
|
|
764
|
+
|
|
740
765
|
tabBar.style.display = 'block';
|
|
741
766
|
|
|
767
|
+
// ── Combo mode: 2 fixed tabs (Telegram / Zalo Personal) ─────────────────
|
|
768
|
+
if (isCombo) {
|
|
769
|
+
tabsEl.style.display = 'flex';
|
|
770
|
+
if (labelEl) { labelEl.style.display = 'block'; labelEl.textContent = ''; }
|
|
771
|
+
if (slashGroup) slashGroup.style.display = 'none'; // slash cmd not relevant for Zalo
|
|
772
|
+
|
|
773
|
+
const COMBO_TABS = [
|
|
774
|
+
{ icon: '📨', labelVi: 'Telegram', labelEn: 'Telegram' },
|
|
775
|
+
{ icon: '💬', labelVi: 'Zalo Personal', labelEn: 'Zalo Personal' },
|
|
776
|
+
];
|
|
777
|
+
const lang = document.getElementById('cfg-language')?.value || 'vi';
|
|
778
|
+
|
|
779
|
+
tabsEl.innerHTML = COMBO_TABS.map((tab, i) => {
|
|
780
|
+
const isActive = i === state.activeBotIndex;
|
|
781
|
+
const customName = state.bots[i]?.name;
|
|
782
|
+
const labelText = customName ? customName : (lang === 'vi' ? tab.labelVi : tab.labelEn);
|
|
783
|
+
const label = `${tab.icon} ${labelText}`;
|
|
784
|
+
return `<button onclick="window.__switchBotTab(${i})" style="
|
|
785
|
+
padding:7px 18px;border-radius:8px;cursor:pointer;font-size:13px;font-weight:600;
|
|
786
|
+
border:1px solid ${isActive ? 'rgba(99,102,241,0.6)' : 'rgba(255,255,255,0.12)'};
|
|
787
|
+
background:${isActive ? 'rgba(99,102,241,0.2)' : 'transparent'};
|
|
788
|
+
color:${isActive ? 'var(--text-primary)' : 'var(--text-secondary)'};
|
|
789
|
+
transition:all 0.15s;">${label}</button>`;
|
|
790
|
+
}).join('');
|
|
791
|
+
|
|
792
|
+
syncBotTabMeta();
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ── Normal mode ──────────────────────────────────────────────────────────
|
|
742
797
|
if (state.botCount <= 1) {
|
|
743
798
|
tabsEl.style.display = 'none';
|
|
744
799
|
if (labelEl) labelEl.style.display = 'none';
|
|
@@ -1074,16 +1129,15 @@
|
|
|
1074
1129
|
if (state.currentStep === 2 && !state.channel) isDisabled = true;
|
|
1075
1130
|
// Step 3 (bot config): require at least one bot name
|
|
1076
1131
|
if (state.currentStep === 3) {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
//
|
|
1132
|
+
const isCombo = state.channel === 'telegram+zalo-personal';
|
|
1133
|
+
if (state.botCount > 1 || isCombo) {
|
|
1134
|
+
// Multi-bot or combo: require name for the currently active bot tab
|
|
1080
1135
|
const activeTab = state.activeBotIndex || 0;
|
|
1081
1136
|
const tabNameVal = document.getElementById('cfg-bot-tab-name')?.value?.trim()
|
|
1082
1137
|
|| state.bots[activeTab]?.name?.trim();
|
|
1083
1138
|
if (!tabNameVal) isDisabled = true;
|
|
1084
1139
|
} else {
|
|
1085
1140
|
// Single bot: require cfg-name or the shared tab name field
|
|
1086
|
-
// Fallback to state.config.botName for cases where the DOM field was cleared on re-render
|
|
1087
1141
|
const nameVal = document.getElementById('cfg-name')?.value?.trim()
|
|
1088
1142
|
|| document.getElementById('cfg-bot-tab-name')?.value?.trim()
|
|
1089
1143
|
|| state.config.botName?.trim();
|
|
@@ -1093,12 +1147,13 @@
|
|
|
1093
1147
|
// Step 4 (api keys): require token/key
|
|
1094
1148
|
if (state.currentStep === 4) {
|
|
1095
1149
|
const provider = PROVIDERS[state.config.provider];
|
|
1096
|
-
|
|
1150
|
+
const hasTelegramCh = state.channel === 'telegram' || state.channel === 'telegram+zalo-personal';
|
|
1151
|
+
if (hasTelegramCh && state.botCount > 1) {
|
|
1097
1152
|
// Multi-bot: check DOM first, fallback to state (works even before user types)
|
|
1098
1153
|
const firstTokenEl = document.getElementById('key-bot-token-0');
|
|
1099
1154
|
const firstTokenVal = firstTokenEl?.value?.trim() || state.bots[0]?.token?.trim() || '';
|
|
1100
1155
|
if (!firstTokenVal) isDisabled = true;
|
|
1101
|
-
} else if (
|
|
1156
|
+
} else if (hasTelegramCh || state.channel === 'zalo-bot') {
|
|
1102
1157
|
const botTokenEl = document.getElementById('key-bot-token');
|
|
1103
1158
|
const botTokenVal = botTokenEl?.value?.trim() || state.config.botToken?.trim() || '';
|
|
1104
1159
|
if (!botTokenVal) isDisabled = true;
|
|
@@ -1430,9 +1485,13 @@
|
|
|
1430
1485
|
// Also save bot-tab-name → bots[0].name so both state locations stay in sync
|
|
1431
1486
|
// Save bot-tab-name to the ACTIVE bot (not always bots[0])
|
|
1432
1487
|
const tabName = document.getElementById('cfg-bot-tab-name')?.value?.trim();
|
|
1488
|
+
const isCombo = state.channel === 'telegram+zalo-personal';
|
|
1433
1489
|
if (tabName && state.bots[state.activeBotIndex]) {
|
|
1434
1490
|
state.bots[state.activeBotIndex].name = tabName;
|
|
1435
|
-
|
|
1491
|
+
// For single-bot or combo (use Telegram tab = index 0 as primary name)
|
|
1492
|
+
if (state.botCount <= 1 || isCombo) {
|
|
1493
|
+
if (!isCombo || state.activeBotIndex === 0) state.config.botName = tabName;
|
|
1494
|
+
}
|
|
1436
1495
|
} else if (state.config.botName && state.bots[0] && !state.bots[0].name) {
|
|
1437
1496
|
state.bots[0].name = state.config.botName;
|
|
1438
1497
|
}
|
|
@@ -1450,9 +1509,9 @@
|
|
|
1450
1509
|
if (botTokenEl) state.config.botToken = botTokenEl.value;
|
|
1451
1510
|
if (apiKeyEl) state.config.apiKey = apiKeyEl.value;
|
|
1452
1511
|
if (pathEl) state.config.projectPath = pathEl.value;
|
|
1453
|
-
if (state.botCount <= 1 && state.bots[
|
|
1454
|
-
if (botTokenEl) state.bots[
|
|
1455
|
-
if (apiKeyEl) state.bots[
|
|
1512
|
+
if (state.botCount <= 1 && state.bots[0]) {
|
|
1513
|
+
if (botTokenEl) state.bots[0].token = botTokenEl.value;
|
|
1514
|
+
if (apiKeyEl) state.bots[0].apiKey = apiKeyEl.value;
|
|
1456
1515
|
}
|
|
1457
1516
|
|
|
1458
1517
|
// Also save multi-bot tokens individually
|
|
@@ -1547,7 +1606,10 @@
|
|
|
1547
1606
|
cHtml += `<div style="padding: 16px 20px; border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; background: rgba(255,255,255,0.02);">`;
|
|
1548
1607
|
cHtml += `<h3 style="margin: 0 0 12px; font-size: 15px; font-weight: 700; color: var(--text-primary);">${channelIcon} ${isVi ? 'Kênh chat' : 'Chat Channel'} — ${channelName}</h3>`;
|
|
1549
1608
|
|
|
1550
|
-
|
|
1609
|
+
const hasTelegramChKey = state.channel === 'telegram' || state.channel === 'telegram+zalo-personal';
|
|
1610
|
+
const hasZaloPersonalChKey = state.channel === 'zalo-personal' || state.channel === 'telegram+zalo-personal';
|
|
1611
|
+
|
|
1612
|
+
if (hasTelegramChKey) {
|
|
1551
1613
|
if (state.botCount > 1) {
|
|
1552
1614
|
// Multi-bot: one token per bot
|
|
1553
1615
|
cHtml += `<div style="display:flex;flex-direction:column;gap:12px;">`;
|
|
@@ -1571,14 +1633,16 @@
|
|
|
1571
1633
|
<p class="form-group__hint">${isVi ? 'Lấy từ <a href="https://t.me/BotFather" target="_blank">@BotFather</a> trên Telegram' : 'Get from <a href="https://t.me/BotFather" target="_blank">@BotFather</a> on Telegram'}</p>
|
|
1572
1634
|
</div>`;
|
|
1573
1635
|
}
|
|
1574
|
-
}
|
|
1636
|
+
}
|
|
1637
|
+
if (state.channel === 'zalo-bot') {
|
|
1575
1638
|
cHtml += `<div class="form-group" style="margin: 0;">
|
|
1576
1639
|
<label class="form-group__label" for="key-bot-token">🔑 Zalo Bot Token <span style="color: var(--danger, #ef4444);">*</span></label>
|
|
1577
1640
|
<input type="text" class="form-input" id="key-bot-token" placeholder="Zalo Bot Token" style="font-family: monospace; font-size: 13px;" oninput="window.__validateKeys()">
|
|
1578
1641
|
<p class="form-group__hint">${isVi ? 'Lấy từ <a href="https://developers.zalo.me" target="_blank">Zalo Bot Platform</a>' : 'Get from <a href="https://developers.zalo.me" target="_blank">Zalo Bot Platform</a>'}</p>
|
|
1579
1642
|
</div>`;
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1643
|
+
}
|
|
1644
|
+
if (hasZaloPersonalChKey) {
|
|
1645
|
+
cHtml += `<div style="display: flex; gap: 8px; align-items: flex-start; padding: 12px 14px; background: rgba(245,158,11,0.06); border: 1px solid rgba(245,158,11,0.2); border-radius: 8px; font-size: 13px; color: var(--warning); margin: ${hasTelegramChKey ? '12px 0 0' : '0'};">
|
|
1582
1646
|
<span style="font-size: 16px; margin-top: -2px;">⚠️</span>
|
|
1583
1647
|
<span style="line-height: 1.5;">${isVi
|
|
1584
1648
|
? '<strong>Zalo Personal</strong> sử dụng unofficial API (zca-js). Tài khoản Zalo của bạn có thể bị hạn chế hoặc khóa. Chỉ nên dùng với tài khoản phụ.'
|
|
@@ -1657,7 +1721,8 @@
|
|
|
1657
1721
|
}
|
|
1658
1722
|
|
|
1659
1723
|
// Bot tokens
|
|
1660
|
-
|
|
1724
|
+
const hasTelegramForEnv = state.channel === 'telegram' || state.channel === 'telegram+zalo-personal';
|
|
1725
|
+
if (hasTelegramForEnv && state.botCount > 1) {
|
|
1661
1726
|
// Multi-bot: one env var per bot
|
|
1662
1727
|
lines.push('');
|
|
1663
1728
|
lines.push('# Multi-bot Telegram tokens');
|
|
@@ -1702,6 +1767,8 @@
|
|
|
1702
1767
|
}
|
|
1703
1768
|
|
|
1704
1769
|
|
|
1770
|
+
const openClawRuntimePackages = 'grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api';
|
|
1771
|
+
|
|
1705
1772
|
// ========== Step 4: Generate Output ==========
|
|
1706
1773
|
function generateOutput() {
|
|
1707
1774
|
const ch = CHANNELS[state.channel];
|
|
@@ -1717,9 +1784,8 @@
|
|
|
1717
1784
|
|
|
1718
1785
|
const is9Router = provider.isProxy;
|
|
1719
1786
|
const isLocal = provider.isLocal;
|
|
1720
|
-
const isTelegramMultiBot = state.botCount > 1 && state.channel === 'telegram';
|
|
1787
|
+
const isTelegramMultiBot = state.botCount > 1 && (state.channel === 'telegram' || state.channel === 'telegram+zalo-personal');
|
|
1721
1788
|
const relayPluginSpec = 'openclaw-telegram-multibot-relay';
|
|
1722
|
-
const openClawRuntimePackages = 'grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api';
|
|
1723
1789
|
|
|
1724
1790
|
function buildRelayPluginInstallCommand(prefix) {
|
|
1725
1791
|
return `${prefix} plugins install ${relayPluginSpec} 2>/dev/null || true`;
|
|
@@ -1867,7 +1933,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
1867
1933
|
},
|
|
1868
1934
|
list: [{
|
|
1869
1935
|
id: agentId,
|
|
1870
|
-
workspace:
|
|
1936
|
+
workspace: `.openclaw/workspace-${agentId}`,
|
|
1871
1937
|
agentDir: `agents/${agentId}/agent`,
|
|
1872
1938
|
model: { primary: state.config.model, fallbacks: [] },
|
|
1873
1939
|
}],
|
|
@@ -1984,7 +2050,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
1984
2050
|
clawConfig.agents.list = multiBotAgentMetas.map((meta) => ({
|
|
1985
2051
|
id: meta.agentId,
|
|
1986
2052
|
name: meta.name,
|
|
1987
|
-
workspace: meta.workspaceDir,
|
|
2053
|
+
workspace: '.openclaw/' + meta.workspaceDir,
|
|
1988
2054
|
agentDir: `agents/${meta.agentId}/agent`,
|
|
1989
2055
|
model: { primary: state.config.model, fallbacks: [] },
|
|
1990
2056
|
}));
|
|
@@ -2159,7 +2225,7 @@ ${finalCmd}`;
|
|
|
2159
2225
|
|
|
2160
2226
|
setOutput('out-dockerfile', dockerfile);
|
|
2161
2227
|
|
|
2162
|
-
const isMultiBotWizard = state.botCount > 1 && state.channel === 'telegram';
|
|
2228
|
+
const isMultiBotWizard = state.botCount > 1 && (state.channel === 'telegram' || state.channel === 'telegram+zalo-personal');
|
|
2163
2229
|
|
|
2164
2230
|
// 4. docker-compose.yml
|
|
2165
2231
|
// extra_hosts always needed for browser (socat → host Chrome)
|
|
@@ -2490,12 +2556,13 @@ docker logs -f openclaw-bot${approveNote}`);
|
|
|
2490
2556
|
// 7. Generate ALL workspace Markdown files
|
|
2491
2557
|
// OpenClaw auto-injects these into agent context at the start of every session.
|
|
2492
2558
|
// Hierarchy: per-agent files → global workspace files → config defaults.
|
|
2493
|
-
const
|
|
2559
|
+
const isComboChannel = state.channel === 'telegram+zalo-personal';
|
|
2560
|
+
const botName = (isMultiBotWizard || isComboChannel)
|
|
2494
2561
|
? (state.bots[0]?.name || state.config.botName || 'Chat Bot')
|
|
2495
2562
|
: (state.config.botName || 'Chat Bot');
|
|
2496
2563
|
const lang = state.config.language || 'vi';
|
|
2497
2564
|
const userPrompt = state.config.systemPrompt || '';
|
|
2498
|
-
const descText = isMultiBotWizard
|
|
2565
|
+
const descText = (isMultiBotWizard || isComboChannel)
|
|
2499
2566
|
? (state.bots[0]?.desc || state.config.description || (lang === 'vi' ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'))
|
|
2500
2567
|
: (state.config.description || (lang === 'vi' ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'));
|
|
2501
2568
|
|
|
@@ -2955,7 +3022,7 @@ fi
|
|
|
2955
3022
|
botConfig.agents.defaults.model = { primary: state.config.model, fallbacks: [] };
|
|
2956
3023
|
botConfig.agents.list = [{
|
|
2957
3024
|
id: botAgentId,
|
|
2958
|
-
workspace:
|
|
3025
|
+
workspace: `.openclaw/workspace-${botAgentId}`,
|
|
2959
3026
|
agentDir: `agents/${botAgentId}/agent`,
|
|
2960
3027
|
model: { primary: state.config.model, fallbacks: [] },
|
|
2961
3028
|
}];
|
|
@@ -3173,6 +3240,7 @@ I am **${botName}**. When asked my name, I answer: _"I'm ${botName}"_.`;
|
|
|
3173
3240
|
.filter((skill) => skill && skill.id !== 'scheduler' && skill.slug && skill.slug !== 'browser-automation');
|
|
3174
3241
|
const selectedModel = (state.config.model || 'ollama/gemma4:e2b').replace('ollama/', '');
|
|
3175
3242
|
const isMultiBot = state.botCount > 1 && state.channel === 'telegram';
|
|
3243
|
+
const isComboChannel = state.channel === 'telegram+zalo-personal';
|
|
3176
3244
|
const projectDir = state.config.projectPath || '.';
|
|
3177
3245
|
const todayStamp = new Date().toISOString().slice(0, 10);
|
|
3178
3246
|
|
|
@@ -3201,30 +3269,76 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3201
3269
|
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);\"";
|
|
3202
3270
|
}
|
|
3203
3271
|
|
|
3204
|
-
function windowsHiddenNodeLaunch(targetPath, extraEnv = {}) {
|
|
3205
|
-
|
|
3206
|
-
return `'${String(value).replace(/'/g, "''")}'`;
|
|
3207
|
-
}
|
|
3272
|
+
function windowsHiddenNodeLaunch(targetPath, extraEnv = {}, extraArgs = []) {
|
|
3273
|
+
// Set env vars via $env: prefix (PS5/PS7 compatible, -Environment flag is PS7+ only)
|
|
3208
3274
|
const envAssignments = Object.entries(extraEnv)
|
|
3209
|
-
.map(([
|
|
3210
|
-
.join('
|
|
3211
|
-
|
|
3275
|
+
.map(([k, v]) => `$env:${k}='${String(v).replace(/'/g, "''")}'; `)
|
|
3276
|
+
.join('');
|
|
3277
|
+
const safePath = targetPath.replace(/\\/g, '\\\\').replace(/'/g, "''");
|
|
3278
|
+
const argList = [`'${safePath}'`, ...extraArgs.map(a => `'${String(a).replace(/'/g, "''")}' `)].join(',');
|
|
3279
|
+
return `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${envAssignments}Start-Process -WindowStyle Hidden -FilePath (Get-Command node).Source -ArgumentList @(${argList})"`;
|
|
3212
3280
|
}
|
|
3213
3281
|
|
|
3214
3282
|
// ─── Shared initializer (provider install) ───────────────────────────────
|
|
3215
3283
|
function providerLines(arr, shell) {
|
|
3216
3284
|
if (is9Router) {
|
|
3217
3285
|
if (shell === 'bat') {
|
|
3286
|
+
arr.push(':: Dung 9Router dang chay (neu co) - tranh loi EBUSY khi npm cap nhat file dang bi lock');
|
|
3287
|
+
arr.push('wmic process where "Name=\'node.exe\' and CommandLine like \'%%9router%%\'" delete >nul 2>&1');
|
|
3288
|
+
arr.push('wmic process where "Name=\'cmd.exe\' and CommandLine like \'%%9router%%\'" delete >nul 2>&1');
|
|
3289
|
+
arr.push('timeout /t 3 /nobreak >nul');
|
|
3218
3290
|
arr.push('call npm install -g 9router || goto :fail');
|
|
3219
|
-
arr.push(
|
|
3220
|
-
|
|
3221
|
-
|
|
3291
|
+
arr.push('echo [OK] 9Router da duoc cai dat thanh cong.');
|
|
3292
|
+
// Pre-create DATA_DIR and seed db.json with requireLogin:false BEFORE starting 9router.
|
|
3293
|
+
// If db.json is missing when 9router boots, it defaults to ~/.9router and requireLogin:true,
|
|
3294
|
+
// blocking the dashboard. Must be done BEFORE the `start` command below.
|
|
3295
|
+
arr.push('if not exist "%DATA_DIR%" mkdir "%DATA_DIR%"');
|
|
3296
|
+
arr.push('if not exist "%DATA_DIR%\\db.json" (');
|
|
3297
|
+
arr.push('> "%DATA_DIR%\\db.json" (');
|
|
3298
|
+
arr.push('echo({');
|
|
3299
|
+
arr.push('echo( "providerConnections": [],');
|
|
3300
|
+
arr.push('echo( "providerNodes": [],');
|
|
3301
|
+
arr.push('echo( "proxyPools": [],');
|
|
3302
|
+
arr.push('echo( "modelAliases": {},');
|
|
3303
|
+
arr.push('echo( "mitmAlias": {},');
|
|
3304
|
+
arr.push('echo( "combos": [],');
|
|
3305
|
+
arr.push('echo( "apiKeys": [],');
|
|
3306
|
+
arr.push('echo( "settings": {');
|
|
3307
|
+
arr.push('echo( "requireLogin": false,');
|
|
3308
|
+
arr.push('echo( "cloudEnabled": false,');
|
|
3309
|
+
arr.push('echo( "tunnelEnabled": false,');
|
|
3310
|
+
arr.push('echo( "comboStrategy": "fallback",');
|
|
3311
|
+
arr.push('echo( "mitmRouterBaseUrl": "http://localhost:20128"');
|
|
3312
|
+
arr.push('echo( },');
|
|
3313
|
+
arr.push('echo( "pricing": {}');
|
|
3314
|
+
arr.push('echo(}');
|
|
3315
|
+
arr.push(')');
|
|
3316
|
+
arr.push(')');
|
|
3317
|
+
// Launch 9Router as a fully detached process via PowerShell Start-Process -WindowStyle Hidden.
|
|
3318
|
+
// Using `start ... cmd /c "...--tray"` caused 9Router to die when the CMD window was closed
|
|
3319
|
+
// because cmd /c is a child of the started CMD, which is killed when that window closes.
|
|
3320
|
+
// Start-Process with WindowStyle Hidden creates a truly independent process that survives
|
|
3321
|
+
// even if all visible terminal windows are closed.
|
|
3322
|
+
// Write a temp .ps1 launcher to avoid CMD->PS quoting issues.
|
|
3323
|
+
// Start-Process cannot run .cmd files directly — cmd.exe must be the FilePath.
|
|
3324
|
+
arr.push('echo Khoi dong 9Router (background)...');
|
|
3325
|
+
arr.push('echo $env:DATA_DIR = \'%DATA_DIR%\' > "%TEMP%\\oc-start9r.ps1"');
|
|
3326
|
+
arr.push('echo $b = Join-Path $env:APPDATA \'npm\\9router.cmd\' >> "%TEMP%\\oc-start9r.ps1"');
|
|
3327
|
+
arr.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\9router\' } >> "%TEMP%\\oc-start9r.ps1"');
|
|
3328
|
+
arr.push(`echo Start-Process 'cmd.exe' -WindowStyle Hidden -WorkingDirectory '${projectDir}' -ArgumentList ^('/c "' + $b + '" -n -H 0.0.0.0 -p 20128 --skip-update'^) >> "%TEMP%\\oc-start9r.ps1"`);
|
|
3329
|
+
arr.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-start9r.ps1"');
|
|
3330
|
+
arr.push('del "%TEMP%\\oc-start9r.ps1" >nul 2>&1');
|
|
3331
|
+
arr.push('timeout /t 8 /nobreak >nul');
|
|
3222
3332
|
} else {
|
|
3223
3333
|
arr.push('npm install -g 9router');
|
|
3224
|
-
|
|
3225
|
-
arr.push('
|
|
3226
|
-
arr.push('
|
|
3227
|
-
arr.push('
|
|
3334
|
+
// Pre-seed .9router/db.json before starting 9router (prevents requireLogin:true on first boot)
|
|
3335
|
+
arr.push('mkdir -p ".9router"');
|
|
3336
|
+
arr.push('if [ ! -f ".9router/db.json" ]; then cat > ".9router/db.json" << \'DBJSON\'\n{\n "providerConnections": [],\n "providerNodes": [],\n "proxyPools": [],\n "modelAliases": {},\n "mitmAlias": {},\n "combos": [],\n "apiKeys": [],\n "settings": {\n "requireLogin": false,\n "cloudEnabled": false,\n "tunnelEnabled": false,\n "comboStrategy": "fallback",\n "mitmRouterBaseUrl": "http://localhost:20128"\n },\n "pricing": {}\n}\nDBJSON\nfi');
|
|
3337
|
+
arr.push('NINE_ROUTER_BIN="$(command -v 9router)"');
|
|
3338
|
+
// NOTE: -l (stdin listen mode) intentionally omitted — causes hangs in non-TTY environments
|
|
3339
|
+
arr.push('nohup env PORT=20128 HOSTNAME=0.0.0.0 DATA_DIR="$PWD/.9router" "$NINE_ROUTER_BIN" -n -H 0.0.0.0 -p 20128 --skip-update > /tmp/9router.log 2>&1 &');
|
|
3340
|
+
arr.push('nohup env DATA_DIR="$PWD/.9router" node ./.9router/9router-smart-route-sync.js > /tmp/9router-sync.log 2>&1 &');
|
|
3341
|
+
arr.push('sleep 5');
|
|
3228
3342
|
}
|
|
3229
3343
|
} else if (isOllama) {
|
|
3230
3344
|
if (shell === 'bat') {
|
|
@@ -3340,7 +3454,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3340
3454
|
list: multiBotAgentMetas.map((meta) => ({
|
|
3341
3455
|
id: meta.agentId,
|
|
3342
3456
|
name: meta.name,
|
|
3343
|
-
workspace: meta.workspaceDir,
|
|
3457
|
+
workspace: '.openclaw/' + meta.workspaceDir,
|
|
3344
3458
|
agentDir: `agents/${meta.agentId}/agent`,
|
|
3345
3459
|
model: { primary: state.config.model, fallbacks: [] },
|
|
3346
3460
|
})),
|
|
@@ -3407,7 +3521,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3407
3521
|
'.openclaw/auth-profiles.json': sharedNativeAuthProfilesContent(),
|
|
3408
3522
|
'TELEGRAM-POST-INSTALL.md': buildTelegramPostInstallChecklist(),
|
|
3409
3523
|
};
|
|
3410
|
-
if (is9Router) files['.
|
|
3524
|
+
if (is9Router) files['.9router/9router-smart-route-sync.js'] = native9RouterSyncScriptContent();
|
|
3411
3525
|
const teamMd = isVi
|
|
3412
3526
|
? `# Doi Bot\n\n${multiBotAgentMetas.map((meta) => `## ${meta.name}\n- Vai tro: ${meta.desc}\n- Agent ID: \`${meta.agentId}\`\n- Telegram accountId: \`${meta.accountId}\`\n- Slash command: ${meta.slashCmd || '_(chua co)_'}\n- Tinh cach: ${meta.persona || '_(khong ghi ro)_'}`).join('\n\n')}\n\n## Quy uoc phoi hop\n- Tat ca bot trong doi biet ro vai tro cua nhau.\n- Neu user bao ban hoi mot bot khac, hay dung agent-to-agent noi bo thay vi doi Telegram chuyen tin cua bot.\n- Bot mo loi chi noi 1 cau ngan, sau do chuyen turn noi bo cho bot dich.\n- Bot dich phai tra loi cong khai bang chinh Telegram account cua minh trong cung chat/thread hien tai.\n- Neu can fallback, chi bot mo loi moi duoc phep tom tat thay.`
|
|
3413
3527
|
: `# Bot Team\n\n${multiBotAgentMetas.map((meta) => `## ${meta.name}\n- Role: ${meta.desc}\n- Agent ID: \`${meta.agentId}\`\n- Telegram accountId: \`${meta.accountId}\`\n- Slash command: ${meta.slashCmd || '_(not set)_'}\n- Persona: ${meta.persona || '_(not specified)_'}`).join('\n\n')}\n\n## Coordination Rules\n- Every bot knows the full roster.\n- If the user asks you to consult another bot, use internal agent-to-agent handoff instead of waiting for Telegram bot-to-bot delivery.\n- The caller bot only sends one short opener, then hands off internally.\n- The target bot must publish the real answer with its own Telegram account in the same chat/thread.\n- If a fallback is needed, only the caller bot may summarize on behalf of the target.`;
|
|
@@ -3461,7 +3575,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3461
3575
|
// ─── Per-bot ENV content ──────────────────────────────────────────────────
|
|
3462
3576
|
function botEnvContent(botIndex) {
|
|
3463
3577
|
const bot = state.bots[botIndex] || {};
|
|
3464
|
-
const botProvider = PROVIDERS[bot.provider] || provider;
|
|
3578
|
+
const botProvider = (provider && provider.isProxy) ? provider : (PROVIDERS[bot.provider] || provider);
|
|
3465
3579
|
const lines = [];
|
|
3466
3580
|
if (botProvider.isProxy) {
|
|
3467
3581
|
lines.push('# 9Router: no API key needed');
|
|
@@ -3485,21 +3599,25 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3485
3599
|
const agentId = botName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
3486
3600
|
const basePort = 18791 + botIndex;
|
|
3487
3601
|
const groupId = state.groupId || '';
|
|
3488
|
-
|
|
3602
|
+
|
|
3603
|
+
// Force use global provider if proxy mode is chosen globally, else use bot specific provider
|
|
3604
|
+
const botProvider = (provider && provider.isProxy) ? provider : (PROVIDERS[bot.provider] || provider);
|
|
3605
|
+
const actualModel = botProvider.isProxy ? provider.models[0].id : (bot.model || state.config.model);
|
|
3606
|
+
|
|
3489
3607
|
const cfg = {
|
|
3490
3608
|
meta: { lastTouchedVersion: '2026.3.24' },
|
|
3491
3609
|
agents: {
|
|
3492
3610
|
defaults: {
|
|
3493
|
-
model: { primary:
|
|
3611
|
+
model: { primary: actualModel },
|
|
3494
3612
|
compaction: { mode: 'safeguard' },
|
|
3495
3613
|
timeoutSeconds: botProvider.isLocal ? 900 : 120,
|
|
3496
3614
|
...(botProvider.isLocal ? { llm: { idleTimeoutSeconds: 300 } } : {}),
|
|
3497
3615
|
},
|
|
3498
3616
|
list: [{
|
|
3499
3617
|
id: agentId,
|
|
3500
|
-
workspace:
|
|
3618
|
+
workspace: `.openclaw/workspace-${agentId}`,
|
|
3501
3619
|
agentDir: `agents/${agentId}/agent`,
|
|
3502
|
-
model: { primary:
|
|
3620
|
+
model: { primary: actualModel }
|
|
3503
3621
|
}],
|
|
3504
3622
|
},
|
|
3505
3623
|
...(botProvider.isProxy ? {
|
|
@@ -3568,7 +3686,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3568
3686
|
cfg.plugins = { ...(cfg.plugins || {}), slots: { ...((cfg.plugins && cfg.plugins.slots) || {}), memory: 'none' } };
|
|
3569
3687
|
}
|
|
3570
3688
|
|
|
3571
|
-
if (state.channel === 'telegram') {
|
|
3689
|
+
if (state.channel === 'telegram' || state.channel === 'telegram+zalo-personal') {
|
|
3572
3690
|
cfg.channels.telegram = {
|
|
3573
3691
|
enabled: true,
|
|
3574
3692
|
dmPolicy: 'open',
|
|
@@ -3584,7 +3702,9 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3584
3702
|
},
|
|
3585
3703
|
};
|
|
3586
3704
|
}
|
|
3587
|
-
}
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
if (state.channel === 'zalo-personal' || state.channel === 'telegram+zalo-personal') {
|
|
3588
3708
|
cfg.channels.zalouser = {
|
|
3589
3709
|
enabled: true,
|
|
3590
3710
|
dmPolicy: 'open',
|
|
@@ -3767,6 +3887,7 @@ You are **${botName}**, ${botDesc.toLowerCase()}.
|
|
|
3767
3887
|
- Prefer English unless user uses another language
|
|
3768
3888
|
- When asked your name: _"I'm ${botName}"_
|
|
3769
3889
|
- Never fabricate information`;
|
|
3890
|
+
const _secRules = state.config.securityRules || DEFAULT_SECURITY_RULES[isVi ? 'vi' : 'en'];
|
|
3770
3891
|
const extraAgentsMd = isVi
|
|
3771
3892
|
? `\n\n## Khi nao nen tra loi\n- Trong group, chi tra loi khi tin nhan co alias cua ban: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')} hoac username Telegram cua ban.\n- Neu tin nhan khong goi ban, hay im lang hoan toan.\n- Neu tin nhan chi goi ro bot khac ${otherBotNames.length ? otherBotNames.map((name) => `\`${name}\``).join(', ') : '`bot khac`'} thi khong cuop loi.\n- Khi da biet user dang goi ban, hay tha reaction co dinh \`👍\` truoc roi moi tra loi bang text. Khong dung emoji khac.\n- Khi can phoi hop noi bo, dung dung agent id ky thuat trong \`TEAM.md\`, khong dung ten hien thi.\n- Khi hoi ve vai tro cac bot, dung \`TEAM.md\` lam nguon su that.`
|
|
3772
3893
|
: `\n\n## When To Reply\n- In group chats, only reply when the message contains one of your aliases: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')} or your Telegram username.\n- If the message is not calling you, stay completely silent.\n- If the message is clearly calling another bot such as ${otherBotNames.length ? otherBotNames.map((name) => `\`${name}\``).join(', ') : '`another bot`'}, do not hijack it.\n- Once you know the user is calling you, add the fixed reaction \`👍\` first, then send the text reply. Do not use any other reaction emoji.\n- When you need internal coordination, use the exact technical agent id from \`TEAM.md\`, not the display name.\n- Use \`TEAM.md\` as the source of truth for team roles.`;
|
|
@@ -3795,7 +3916,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(Chưa có ski
|
|
|
3795
3916
|
## Quy ước
|
|
3796
3917
|
- Ưu tiên dùng tool thay vì đoán
|
|
3797
3918
|
- Browser: dùng khi user yêu cầu thao tác web
|
|
3798
|
-
- Memory: cập nhật khi biết thông tin quan trọng
|
|
3919
|
+
- Memory: cập nhật khi biết thông tin quan trọng\n\n## Ghi chú thiết lập của bạn\n\nGhi lại cấu hình riêng của môi trường bạn, ví dụ:\n- Tên thiết bị, camera, SSH hosts\n- Giọng nói ưa thích (TTS)\n- Alias và shortcut\n\n---\n\nThêm ghi chú nào giúp ích cho công việc của bạn.`
|
|
3799
3920
|
: `# Tool Usage Guide
|
|
3800
3921
|
|
|
3801
3922
|
## Installed Skills
|
|
@@ -3804,7 +3925,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3804
3925
|
## Conventions
|
|
3805
3926
|
- Prefer tools over guessing
|
|
3806
3927
|
- Use Browser for explicit web tasks
|
|
3807
|
-
- Update Memory when important user info appears
|
|
3928
|
+
- Update Memory when important user info appears\n\n## Your Setup Notes\n\nRecord environment-specific config, e.g.:\n- Device names, cameras, SSH hosts\n- Preferred TTS voice\n- Aliases and shortcuts\n\n---\n\nAdd whatever helps you do your job.`;
|
|
3808
3929
|
const memoryMd = isVi
|
|
3809
3930
|
? `# Bộ nhớ dài hạn
|
|
3810
3931
|
|
|
@@ -3817,7 +3938,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3817
3938
|
const files = {
|
|
3818
3939
|
'IDENTITY.md': identityMd,
|
|
3819
3940
|
'SOUL.md': soulMd,
|
|
3820
|
-
'AGENTS.md': agentsMd + extraAgentsMd,
|
|
3941
|
+
'AGENTS.md': agentsMd + extraAgentsMd + '\n\n' + _secRules,
|
|
3821
3942
|
'TEAM.md': teamMd,
|
|
3822
3943
|
'USER.md': userMd,
|
|
3823
3944
|
'TOOLS.md': toolsMd,
|
|
@@ -3842,11 +3963,11 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3842
3963
|
files[`${base}/.openclaw/openclaw.json`] = botConfigContent(botIndex);
|
|
3843
3964
|
files[`${base}/.openclaw/exec-approvals.json`] = botExecApprovalsContent(botIndex);
|
|
3844
3965
|
files[`${base}/.openclaw/auth-profiles.json`] = botAuthProfilesContent(botIndex);
|
|
3845
|
-
if (is9Router) files[`${base}/.
|
|
3966
|
+
if (is9Router) files[`${base}/.9router/9router-smart-route-sync.js`] = native9RouterSyncScriptContent();
|
|
3846
3967
|
files[`${base}/.openclaw/agents/${agentId}.yaml`] = botAgentYamlContent(botIndex);
|
|
3847
3968
|
files[`${base}/.openclaw/agents/${agentId}/agent/auth-profiles.json`] = botAuthProfilesContent(botIndex);
|
|
3848
3969
|
Object.entries(botWorkspaceFiles(botIndex)).forEach(([name, content]) => {
|
|
3849
|
-
files[`${base}/.openclaw/workspace/${name}`] = content;
|
|
3970
|
+
files[`${base}/.openclaw/workspace-${agentId}/${name}`] = content;
|
|
3850
3971
|
});
|
|
3851
3972
|
return files;
|
|
3852
3973
|
}
|
|
@@ -3888,6 +4009,9 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3888
4009
|
return Object.fromEntries(Object.entries(files).map(([relPath, content]) => {
|
|
3889
4010
|
const normalized = relPath.replace(/\\/g, '/');
|
|
3890
4011
|
if (normalized === '.env') return ['%PROJECT_DIR%\\.env', content];
|
|
4012
|
+
if (normalized.startsWith('.9router/')) {
|
|
4013
|
+
return [`%DATA_DIR%\\${normalized.slice('.9router/'.length).replace(/\//g, '\\')}`, content];
|
|
4014
|
+
}
|
|
3891
4015
|
if (normalized.startsWith('.openclaw/')) {
|
|
3892
4016
|
return [`%OPENCLAW_HOME%\\${normalized.slice('.openclaw/'.length).replace(/\//g, '\\')}`, content];
|
|
3893
4017
|
}
|
|
@@ -3913,12 +4037,15 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3913
4037
|
'set "OPENCLAW_STATE_DIR=%PROJECT_DIR%\\.openclaw"',
|
|
3914
4038
|
'set "DATA_DIR=%PROJECT_DIR%\\.9router"',
|
|
3915
4039
|
'set "PATH=%APPDATA%\\npm;%PATH%"',
|
|
4040
|
+
':: Fix PowerShell ExecutionPolicy so .ps1 wrappers (openclaw, 9router) can run',
|
|
4041
|
+
'powershell -NoProfile -Command "Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force" >nul 2>&1',
|
|
3916
4042
|
`echo === OpenClaw Setup — Windows${isDocker ? ' Docker' : ' Native'} ===`,
|
|
3917
4043
|
'echo.',
|
|
3918
4044
|
'echo [1/5] Kiem tra Node.js...',
|
|
3919
4045
|
'where node >nul 2>&1 || (echo ERROR: Node.js chua cai! Tai tai: https://nodejs.org && pause && exit /b 1)',
|
|
3920
4046
|
'echo [2/5] Cai OpenClaw CLI...',
|
|
3921
4047
|
`call npm install -g openclaw@2026.4.5 ${openClawRuntimePackages} || goto :fail`,
|
|
4048
|
+
'echo [OK] OpenClaw da duoc cai dat thanh cong.',
|
|
3922
4049
|
];
|
|
3923
4050
|
providerLines(lines, 'bat');
|
|
3924
4051
|
if (hasBrowser) {
|
|
@@ -3932,12 +4059,14 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3932
4059
|
}
|
|
3933
4060
|
if (pluginCmd) { lines.push('echo Cai plugins...'); lines.push(pluginCmd); }
|
|
3934
4061
|
lines.push('if not exist "%OPENCLAW_HOME%" mkdir "%OPENCLAW_HOME%"');
|
|
3935
|
-
|
|
4062
|
+
// DATA_DIR creation + db.json pre-seeding is handled inside providerLines() for 9Router.
|
|
4063
|
+
// For non-9Router providers we still ensure the folder exists.
|
|
4064
|
+
if (!is9Router) lines.push('if not exist "%DATA_DIR%" mkdir "%DATA_DIR%"');
|
|
3936
4065
|
|
|
3937
4066
|
if (isMultiBot) {
|
|
3938
4067
|
lines.push('echo [4/5] Tao runtime multi-agent dung chung...');
|
|
3939
4068
|
appendBatWriteCommands(lines, mapWindowsNativeFiles(sharedNativeFileMap()));
|
|
3940
|
-
if (is9Router) lines.push(windowsHiddenNodeLaunch('%
|
|
4069
|
+
if (is9Router) lines.push(windowsHiddenNodeLaunch('%DATA_DIR%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
|
|
3941
4070
|
lines.push('if not exist "%OPENCLAW_HOME%\\openclaw.json" (echo ERROR: Khong tim thay "%OPENCLAW_HOME%\\openclaw.json" && goto :fail)');
|
|
3942
4071
|
lines.push('echo.');
|
|
3943
4072
|
lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
|
|
@@ -3948,12 +4077,182 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3948
4077
|
lines.push('echo 9Router Dashboard: http://127.0.0.1:20128/dashboard');
|
|
3949
4078
|
lines.push('echo Other reachable URLs: http://localhost:20128/dashboard');
|
|
3950
4079
|
}
|
|
3951
|
-
|
|
3952
|
-
|
|
4080
|
+
const needsZaloLoginMulti = state.channel === 'zalo-personal' || state.channel === 'telegram+zalo-personal';
|
|
4081
|
+
if (needsZaloLoginMulti) {
|
|
4082
|
+
lines.push('echo [5/6] Khoi dong gateway (cua so moi) de chuan bi dang nhap Zalo...');
|
|
4083
|
+
// Use BAT-native `start` which inherits current env vars - no PS escaping needed
|
|
4084
|
+
lines.push('start "OpenClaw Gateway" cmd /c "openclaw gateway run"');
|
|
4085
|
+
lines.push('echo Cho gateway khoi dong (15 giay)...');
|
|
4086
|
+
lines.push('timeout /t 15 /nobreak >nul');
|
|
4087
|
+
lines.push('echo [6/6] Dang nhap Zalo - dang tao ma QR...');
|
|
4088
|
+
lines.push('openclaw channels login --channel zalouser --instance default --verbose');
|
|
4089
|
+
lines.push('echo.');
|
|
4090
|
+
// Copy QR PNG from TEMP to project dir so user can open it easily
|
|
4091
|
+
lines.push('set "QR_TMP=%TEMP%\\openclaw\\openclaw-zalouser-qr-default.png"');
|
|
4092
|
+
lines.push('if exist "%QR_TMP%" (');
|
|
4093
|
+
lines.push(' copy /y "%QR_TMP%" "%PROJECT_DIR%\\zalo-login-qr.png" >nul');
|
|
4094
|
+
lines.push(' echo ===================================================');
|
|
4095
|
+
lines.push(' echo Ma QR Zalo da duoc luu tai:');
|
|
4096
|
+
lines.push(' echo %PROJECT_DIR%\\zalo-login-qr.png');
|
|
4097
|
+
lines.push(' echo Mo file anh tren r dung Zalo quet de dang nhap!');
|
|
4098
|
+
lines.push(' echo ===================================================');
|
|
4099
|
+
lines.push(' start "" "%PROJECT_DIR%\\zalo-login-qr.png"');
|
|
4100
|
+
lines.push(') else (');
|
|
4101
|
+
lines.push(' echo Khong tim thay file QR. Vui long kiem tra cua so Gateway.');
|
|
4102
|
+
lines.push(')');
|
|
4103
|
+
lines.push('echo Gateway dang chay trong cua so rieng.');
|
|
4104
|
+
lines.push('echo De khoi dong lai: openclaw gateway run');
|
|
4105
|
+
} else {
|
|
4106
|
+
lines.push('echo [5/5] Khoi dong gateway multi-bot...');
|
|
4107
|
+
lines.push(':: Khoi dong OpenClaw Gateway trong cua so moi');
|
|
4108
|
+
lines.push('echo $env:OPENCLAW_HOME = \'%OPENCLAW_HOME%\' > "%TEMP%\\oc-startgw.ps1"');
|
|
4109
|
+
lines.push('echo $env:OPENCLAW_STATE_DIR = \'%OPENCLAW_HOME%\' >> "%TEMP%\\oc-startgw.ps1"');
|
|
4110
|
+
lines.push('echo $b = Join-Path $env:APPDATA \'npm\\openclaw.cmd\' >> "%TEMP%\\oc-startgw.ps1"');
|
|
4111
|
+
lines.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\openclaw\' } >> "%TEMP%\\oc-startgw.ps1"');
|
|
4112
|
+
lines.push("echo Start-Process 'cmd.exe' -WindowStyle Normal -WorkingDirectory '%PROJECT_DIR%' -ArgumentList ^('/c \"' + $b + '\" gateway run'^) >> \"%TEMP%\\oc-startgw.ps1\"");
|
|
4113
|
+
lines.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-startgw.ps1"');
|
|
4114
|
+
lines.push('del "%TEMP%\\oc-startgw.ps1" >nul 2>&1');
|
|
4115
|
+
lines.push('timeout /t 5 /nobreak >nul');
|
|
4116
|
+
lines.push('echo.');
|
|
4117
|
+
lines.push('echo [OK] OpenClaw Gateway dang khoi dong trong cua so moi!');
|
|
4118
|
+
lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
|
|
4119
|
+
lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
|
|
4120
|
+
}
|
|
4121
|
+
} else if (isComboChannel) {
|
|
4122
|
+
// ── Combo: Telegram + Zalo Personal — 2 bots, 1 gateway ─────────────
|
|
4123
|
+
lines.push('echo [4/5] Tao file cau hinh cho 2 bot (Telegram + Zalo Personal)...');
|
|
4124
|
+
// Bot 0 = Telegram bot
|
|
4125
|
+
const bot0Files = botFiles(0);
|
|
4126
|
+
// Bot 1 = Zalo bot (same workspace root, separate agent/workspace dirs)
|
|
4127
|
+
const bot1 = state.bots[1] || {};
|
|
4128
|
+
const zaloName = bot1.name || 'Zalo Bot';
|
|
4129
|
+
const zaloDesc = bot1.desc || (isVi ? 'Tro ly Zalo ca nhan' : 'Personal Zalo assistant');
|
|
4130
|
+
const zaloPersona = bot1.persona || '';
|
|
4131
|
+
const zaloAgentId = zaloName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'zalo-bot';
|
|
4132
|
+
const bot0Name = (state.bots[0] || {}).name || 'Bot 1';
|
|
4133
|
+
const bot0AgentId = bot0Name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'bot-1';
|
|
4134
|
+
// Merge bot0's config to also include zalo agent in agents.list + bindings
|
|
4135
|
+
const mergedConfig = JSON.parse(bot0Files['./.openclaw/openclaw.json'] || '{}');
|
|
4136
|
+
if (!mergedConfig.agents) mergedConfig.agents = { defaults: {}, list: [] };
|
|
4137
|
+
if (!Array.isArray(mergedConfig.agents.list)) mergedConfig.agents.list = [];
|
|
4138
|
+
// Add Zalo agent to list if not already there
|
|
4139
|
+
const hasZaloInList = mergedConfig.agents.list.some(a => a.id === zaloAgentId);
|
|
4140
|
+
if (!hasZaloInList) {
|
|
4141
|
+
mergedConfig.agents.list.push({
|
|
4142
|
+
id: zaloAgentId,
|
|
4143
|
+
name: zaloName,
|
|
4144
|
+
workspace: `.openclaw/workspace-${zaloAgentId}`,
|
|
4145
|
+
agentDir: `agents/${zaloAgentId}/agent`,
|
|
4146
|
+
model: { primary: (bot1.model || state.config.model) },
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4149
|
+
// Ensure bindings exist
|
|
4150
|
+
if (!Array.isArray(mergedConfig.bindings)) mergedConfig.bindings = [];
|
|
4151
|
+
// Bind Telegram bot to bot0
|
|
4152
|
+
const hasTelegramBinding = mergedConfig.bindings.some(b => b && b.match && b.match.channel === 'telegram');
|
|
4153
|
+
if (!hasTelegramBinding) {
|
|
4154
|
+
mergedConfig.bindings.push({ agentId: bot0AgentId, match: { channel: 'telegram', accountId: 'default' } });
|
|
4155
|
+
}
|
|
4156
|
+
// Bind Zalo channel to zalo agent
|
|
4157
|
+
const hasZaloBinding = mergedConfig.bindings.some(b => b && b.match && b.match.channel === 'zalouser');
|
|
4158
|
+
if (!hasZaloBinding) {
|
|
4159
|
+
mergedConfig.bindings.push({ agentId: zaloAgentId, match: { channel: 'zalouser', accountId: 'default' } });
|
|
4160
|
+
}
|
|
4161
|
+
// Ensure zalouser channel is in config
|
|
4162
|
+
if (!mergedConfig.channels) mergedConfig.channels = {};
|
|
4163
|
+
if (!mergedConfig.channels.zalouser) {
|
|
4164
|
+
mergedConfig.channels.zalouser = { enabled: true, dmPolicy: 'open', autoReply: true };
|
|
4165
|
+
}
|
|
4166
|
+
bot0Files['./.openclaw/openclaw.json'] = JSON.stringify(mergedConfig, null, 2);
|
|
4167
|
+
appendBatWriteCommands(lines, mapWindowsNativeFiles(bot0Files));
|
|
4168
|
+
// Zalo agent YAML
|
|
4169
|
+
const zaloAgentYaml = `name: ${zaloAgentId}\ndescription: "${zaloDesc}"\n\nmodel:\n primary: ${bot1.model || state.config.model}`;
|
|
4170
|
+
const zaloWorkspaceDir = `workspace-${zaloAgentId}`;
|
|
4171
|
+
const _zaloSecRules = state.config.securityRules || DEFAULT_SECURITY_RULES[isVi ? 'vi' : 'en'];
|
|
4172
|
+
const zaloFiles = {
|
|
4173
|
+
[`.openclaw/agents/${zaloAgentId}.yaml`]: zaloAgentYaml,
|
|
4174
|
+
[`.openclaw/agents/${zaloAgentId}/agent/auth-profiles.json`]: sharedNativeAuthProfilesContent(),
|
|
4175
|
+
[`.openclaw/${zaloWorkspaceDir}/IDENTITY.md`]: isVi
|
|
4176
|
+
? `# Danh tinh\n\n- **Ten:** ${zaloName}\n- **Vai tro:** ${zaloDesc}\n\n---\n\nMinh la **${zaloName}**. Khi ai hoi ten, minh tra loi: _"Minh la ${zaloName}"_.`
|
|
4177
|
+
: `# Identity\n\n- **Name:** ${zaloName}\n- **Role:** ${zaloDesc}\n\n---\n\nI am **${zaloName}**. When asked my name, I answer: _"I'm ${zaloName}"_.`,
|
|
4178
|
+
[`.openclaw/${zaloWorkspaceDir}/SOUL.md`]: isVi
|
|
4179
|
+
? `# Tinh cach\n\n**Huu ich that su.** Bo qua cau ne, cu giup thang.\n**Co ca tinh.** Tro ly khong co ca tinh thi chi la cong cu.\n\n## Phong cach\n- Tu nhien, gan gui\n- Truc tiep, ngan gon${zaloPersona ? `\n\n## Custom Rules\n${zaloPersona}` : ''}`
|
|
4180
|
+
: `# Soul\n\n**Be genuinely helpful.** Skip filler and just help.\n**Have personality.** An assistant with no personality is just a tool.\n\n## Style\n- Natural and concise\n- Direct and practical${zaloPersona ? `\n\n## Custom Rules\n${zaloPersona}` : ''}`,
|
|
4181
|
+
[`.openclaw/${zaloWorkspaceDir}/AGENTS.md`]: isVi
|
|
4182
|
+
? `# Huong dan van hanh\n\n## Vai tro\nBan la **${zaloName}**, ${zaloDesc.toLowerCase()}.\n\n## Kenh Zalo Personal\n- Ban hoat dong tren kenh Zalo Personal (zca-js).\n- Tra loi moi tin nhan DM theo chinh sach dmPolicy: open.\n- Khong can duoc goi ten moi tra loi (DM la rieng tu).\n\n## Quy tac tra loi\n- Tra loi ngan gon, suc tich\n- Uu tien tieng Viet\n- Khi hoi ten: _"Minh la ${zaloName}"_\n- Khong bia thong tin\n\n${_zaloSecRules}`
|
|
4183
|
+
: `# Operating Manual\n\n## Role\nYou are **${zaloName}**, ${zaloDesc.toLowerCase()}.\n\n## Zalo Personal Channel\n- You operate on the Zalo Personal channel (zca-js).\n- Reply to all DMs with dmPolicy: open.\n- DMs are private — no need to be mentioned to reply.\n\n## Reply Rules\n- Be concise\n- Prefer Vietnamese\n- When asked your name: _"I'm ${zaloName}"_\n- Never fabricate information\n\n${_zaloSecRules}`,
|
|
4184
|
+
[`.openclaw/${zaloWorkspaceDir}/TEAM.md`]: isVi
|
|
4185
|
+
? `# Doi Bot\n\n## ${bot0Name}\n- Vai tro: ${(state.bots[0] || {}).desc || 'Tro ly Telegram'}\n- Kenh: Telegram\n\n## ${zaloName}\n- Vai tro: ${zaloDesc}\n- Kenh: Zalo Personal`
|
|
4186
|
+
: `# Bot Team\n\n## ${bot0Name}\n- Role: ${(state.bots[0] || {}).desc || 'Telegram assistant'}\n- Channel: Telegram\n\n## ${zaloName}\n- Role: ${zaloDesc}\n- Channel: Zalo Personal`,
|
|
4187
|
+
[`.openclaw/${zaloWorkspaceDir}/USER.md`]: isVi
|
|
4188
|
+
? `# Thong tin nguoi dung\n\n## Tong quan\n- **Ngon ngu uu tien:** Tieng Viet\n\n## Thong tin ca nhan\n${state.config.userInfo || '- _(Chua co gi)_'}`
|
|
4189
|
+
: `# User Profile\n\n## Overview\n- **Preferred language:** Vietnamese\n\n## Notes\n${state.config.userInfo || '- _(Nothing yet)_'}`,
|
|
4190
|
+
[`.openclaw/${zaloWorkspaceDir}/MEMORY.md`]: isVi
|
|
4191
|
+
? `# Bo nho dai han\n\n## Ghi chu\n- _(Chua co gi)_`
|
|
4192
|
+
[`.openclaw/${zaloWorkspaceDir}/TOOLS.md`]: isVi
|
|
4193
|
+
? `# Hướng dẫn sử dụng Tools\n\n## Skills đã cài\n${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(Chưa có skill nào)_'}\n\n## Quy ước\n- Ưu tiên dùng tool thay vì đoán\n- Browser: dùng khi user yêu cầu thao tác web\n- Memory: cập nhật khi biết thông tin quan trọng\n\n## Ghi chú thiết lập của bạn\n\nGhi lại cấu hình riêng của môi trường bạn, ví dụ:\n- Tên thiết bị, camera, SSH hosts\n- Giọng nói ưa thích (TTS)\n\n---\n\nThêm ghi chú nào giúp ích cho công việc của bạn.`
|
|
4194
|
+
: `# Tool Usage Guide\n\n## Installed Skills\n${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills installed)_'}\n\n## Conventions\n- Prefer tools over guessing\n- Use Browser for explicit web tasks\n- Update Memory when important user info appears\n\n## Your Setup Notes\n\nRecord environment-specific config, e.g.:\n- Device names, cameras, SSH hosts\n- Preferred TTS voice\n\n---\n\nAdd whatever helps you do your job.`,
|
|
4195
|
+
: `# Long-term Memory\n\n## Notes\n- _(Nothing yet)_`,
|
|
4196
|
+
};
|
|
4197
|
+
appendBatWriteCommands(lines, mapWindowsNativeFiles(zaloFiles));
|
|
4198
|
+
if (is9Router) lines.push(windowsHiddenNodeLaunch('%DATA_DIR%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
|
|
4199
|
+
lines.push('if not exist "%OPENCLAW_HOME%\\openclaw.json" (echo ERROR: Khong tim thay "%OPENCLAW_HOME%\\openclaw.json" && goto :fail)');
|
|
4200
|
+
lines.push('echo.');
|
|
4201
|
+
lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
|
|
4202
|
+
lines.push('echo Other reachable URLs: http://localhost:18791');
|
|
4203
|
+
lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
|
|
4204
|
+
if (is9Router) {
|
|
4205
|
+
lines.push('echo.');
|
|
4206
|
+
lines.push('echo 9Router Dashboard: http://127.0.0.1:20128/dashboard');
|
|
4207
|
+
lines.push('echo Other reachable URLs: http://localhost:20128/dashboard');
|
|
4208
|
+
}
|
|
4209
|
+
// Login Zalo trực tiếp (không cần gateway chạy trước — openclaw channels login standalone)
|
|
4210
|
+
// Sau khi login thành công, gateway sẽ tự nhận credentials khi khởi động.
|
|
4211
|
+
lines.push('echo [5/6] Dang nhap Zalo Personal...');
|
|
4212
|
+
lines.push('echo.');
|
|
4213
|
+
lines.push('echo === HUONG DAN DANG NHAP ZALO ===');
|
|
4214
|
+
lines.push('echo OpenClaw se hien duong dan file anh QR trong cua so nay.');
|
|
4215
|
+
lines.push('echo Hay mo file anh do, chon Quet QR trong app Zalo va quet.');
|
|
4216
|
+
lines.push('echo Neu het gio, nhap R de tao ma QR moi.');
|
|
4217
|
+
lines.push('echo ================================');
|
|
4218
|
+
lines.push('echo.');
|
|
4219
|
+
lines.push(':retry_zalo');
|
|
4220
|
+
lines.push('echo Dang tao ma QR Zalo moi...');
|
|
4221
|
+
lines.push('echo.');
|
|
4222
|
+
lines.push('call openclaw channels login --channel zalouser --verbose');
|
|
4223
|
+
lines.push('if %ERRORLEVEL% equ 0 goto :zalo_done');
|
|
4224
|
+
lines.push('echo.');
|
|
4225
|
+
lines.push('echo [WARN] Ma QR het han hoac chua duoc quet kip.');
|
|
4226
|
+
lines.push('echo R - Tao ma QR moi va thu lai');
|
|
4227
|
+
lines.push('echo Enter - Bo qua dang nhap Zalo (Zalo se khong hoat dong)');
|
|
4228
|
+
lines.push('set /p RETRY_ZALO=Ban chon: ');
|
|
4229
|
+
lines.push('if /i "%RETRY_ZALO%"=="R" goto :retry_zalo');
|
|
4230
|
+
lines.push('echo [SKIP] Bo qua. Zalo se khong hoat dong cho den khi dang nhap lai.');
|
|
4231
|
+
lines.push('goto :zalo_continue');
|
|
4232
|
+
lines.push(':zalo_done');
|
|
4233
|
+
lines.push('echo [OK] Dang nhap Zalo thanh cong!');
|
|
4234
|
+
lines.push(':zalo_continue');
|
|
4235
|
+
lines.push(':: Dong gateway cu (neu co lock file) truoc khi khoi dong lai');
|
|
4236
|
+
lines.push('call openclaw gateway stop 2>nul');
|
|
4237
|
+
lines.push('timeout /t 2 /nobreak >nul');
|
|
4238
|
+
lines.push('echo [6/6] Khoi dong bot (Telegram + Zalo)...');
|
|
4239
|
+
lines.push(':: Khoi dong OpenClaw Gateway trong cua so moi');
|
|
4240
|
+
lines.push('echo $env:OPENCLAW_HOME = \'%OPENCLAW_HOME%\' > "%TEMP%\\oc-startgw.ps1"');
|
|
4241
|
+
lines.push('echo $env:OPENCLAW_STATE_DIR = \'%OPENCLAW_HOME%\' >> "%TEMP%\\oc-startgw.ps1"');
|
|
4242
|
+
lines.push('echo $b = Join-Path $env:APPDATA \'npm\\openclaw.cmd\' >> "%TEMP%\\oc-startgw.ps1"');
|
|
4243
|
+
lines.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\openclaw\' } >> "%TEMP%\\oc-startgw.ps1"');
|
|
4244
|
+
lines.push("echo Start-Process 'cmd.exe' -WindowStyle Normal -WorkingDirectory '%PROJECT_DIR%' -ArgumentList ^('/c \"' + $b + '\" gateway run'^) >> \"%TEMP%\\oc-startgw.ps1\"");
|
|
4245
|
+
lines.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-startgw.ps1"');
|
|
4246
|
+
lines.push('del "%TEMP%\\oc-startgw.ps1" >nul 2>&1');
|
|
4247
|
+
lines.push('timeout /t 5 /nobreak >nul');
|
|
4248
|
+
lines.push('echo.');
|
|
4249
|
+
lines.push('echo [OK] OpenClaw Gateway dang khoi dong trong cua so moi!');
|
|
4250
|
+
lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
|
|
4251
|
+
lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
|
|
3953
4252
|
} else {
|
|
3954
4253
|
lines.push('echo [4/5] Tao file cau hinh...');
|
|
3955
4254
|
appendBatWriteCommands(lines, mapWindowsNativeFiles(botFiles(0)));
|
|
3956
|
-
if (is9Router) lines.push(windowsHiddenNodeLaunch('%
|
|
4255
|
+
if (is9Router) lines.push(windowsHiddenNodeLaunch('%DATA_DIR%\\9router-smart-route-sync.js', { DATA_DIR: '%DATA_DIR%' }));
|
|
3957
4256
|
lines.push('if not exist "%OPENCLAW_HOME%\\openclaw.json" (echo ERROR: Khong tim thay "%OPENCLAW_HOME%\\openclaw.json" && goto :fail)');
|
|
3958
4257
|
lines.push('echo.');
|
|
3959
4258
|
lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
|
|
@@ -3964,8 +4263,55 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
3964
4263
|
lines.push('echo 9Router Dashboard: http://127.0.0.1:20128/dashboard');
|
|
3965
4264
|
lines.push('echo Other reachable URLs: http://localhost:20128/dashboard');
|
|
3966
4265
|
}
|
|
3967
|
-
|
|
3968
|
-
|
|
4266
|
+
const needsZaloLogin = state.channel === 'zalo-personal';
|
|
4267
|
+
if (needsZaloLogin) {
|
|
4268
|
+
// Login Zalo trực tiếp (không cần gateway chạy trước)
|
|
4269
|
+
lines.push('echo [5/5] Dang nhap Zalo Personal...');
|
|
4270
|
+
lines.push('echo.');
|
|
4271
|
+
lines.push('echo === HUONG DAN DANG NHAP ZALO ===');
|
|
4272
|
+
lines.push('echo Cua so Zalo Login se mo. Hay:');
|
|
4273
|
+
lines.push('echo 1. Doi QR hien ra trong cua so Zalo Login');
|
|
4274
|
+
lines.push('echo 2. Mo app Zalo, chon Quet QR va quet ma');
|
|
4275
|
+
lines.push('echo 3. Doi thay chu "Login successful" trong cua so do');
|
|
4276
|
+
lines.push('echo 4. Dong cua so Zalo Login');
|
|
4277
|
+
lines.push('echo ================================');
|
|
4278
|
+
lines.push('echo.');
|
|
4279
|
+
lines.push('start "Zalo Login" cmd /k "cd /d \"%PROJECT_DIR%\" && set OPENCLAW_HOME=%OPENCLAW_HOME% && set OPENCLAW_STATE_DIR=%OPENCLAW_HOME% && openclaw channels login --channel zalouser --verbose"');
|
|
4280
|
+
lines.push('echo Nhan phim bat ky sau khi dong cua so Zalo Login...');
|
|
4281
|
+
lines.push('pause >nul');
|
|
4282
|
+
lines.push(':: Dong gateway cu (neu co lock file tu cua so Zalo Login) truoc khi khoi dong lai');
|
|
4283
|
+
lines.push('call openclaw gateway stop 2>nul');
|
|
4284
|
+
lines.push('timeout /t 2 /nobreak >nul');
|
|
4285
|
+
lines.push('echo [6/6] Khoi dong bot...');
|
|
4286
|
+
lines.push(':: Khoi dong OpenClaw Gateway trong cua so moi');
|
|
4287
|
+
lines.push('echo $env:OPENCLAW_HOME = \'%OPENCLAW_HOME%\' > "%TEMP%\\oc-startgw.ps1"');
|
|
4288
|
+
lines.push('echo $env:OPENCLAW_STATE_DIR = \'%OPENCLAW_HOME%\' >> "%TEMP%\\oc-startgw.ps1"');
|
|
4289
|
+
lines.push('echo $b = Join-Path $env:APPDATA \'npm\\openclaw.cmd\' >> "%TEMP%\\oc-startgw.ps1"');
|
|
4290
|
+
lines.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\openclaw\' } >> "%TEMP%\\oc-startgw.ps1"');
|
|
4291
|
+
lines.push("echo Start-Process 'cmd.exe' -WindowStyle Normal -WorkingDirectory '%PROJECT_DIR%' -ArgumentList ^('/c \"' + $b + '\" gateway run'^) >> \"%TEMP%\\oc-startgw.ps1\"");
|
|
4292
|
+
lines.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-startgw.ps1"');
|
|
4293
|
+
lines.push('del "%TEMP%\\oc-startgw.ps1" >nul 2>&1');
|
|
4294
|
+
lines.push('timeout /t 5 /nobreak >nul');
|
|
4295
|
+
lines.push('echo.');
|
|
4296
|
+
lines.push('echo [OK] OpenClaw Gateway dang khoi dong trong cua so moi!');
|
|
4297
|
+
lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
|
|
4298
|
+
lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
|
|
4299
|
+
} else {
|
|
4300
|
+
lines.push('echo [5/5] Khoi dong bot...');
|
|
4301
|
+
lines.push(':: Khoi dong OpenClaw Gateway trong cua so moi');
|
|
4302
|
+
lines.push('echo $env:OPENCLAW_HOME = \'%OPENCLAW_HOME%\' > "%TEMP%\\oc-startgw.ps1"');
|
|
4303
|
+
lines.push('echo $env:OPENCLAW_STATE_DIR = \'%OPENCLAW_HOME%\' >> "%TEMP%\\oc-startgw.ps1"');
|
|
4304
|
+
lines.push('echo $b = Join-Path $env:APPDATA \'npm\\openclaw.cmd\' >> "%TEMP%\\oc-startgw.ps1"');
|
|
4305
|
+
lines.push('echo if ^(-not ^(Test-Path $b^)^) { $b = Join-Path $env:APPDATA \'npm\\openclaw\' } >> "%TEMP%\\oc-startgw.ps1"');
|
|
4306
|
+
lines.push("echo Start-Process 'cmd.exe' -WindowStyle Normal -WorkingDirectory '%PROJECT_DIR%' -ArgumentList ^('/c \"' + $b + '\" gateway run'^) >> \"%TEMP%\\oc-startgw.ps1\"");
|
|
4307
|
+
lines.push('powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\\oc-startgw.ps1"');
|
|
4308
|
+
lines.push('del "%TEMP%\\oc-startgw.ps1" >nul 2>&1');
|
|
4309
|
+
lines.push('timeout /t 5 /nobreak >nul');
|
|
4310
|
+
lines.push('echo.');
|
|
4311
|
+
lines.push('echo [OK] OpenClaw Gateway dang khoi dong trong cua so moi!');
|
|
4312
|
+
lines.push('echo OpenClaw Dashboard: http://127.0.0.1:18791');
|
|
4313
|
+
lines.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
|
|
4314
|
+
}
|
|
3969
4315
|
}
|
|
3970
4316
|
|
|
3971
4317
|
lines.push('goto :end');
|
|
@@ -4073,7 +4419,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
4073
4419
|
if (is9Router) {
|
|
4074
4420
|
vps.push(`NINE_ROUTER_ENTRY="$(${native9RouterServerEntryLookup()})"`);
|
|
4075
4421
|
vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$NINE_ROUTER_ENTRY" --name openclaw-multibot-9router --interpreter "$(command -v node)"');
|
|
4076
|
-
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.
|
|
4422
|
+
vps.push('pm2 start --name openclaw-multibot-9router-sync -- sh -c "node ./.9router/9router-smart-route-sync.js"');
|
|
4077
4423
|
}
|
|
4078
4424
|
vps.push('pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"');
|
|
4079
4425
|
vps.push('pm2 save && pm2 startup');
|
|
@@ -4087,7 +4433,7 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
4087
4433
|
if (is9Router) {
|
|
4088
4434
|
vps.push(`NINE_ROUTER_ENTRY="$(${native9RouterServerEntryLookup()})"`);
|
|
4089
4435
|
vps.push('PORT=20128 HOSTNAME=0.0.0.0 pm2 start "$NINE_ROUTER_ENTRY" --name openclaw-9router --interpreter "$(command -v node)"');
|
|
4090
|
-
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.
|
|
4436
|
+
vps.push('pm2 start --name openclaw-9router-sync -- sh -c "node ./.9router/9router-smart-route-sync.js"');
|
|
4091
4437
|
}
|
|
4092
4438
|
vps.push('pm2 start --name openclaw -- sh -c "openclaw gateway run"');
|
|
4093
4439
|
vps.push('pm2 save && pm2 startup');
|
|
@@ -4175,17 +4521,294 @@ ${selectedSkillNames.length ? selectedSkillNames.join('\n') : '- _(No skills ins
|
|
|
4175
4521
|
|
|
4176
4522
|
|
|
4177
4523
|
|
|
4524
|
+
// ========== Generate Uninstall Script ==========
|
|
4525
|
+
function generateUninstallScript() {
|
|
4526
|
+
const os = state.nativeOs || 'win';
|
|
4527
|
+
const isDocker = state.deployMode === 'docker';
|
|
4528
|
+
const projectDirRaw = document.getElementById('cfg-project-path')?.value?.trim() || '.';
|
|
4529
|
+
// Normalise to a sensible display path
|
|
4530
|
+
const projectDir = projectDirRaw;
|
|
4531
|
+
const absWin = projectDir.replace(/\//g, '\\');
|
|
4532
|
+
const absUnix = projectDir.replace(/\\/g, '/');
|
|
4533
|
+
const botName = (state.bots[0]?.name || 'openclaw').toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
4534
|
+
|
|
4535
|
+
// ── Windows native .bat ────────────────────────────────────────────────────
|
|
4536
|
+
if (os === 'win' && !isDocker) {
|
|
4537
|
+
return {
|
|
4538
|
+
name: 'uninstall-openclaw-win.bat',
|
|
4539
|
+
content: `@echo off
|
|
4540
|
+
setlocal EnableExtensions
|
|
4541
|
+
chcp 65001 >nul
|
|
4542
|
+
echo.
|
|
4543
|
+
echo ============================================================
|
|
4544
|
+
echo OpenClaw Uninstaller - Windows Native
|
|
4545
|
+
echo Project: ${absWin}
|
|
4546
|
+
echo ============================================================
|
|
4547
|
+
echo.
|
|
4548
|
+
echo [WARNING] This will:
|
|
4549
|
+
echo 1. Kill openclaw and 9router background processes
|
|
4550
|
+
echo 2. Uninstall global npm packages (openclaw, 9router)
|
|
4551
|
+
echo 3. Delete the project folder and all its data
|
|
4552
|
+
echo.
|
|
4553
|
+
set /p CONFIRM=Nhap YES de xac nhan xoa toan bo:
|
|
4554
|
+
if /i not "%CONFIRM%"=="YES" (
|
|
4555
|
+
echo Huy bo. Khong xoa gi ca.
|
|
4556
|
+
pause
|
|
4557
|
+
exit /b 0
|
|
4558
|
+
)
|
|
4559
|
+
echo.
|
|
4560
|
+
echo [1/4] Dang dung cac tien trinh openclaw va 9router...
|
|
4561
|
+
wmic process where "Name='node.exe' and CommandLine like '%%9router%%'" delete >nul 2>&1
|
|
4562
|
+
wmic process where "Name='cmd.exe' and CommandLine like '%%9router%%'" delete >nul 2>&1
|
|
4563
|
+
wmic process where "Name='node.exe' and CommandLine like '%%openclaw.mjs%%'" delete >nul 2>&1
|
|
4564
|
+
timeout /t 2 /nobreak >nul
|
|
4565
|
+
echo OK: Tien trinh da dung.
|
|
4566
|
+
echo.
|
|
4567
|
+
echo [2/4] Dang go cai npm packages toan cau...
|
|
4568
|
+
set "PATH=%APPDATA%\\npm;%PATH%"
|
|
4569
|
+
call npm uninstall -g openclaw 9router grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>nul
|
|
4570
|
+
echo OK: npm packages da duoc go cai.
|
|
4571
|
+
echo.
|
|
4572
|
+
echo [3/4] Xoa thu muc project...
|
|
4573
|
+
set "TARGET=${absWin}"
|
|
4574
|
+
if exist "%TARGET%" (
|
|
4575
|
+
rd /s /q "%TARGET%"
|
|
4576
|
+
echo OK: Da xoa %TARGET%
|
|
4577
|
+
) else (
|
|
4578
|
+
echo INFO: Thu muc khong ton tai: %TARGET%
|
|
4579
|
+
)
|
|
4580
|
+
echo.
|
|
4581
|
+
echo [4/4] Xoa thu muc .9router trong Home (neu co)...
|
|
4582
|
+
if exist "%USERPROFILE%\\.9router" (
|
|
4583
|
+
set /p CLEAN_HOME=Xoa ca %USERPROFILE%\\.9router? [YES/no]:
|
|
4584
|
+
if /i "%CLEAN_HOME%"=="YES" rd /s /q "%USERPROFILE%\\.9router" >nul 2>&1
|
|
4585
|
+
)
|
|
4586
|
+
echo.
|
|
4587
|
+
echo ============================================================
|
|
4588
|
+
echo Go cai hoan tat!
|
|
4589
|
+
echo De cai lai: chay lai file setup hoac npx create-openclaw-bot
|
|
4590
|
+
echo ============================================================
|
|
4591
|
+
pause
|
|
4592
|
+
endlocal
|
|
4593
|
+
`
|
|
4594
|
+
};
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
// ── Windows Docker .bat ────────────────────────────────────────────────────
|
|
4598
|
+
if (os === 'win' && isDocker) {
|
|
4599
|
+
return {
|
|
4600
|
+
name: 'uninstall-openclaw-docker.bat',
|
|
4601
|
+
content: `@echo off
|
|
4602
|
+
setlocal EnableExtensions
|
|
4603
|
+
chcp 65001 >nul
|
|
4604
|
+
echo.
|
|
4605
|
+
echo ============================================================
|
|
4606
|
+
echo OpenClaw Uninstaller - Docker (Windows)
|
|
4607
|
+
echo Project: ${absWin}
|
|
4608
|
+
echo ============================================================
|
|
4609
|
+
echo.
|
|
4610
|
+
echo [WARNING] This will stop Docker containers and delete the project folder.
|
|
4611
|
+
echo.
|
|
4612
|
+
set /p CONFIRM=Nhap YES de xac nhan xoa toan bo:
|
|
4613
|
+
if /i not "%CONFIRM%"=="YES" (
|
|
4614
|
+
echo Huy bo. Khong xoa gi ca.
|
|
4615
|
+
pause
|
|
4616
|
+
exit /b 0
|
|
4617
|
+
)
|
|
4618
|
+
echo.
|
|
4619
|
+
echo [1/2] Dang dung Docker containers...
|
|
4620
|
+
cd /d "${absWin}\docker\openclaw" 2>nul && (
|
|
4621
|
+
docker compose down --volumes --remove-orphans 2>nul || docker-compose down --volumes --remove-orphans 2>nul
|
|
4622
|
+
echo OK: Containers da dung.
|
|
4623
|
+
) || echo INFO: Khong tim thay docker compose.
|
|
4624
|
+
echo.
|
|
4625
|
+
echo [2/2] Xoa thu muc project...
|
|
4626
|
+
cd /d "%USERPROFILE%"
|
|
4627
|
+
if exist "${absWin}" (
|
|
4628
|
+
rd /s /q "${absWin}"
|
|
4629
|
+
echo OK: Da xoa ${absWin}
|
|
4630
|
+
)
|
|
4631
|
+
echo.
|
|
4632
|
+
echo ============================================================
|
|
4633
|
+
echo Go cai hoan tat! De cai lai: npx create-openclaw-bot@latest
|
|
4634
|
+
echo ============================================================
|
|
4635
|
+
pause
|
|
4636
|
+
endlocal
|
|
4637
|
+
`
|
|
4638
|
+
};
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4641
|
+
// ── VPS / PM2 .sh ─────────────────────────────────────────────────────────
|
|
4642
|
+
if (os === 'vps') {
|
|
4643
|
+
return {
|
|
4644
|
+
name: 'uninstall-openclaw-vps.sh',
|
|
4645
|
+
content: `#!/usr/bin/env bash
|
|
4646
|
+
# ====== OpenClaw Uninstaller — VPS / Ubuntu Server (PM2) ======
|
|
4647
|
+
set -e
|
|
4648
|
+
PROJECT_DIR="${absUnix}"
|
|
4649
|
+
APP_NAME="${botName}"
|
|
4650
|
+
|
|
4651
|
+
echo ""
|
|
4652
|
+
echo "============================================================"
|
|
4653
|
+
echo " OpenClaw Uninstaller — VPS / Ubuntu Server"
|
|
4654
|
+
echo " Project: $PROJECT_DIR"
|
|
4655
|
+
echo " PM2 app: $APP_NAME"
|
|
4656
|
+
echo "============================================================"
|
|
4657
|
+
echo ""
|
|
4658
|
+
read -rp "Type YES to confirm full removal: " CONFIRM
|
|
4659
|
+
if [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi
|
|
4660
|
+
|
|
4661
|
+
echo "[1/5] Stopping PM2 processes..."
|
|
4662
|
+
if command -v pm2 &>/dev/null; then
|
|
4663
|
+
pm2 delete "$APP_NAME" "$APP_NAME-9router" "$APP_NAME-9router-sync" openclaw openclaw-multibot 2>/dev/null || true
|
|
4664
|
+
pm2 save --force 2>/dev/null || true
|
|
4665
|
+
fi
|
|
4666
|
+
|
|
4667
|
+
echo "[2/5] Killing leftover processes on ports 18791 / 20128..."
|
|
4668
|
+
for port in 18791 20128; do
|
|
4669
|
+
pid=$(lsof -ti tcp:\$port 2>/dev/null || true)
|
|
4670
|
+
[ -n "\$pid" ] && kill -9 \$pid 2>/dev/null || true
|
|
4671
|
+
done
|
|
4672
|
+
|
|
4673
|
+
echo "[3/5] Uninstalling npm packages..."
|
|
4674
|
+
npm uninstall -g openclaw 9router pm2 grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>/dev/null || true
|
|
4675
|
+
|
|
4676
|
+
echo "[4/5] Removing project directory..."
|
|
4677
|
+
[ -d "\$PROJECT_DIR" ] && rm -rf "\$PROJECT_DIR" && echo " OK: Deleted \$PROJECT_DIR" || echo " INFO: Not found."
|
|
4678
|
+
|
|
4679
|
+
echo "[5/5] Checking home-level .9router / .openclaw..."
|
|
4680
|
+
for dir in "\$HOME/.9router" "\$HOME/.openclaw"; do
|
|
4681
|
+
if [ -d "\$dir" ]; then
|
|
4682
|
+
read -rp "Delete \$dir ? [YES/no]: " CLEAN
|
|
4683
|
+
[ "\$CLEAN" = "YES" ] && rm -rf "\$dir" && echo " OK: Deleted \$dir" || echo " Kept: \$dir"
|
|
4684
|
+
fi
|
|
4685
|
+
done
|
|
4686
|
+
|
|
4687
|
+
echo ""
|
|
4688
|
+
echo "============================================================"
|
|
4689
|
+
echo " Uninstall complete! Re-install: npx create-openclaw-bot@latest"
|
|
4690
|
+
echo "============================================================"
|
|
4691
|
+
`
|
|
4692
|
+
};
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
// ── macOS / Linux Desktop .sh ──────────────────────────────────────────────
|
|
4696
|
+
if (os === 'linux' || os === 'linux-desktop') {
|
|
4697
|
+
const label = os === 'linux' ? 'macOS' : 'Linux Desktop';
|
|
4698
|
+
return {
|
|
4699
|
+
name: 'uninstall-openclaw.sh',
|
|
4700
|
+
content: `#!/usr/bin/env bash
|
|
4701
|
+
# ====== OpenClaw Uninstaller — ${label} (Native) ======
|
|
4702
|
+
set -e
|
|
4703
|
+
PROJECT_DIR="${absUnix}"
|
|
4704
|
+
|
|
4705
|
+
echo ""
|
|
4706
|
+
echo "============================================================"
|
|
4707
|
+
echo " OpenClaw Uninstaller — ${label} Native"
|
|
4708
|
+
echo " Project: $PROJECT_DIR"
|
|
4709
|
+
echo "============================================================"
|
|
4710
|
+
echo ""
|
|
4711
|
+
read -rp "Type YES to confirm full removal: " CONFIRM
|
|
4712
|
+
if [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi
|
|
4713
|
+
|
|
4714
|
+
echo "[1/4] Stopping openclaw and 9router processes..."
|
|
4715
|
+
pkill -f "openclaw gateway run" 2>/dev/null || true
|
|
4716
|
+
pkill -f "9router.*20128" 2>/dev/null || true
|
|
4717
|
+
pkill -f "9router-smart-route" 2>/dev/null || true
|
|
4718
|
+
pkill -f "\$PROJECT_DIR" 2>/dev/null || true
|
|
4719
|
+
for port in 18791 20128; do
|
|
4720
|
+
pid=$(lsof -ti tcp:\$port 2>/dev/null || true)
|
|
4721
|
+
[ -n "\$pid" ] && kill -9 \$pid 2>/dev/null || true
|
|
4722
|
+
done
|
|
4723
|
+
|
|
4724
|
+
echo "[2/4] Uninstalling npm packages..."
|
|
4725
|
+
npm uninstall -g openclaw 9router grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>/dev/null || true
|
|
4726
|
+
sudo npm uninstall -g openclaw 9router 2>/dev/null || true
|
|
4727
|
+
|
|
4728
|
+
echo "[3/4] Removing project directory..."
|
|
4729
|
+
[ -d "\$PROJECT_DIR" ] && rm -rf "\$PROJECT_DIR" && echo " OK: Deleted \$PROJECT_DIR" || echo " INFO: Not found."
|
|
4730
|
+
|
|
4731
|
+
echo "[4/4] Checking home-level .9router / .openclaw..."
|
|
4732
|
+
for dir in "\$HOME/.9router" "\$HOME/.openclaw"; do
|
|
4733
|
+
if [ -d "\$dir" ]; then
|
|
4734
|
+
read -rp "Delete \$dir ? [YES/no]: " CLEAN
|
|
4735
|
+
[ "\$CLEAN" = "YES" ] && rm -rf "\$dir" && echo " OK: Deleted \$dir" || echo " Kept: \$dir"
|
|
4736
|
+
fi
|
|
4737
|
+
done
|
|
4738
|
+
|
|
4739
|
+
echo ""
|
|
4740
|
+
echo "============================================================"
|
|
4741
|
+
echo " Uninstall complete! Re-install: run setup script or npx create-openclaw-bot"
|
|
4742
|
+
echo "============================================================"
|
|
4743
|
+
`
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
|
|
4747
|
+
// ── Docker macOS/Linux/VPS .sh ─────────────────────────────────────────────
|
|
4748
|
+
if (isDocker) {
|
|
4749
|
+
return {
|
|
4750
|
+
name: 'uninstall-openclaw-docker.sh',
|
|
4751
|
+
content: `#!/usr/bin/env bash
|
|
4752
|
+
# ====== OpenClaw Uninstaller — Docker ======
|
|
4753
|
+
set -e
|
|
4754
|
+
PROJECT_DIR="${absUnix}"
|
|
4755
|
+
DOCKER_DIR="$PROJECT_DIR/docker/openclaw"
|
|
4756
|
+
|
|
4757
|
+
echo ""
|
|
4758
|
+
echo "============================================================"
|
|
4759
|
+
echo " OpenClaw Uninstaller — Docker"
|
|
4760
|
+
echo " Project: $PROJECT_DIR"
|
|
4761
|
+
echo "============================================================"
|
|
4762
|
+
echo ""
|
|
4763
|
+
read -rp "Type YES to confirm full removal: " CONFIRM
|
|
4764
|
+
if [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi
|
|
4765
|
+
|
|
4766
|
+
echo "[1/3] Stopping Docker containers and removing volumes..."
|
|
4767
|
+
if [ -d "\$DOCKER_DIR" ] && command -v docker &>/dev/null; then
|
|
4768
|
+
cd "\$DOCKER_DIR"
|
|
4769
|
+
docker compose down --volumes --remove-orphans 2>/dev/null || docker-compose down --volumes --remove-orphans 2>/dev/null || true
|
|
4770
|
+
fi
|
|
4771
|
+
|
|
4772
|
+
echo "[2/3] Removing project directory..."
|
|
4773
|
+
[ -d "\$PROJECT_DIR" ] && rm -rf "\$PROJECT_DIR" && echo " OK: Deleted \$PROJECT_DIR" || echo " INFO: Not found."
|
|
4774
|
+
|
|
4775
|
+
echo "[3/3] Checking home-level .openclaw..."
|
|
4776
|
+
if [ -d "\$HOME/.openclaw" ]; then
|
|
4777
|
+
read -rp "Delete \$HOME/.openclaw? [YES/no]: " CLEAN
|
|
4778
|
+
[ "\$CLEAN" = "YES" ] && rm -rf "\$HOME/.openclaw" && echo " OK." || echo " Kept."
|
|
4779
|
+
fi
|
|
4780
|
+
|
|
4781
|
+
echo ""
|
|
4782
|
+
echo "============================================================"
|
|
4783
|
+
echo " Uninstall complete! Re-install: npx create-openclaw-bot@latest"
|
|
4784
|
+
echo "============================================================"
|
|
4785
|
+
`
|
|
4786
|
+
};
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4789
|
+
return null;
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4792
|
+
function _triggerDownload(filename, content, mimeType) {
|
|
4793
|
+
const blob = new Blob([content], { type: mimeType || 'text/plain;charset=utf-8' });
|
|
4794
|
+
const url = URL.createObjectURL(blob);
|
|
4795
|
+
const a = document.createElement('a');
|
|
4796
|
+
a.href = url; a.download = filename; a.style.display = 'none';
|
|
4797
|
+
document.body.appendChild(a); a.click();
|
|
4798
|
+
setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 1500);
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4178
4801
|
window.downloadNativeScript = function() {
|
|
4179
4802
|
// Regenerate output first so the downloaded script always matches the latest wizard state.
|
|
4180
4803
|
generateOutput();
|
|
4181
4804
|
const script = window._nativeScript;
|
|
4182
4805
|
if (!script) return;
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4806
|
+
_triggerDownload(script.name, script.content, 'text/plain;charset=utf-8');
|
|
4807
|
+
// Also download the matching uninstall script right after
|
|
4808
|
+
const uninstall = generateUninstallScript();
|
|
4809
|
+
if (uninstall) {
|
|
4810
|
+
setTimeout(() => _triggerDownload(uninstall.name, uninstall.content, 'text/plain;charset=utf-8'), 600);
|
|
4811
|
+
}
|
|
4189
4812
|
};
|
|
4190
4813
|
|
|
4191
4814
|
// ========== Generate Windows Auto Setup .bat ==========
|
|
@@ -4244,10 +4867,23 @@ New-Item -ItemType Directory -Force -Path "$projectDir" | Out-Null
|
|
|
4244
4867
|
// [3/4] Docker build
|
|
4245
4868
|
ps += `# [3/4] Docker build
|
|
4246
4869
|
Write-Host "[3/4] ${isVi ? 'Build Docker image (co the mat vai phut)...' : 'Building Docker image (may take a few minutes)...'}" -ForegroundColor Yellow
|
|
4870
|
+
\$dockerExe = \$null
|
|
4871
|
+
try { \$dockerExe = (Get-Command 'docker' -ErrorAction Stop).Source } catch {}
|
|
4872
|
+
if (-not \$dockerExe) {
|
|
4873
|
+
Write-Host " ${isVi ? 'Khong tim thay Docker. Tai Docker Desktop tai: https://www.docker.com/products/docker-desktop' : 'Docker not found. Download Docker Desktop at: https://www.docker.com/products/docker-desktop'}" -ForegroundColor Red
|
|
4874
|
+
Read-Host "Press Enter to exit"
|
|
4875
|
+
exit 1
|
|
4876
|
+
}
|
|
4877
|
+
& \$dockerExe info 2>&1 | Out-Null
|
|
4878
|
+
if (\$LASTEXITCODE -ne 0) {
|
|
4879
|
+
Write-Host " ${isVi ? 'Docker Desktop chua chay. Mo Docker Desktop roi thu lai.' : 'Docker Desktop is not running. Start Docker Desktop, then re-run this script.'}" -ForegroundColor Red
|
|
4880
|
+
Read-Host "Press Enter to exit"
|
|
4881
|
+
exit 1
|
|
4882
|
+
}
|
|
4247
4883
|
Set-Location "$projectDir\\docker\\openclaw"
|
|
4248
|
-
|
|
4249
|
-
if (
|
|
4250
|
-
Write-Host " ❌ ${isVi ? 'Docker build that bai.
|
|
4884
|
+
& \$dockerExe compose build
|
|
4885
|
+
if (\$LASTEXITCODE -ne 0) {
|
|
4886
|
+
Write-Host " ❌ ${isVi ? 'Docker build that bai. Xem log phia tren.' : 'Docker build failed. Check the log above.'}" -ForegroundColor Red
|
|
4251
4887
|
Read-Host "${isVi ? 'Nhan Enter de thoat' : 'Press Enter to exit'}"
|
|
4252
4888
|
exit 1
|
|
4253
4889
|
}
|
|
@@ -4258,7 +4894,7 @@ Write-Host " ✅ ${isVi ? 'Docker image da build' : 'Docker image built'}" -For
|
|
|
4258
4894
|
// [4/4] Docker up
|
|
4259
4895
|
ps += `# [4/4] Start bot
|
|
4260
4896
|
Write-Host "[4/4] ${isVi ? 'Khoi dong bot...' : 'Starting bot...'}" -ForegroundColor Yellow
|
|
4261
|
-
|
|
4897
|
+
& \$dockerExe compose up -d
|
|
4262
4898
|
Write-Host " ✅ ${isVi ? 'Bot dang chay!' : 'Bot is running!'}" -ForegroundColor Green
|
|
4263
4899
|
|
|
4264
4900
|
Write-Host ""
|
|
@@ -4270,8 +4906,8 @@ Write-Host " 🎉 ${isVi ? 'Setup hoan tat!' : 'Setup complete!'}" -ForegroundC
|
|
|
4270
4906
|
if (is9Router) {
|
|
4271
4907
|
ps += `Write-Host " ${isVi ? 'Mo http://localhost:30128/dashboard de login OAuth' : 'Open http://localhost:30128/dashboard to login OAuth'}" -ForegroundColor White\n`;
|
|
4272
4908
|
}
|
|
4273
|
-
if (state.channel === 'zalo-personal') {
|
|
4274
|
-
ps += `Write-Host " ${isVi ? 'Chay: docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose' : 'Run: docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose'}" -ForegroundColor White\n`;
|
|
4909
|
+
if (state.channel === 'zalo-personal' || state.channel === 'telegram+zalo-personal') {
|
|
4910
|
+
ps += `Write-Host " ${isVi ? 'Chay: docker compose exec -it ai-bot openclaw channels login --channel zalouser --instance default --verbose' : 'Run: docker compose exec -it ai-bot openclaw channels login --channel zalouser --instance default --verbose'}" -ForegroundColor White\n`;
|
|
4275
4911
|
ps += `Write-Host " ${isVi ? 'QR se nam tai /tmp/openclaw/openclaw-zalouser-qr-default.png' : 'QR will be written to /tmp/openclaw/openclaw-zalouser-qr-default.png'}" -ForegroundColor DarkGray\n`;
|
|
4276
4912
|
ps += `Write-Host " ${isVi ? 'Copy QR ra ngoai: docker compose cp ai-bot:/tmp/openclaw/openclaw-zalouser-qr-default.png ./zalo-login-qr.png' : 'Copy the QR out: docker compose cp ai-bot:/tmp/openclaw/openclaw-zalouser-qr-default.png ./zalo-login-qr.png'}" -ForegroundColor DarkGray\n`;
|
|
4277
4913
|
}
|
|
@@ -4452,7 +5088,7 @@ echo ""
|
|
|
4452
5088
|
|
|
4453
5089
|
function generateZaloOnboardGuide() {
|
|
4454
5090
|
const lang = document.getElementById('cfg-language')?.value || 'vi';
|
|
4455
|
-
setOutput('out-zalo-onboard-cmd', `docker compose exec -it ai-bot openclaw channels login --channel zalouser --verbose`);
|
|
5091
|
+
setOutput('out-zalo-onboard-cmd', `docker compose exec -it ai-bot openclaw channels login --channel zalouser --instance default --verbose`);
|
|
4456
5092
|
|
|
4457
5093
|
if (lang === 'vi') {
|
|
4458
5094
|
setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
|
|
@@ -4524,6 +5160,18 @@ echo ""
|
|
|
4524
5160
|
zip.file(path, content);
|
|
4525
5161
|
});
|
|
4526
5162
|
|
|
5163
|
+
// Include the native setup script (if native mode)
|
|
5164
|
+
const nativeScript = window._nativeScript;
|
|
5165
|
+
if (nativeScript && nativeScript.name && nativeScript.content) {
|
|
5166
|
+
zip.file(nativeScript.name, nativeScript.content);
|
|
5167
|
+
}
|
|
5168
|
+
|
|
5169
|
+
// Include the matching uninstall script
|
|
5170
|
+
const uninstall = generateUninstallScript();
|
|
5171
|
+
if (uninstall && uninstall.name && uninstall.content) {
|
|
5172
|
+
zip.file(uninstall.name, uninstall.content);
|
|
5173
|
+
}
|
|
5174
|
+
|
|
4527
5175
|
const blob = await zip.generateAsync({ type: 'blob' });
|
|
4528
5176
|
const url = URL.createObjectURL(blob);
|
|
4529
5177
|
const a = document.createElement('a');
|