create-openclaw-bot 5.1.14 → 5.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/CHANGELOG.vi.md +7 -0
- package/README.md +3 -3
- package/README.vi.md +3 -3
- package/cli.js +261 -199
- package/package.json +1 -1
- package/setup.js +360 -178
- package/tests/smoke-cli-logic.mjs +87 -18
package/cli.js
CHANGED
|
@@ -5,11 +5,12 @@ import fs from 'fs-extra';
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
|
-
import { spawn, execSync, execFileSync } from 'child_process';
|
|
9
|
-
const
|
|
8
|
+
import { spawn, execSync, execFileSync } from 'child_process';
|
|
9
|
+
const TELEGRAM_RELAY_PLUGIN_RUNTIME_ID = 'telegram-multibot-relay';
|
|
10
|
+
const TELEGRAM_RELAY_PLUGIN_PACKAGE = 'openclaw-telegram-multibot-relay';
|
|
10
11
|
const OPENCLAW_NPM_SPEC = 'openclaw@2026.4.5';
|
|
11
|
-
// Use plain npm package name — clawhub: protocol not supported in all OpenClaw versions
|
|
12
|
-
const TELEGRAM_RELAY_PLUGIN_SPEC =
|
|
12
|
+
// Use plain npm package name — clawhub: protocol not supported in all OpenClaw versions
|
|
13
|
+
const TELEGRAM_RELAY_PLUGIN_SPEC = TELEGRAM_RELAY_PLUGIN_PACKAGE;
|
|
13
14
|
|
|
14
15
|
// Install command: only use clawhub: spec (published to ClawHub)
|
|
15
16
|
function buildRelayPluginInstallCommand(prefix = 'openclaw') {
|
|
@@ -20,13 +21,17 @@ function buildRelayPluginInstallCommandWin(prefix = 'openclaw') {
|
|
|
20
21
|
return `${prefix} plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC} || exit /b 0`;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function installRelayPluginForProject(projectDir, isVi) {
|
|
24
|
-
try {
|
|
25
|
-
execSync(`openclaw plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC}`, {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
function installRelayPluginForProject(projectDir, isVi) {
|
|
25
|
+
try {
|
|
26
|
+
execSync(`openclaw plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC}`, {
|
|
27
|
+
cwd: projectDir,
|
|
28
|
+
stdio: 'ignore',
|
|
29
|
+
env: getProjectRuntimeEnv(projectDir),
|
|
30
|
+
});
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
// silent fallback
|
|
34
|
+
}
|
|
30
35
|
console.log(chalk.yellow(isVi
|
|
31
36
|
? `\n⚠️ Chua the tu dong cai plugin. Sau khi bot chay, chay thu cong:\n openclaw plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC}`
|
|
32
37
|
: `\n⚠️ Could not auto-install plugin. After the bot starts, run manually:\n openclaw plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC}`));
|
|
@@ -114,19 +119,23 @@ function resolveWindowsCommand(command) {
|
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
function spawnBackgroundProcess(command, args, options = {}) {
|
|
118
|
-
const { cwd, env = {} } = options;
|
|
119
|
-
const mergedEnv = { ...process.env, ...env };
|
|
120
|
-
|
|
121
|
-
if (process.platform === 'win32') {
|
|
122
|
-
const resolvedCommand = resolveWindowsCommand(command);
|
|
123
|
-
const argList = args.map((arg) => quotePowerShellSingle(arg)).join(', ');
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
122
|
+
function spawnBackgroundProcess(command, args, options = {}) {
|
|
123
|
+
const { cwd, env = {} } = options;
|
|
124
|
+
const mergedEnv = { ...process.env, ...env };
|
|
125
|
+
|
|
126
|
+
if (process.platform === 'win32') {
|
|
127
|
+
const resolvedCommand = resolveWindowsCommand(command);
|
|
128
|
+
const argList = args.map((arg) => quotePowerShellSingle(arg)).join(', ');
|
|
129
|
+
const envAssignments = Object.entries(env)
|
|
130
|
+
.map(([key, value]) => `$env:${key}=${quotePowerShellSingle(String(value))}`)
|
|
131
|
+
.join('; ');
|
|
132
|
+
const startProcessScript = [
|
|
133
|
+
`$filePath = ${quotePowerShellSingle(resolvedCommand)}`,
|
|
134
|
+
`$workingDir = ${quotePowerShellSingle(cwd || process.cwd())}`,
|
|
135
|
+
`$argList = @(${argList})`,
|
|
136
|
+
...(envAssignments ? [envAssignments] : []),
|
|
137
|
+
"Start-Process -WindowStyle Hidden -FilePath $filePath -WorkingDirectory $workingDir -ArgumentList $argList"
|
|
138
|
+
].join('; ');
|
|
130
139
|
|
|
131
140
|
return spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', startProcessScript], {
|
|
132
141
|
cwd,
|
|
@@ -146,26 +155,38 @@ function spawnBackgroundProcess(command, args, options = {}) {
|
|
|
146
155
|
});
|
|
147
156
|
}
|
|
148
157
|
|
|
149
|
-
function resolveNative9RouterDesktopLaunch() {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
158
|
+
function resolveNative9RouterDesktopLaunch() {
|
|
159
|
+
const serverEntry = get9RouterServerEntryCandidates().find((candidate) => fs.existsSync(candidate))
|
|
160
|
+
|| get9RouterServerEntryCandidates()[0];
|
|
161
|
+
return {
|
|
162
|
+
command: process.execPath,
|
|
163
|
+
args: [serverEntry],
|
|
164
|
+
env: {
|
|
165
|
+
PORT: '20128',
|
|
166
|
+
HOSTNAME: '0.0.0.0'
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getProjectOpenClawHome(projectDir) {
|
|
172
|
+
return path.join(projectDir, '.openclaw');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getProject9RouterDataDir(projectDir) {
|
|
176
|
+
return path.join(projectDir, '.9router');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getProjectRuntimeEnv(projectDir, extraEnv = {}) {
|
|
180
|
+
return {
|
|
181
|
+
...process.env,
|
|
182
|
+
OPENCLAW_HOME: getProjectOpenClawHome(projectDir),
|
|
183
|
+
OPENCLAW_STATE_DIR: getProjectOpenClawHome(projectDir),
|
|
184
|
+
DATA_DIR: getProject9RouterDataDir(projectDir),
|
|
185
|
+
...extraEnv,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getGatewayAllowedOrigins(port) {
|
|
169
190
|
const normalizedPort = Number(port) || 18791;
|
|
170
191
|
const origins = new Set([
|
|
171
192
|
`http://localhost:${normalizedPort}`,
|
|
@@ -427,7 +448,7 @@ function resolveCommandOnPath(command) {
|
|
|
427
448
|
}
|
|
428
449
|
}
|
|
429
450
|
|
|
430
|
-
function getGlobalNpmRoot() {
|
|
451
|
+
function getGlobalNpmRoot() {
|
|
431
452
|
try {
|
|
432
453
|
return execSync('npm root -g', {
|
|
433
454
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -435,13 +456,57 @@ function getGlobalNpmRoot() {
|
|
|
435
456
|
shell: true,
|
|
436
457
|
env: process.env
|
|
437
458
|
}).trim();
|
|
438
|
-
} catch {
|
|
439
|
-
if (process.platform === 'win32') {
|
|
440
|
-
return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
|
|
441
|
-
}
|
|
442
|
-
return path.join(os.homedir(), '.local', 'lib', 'node_modules');
|
|
443
|
-
}
|
|
444
|
-
}
|
|
459
|
+
} catch {
|
|
460
|
+
if (process.platform === 'win32') {
|
|
461
|
+
return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'npm', 'node_modules');
|
|
462
|
+
}
|
|
463
|
+
return path.join(os.homedir(), '.local', 'share', 'npm', 'lib', 'node_modules');
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function getNativeOpenClawRootDir(projectDir = '.') {
|
|
468
|
+
return path.join(projectDir, '.openclaw').replace(/\\/g, '/');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function getGeneratedWorkspaceRoot(deployMode, projectDir = '.') {
|
|
472
|
+
return deployMode === 'native' ? getNativeOpenClawRootDir(projectDir) : '/root/.openclaw';
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function get9RouterServerEntryCandidates() {
|
|
476
|
+
const homeDir = os.homedir();
|
|
477
|
+
const npmRoots = [];
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
const root = execSync('npm root -g', {
|
|
481
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
482
|
+
encoding: 'utf8',
|
|
483
|
+
shell: true,
|
|
484
|
+
env: process.env
|
|
485
|
+
}).trim();
|
|
486
|
+
if (root) npmRoots.push(root);
|
|
487
|
+
} catch {
|
|
488
|
+
// handled by fallback candidates below
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const prefixes = [
|
|
492
|
+
process.env.npm_config_prefix,
|
|
493
|
+
process.env.NPM_CONFIG_PREFIX,
|
|
494
|
+
process.env.PREFIX,
|
|
495
|
+
process.env.NPM_PREFIX,
|
|
496
|
+
path.join(homeDir, '.local'),
|
|
497
|
+
path.join(homeDir, '.npm-global'),
|
|
498
|
+
path.join(homeDir, '.local', 'share', 'npm')
|
|
499
|
+
].filter(Boolean);
|
|
500
|
+
|
|
501
|
+
for (const prefix of prefixes) {
|
|
502
|
+
npmRoots.push(path.join(prefix, 'lib', 'node_modules'));
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
npmRoots.push(path.join(homeDir, '.local', 'lib', 'node_modules'));
|
|
506
|
+
npmRoots.push(getGlobalNpmRoot());
|
|
507
|
+
|
|
508
|
+
return [...new Set(npmRoots.map((root) => path.join(root, '9router', 'app', 'server.js')))];
|
|
509
|
+
}
|
|
445
510
|
|
|
446
511
|
function indentBlock(text, spaces) {
|
|
447
512
|
const prefix = ' '.repeat(spaces);
|
|
@@ -460,26 +525,26 @@ function build9RouterComposeEntrypointScript(syncScriptBase64) {
|
|
|
460
525
|
].join('\n');
|
|
461
526
|
}
|
|
462
527
|
|
|
463
|
-
async function writeNative9RouterSyncScript(projectDir) {
|
|
464
|
-
const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
|
|
465
|
-
await fs.ensureDir(path.dirname(syncScriptPath));
|
|
466
|
-
await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(
|
|
467
|
-
return syncScriptPath;
|
|
468
|
-
}
|
|
528
|
+
async function writeNative9RouterSyncScript(projectDir) {
|
|
529
|
+
const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
|
|
530
|
+
await fs.ensureDir(path.dirname(syncScriptPath));
|
|
531
|
+
await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(getProject9RouterDataDir(projectDir), 'db.json')));
|
|
532
|
+
return syncScriptPath;
|
|
533
|
+
}
|
|
469
534
|
|
|
470
535
|
function extractFirstHttpUrl(text) {
|
|
471
536
|
const match = String(text || '').match(/https?:\/\/[^\s"'`]+/);
|
|
472
537
|
return match ? match[0] : null;
|
|
473
538
|
}
|
|
474
539
|
|
|
475
|
-
function getTokenizedDashboardUrl(projectDir) {
|
|
476
|
-
try {
|
|
477
|
-
const output = execSync('openclaw dashboard', {
|
|
478
|
-
cwd: projectDir,
|
|
479
|
-
env:
|
|
480
|
-
encoding: 'utf8',
|
|
481
|
-
shell: true,
|
|
482
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
540
|
+
function getTokenizedDashboardUrl(projectDir) {
|
|
541
|
+
try {
|
|
542
|
+
const output = execSync('openclaw dashboard', {
|
|
543
|
+
cwd: projectDir,
|
|
544
|
+
env: getProjectRuntimeEnv(projectDir),
|
|
545
|
+
encoding: 'utf8',
|
|
546
|
+
shell: true,
|
|
547
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
483
548
|
timeout: 15000
|
|
484
549
|
});
|
|
485
550
|
return extractFirstHttpUrl(output);
|
|
@@ -607,14 +672,14 @@ function extractZaloPairingCode(text) {
|
|
|
607
672
|
return null;
|
|
608
673
|
}
|
|
609
674
|
|
|
610
|
-
function approveZaloPairingCode({ pairingCode, projectDir, isVi }) {
|
|
611
|
-
try {
|
|
612
|
-
execSync(`openclaw pairing approve zalouser ${pairingCode}`, {
|
|
613
|
-
cwd: projectDir,
|
|
614
|
-
stdio: 'inherit',
|
|
615
|
-
shell: true,
|
|
616
|
-
env:
|
|
617
|
-
});
|
|
675
|
+
function approveZaloPairingCode({ pairingCode, projectDir, isVi }) {
|
|
676
|
+
try {
|
|
677
|
+
execSync(`openclaw pairing approve zalouser ${pairingCode}`, {
|
|
678
|
+
cwd: projectDir,
|
|
679
|
+
stdio: 'inherit',
|
|
680
|
+
shell: true,
|
|
681
|
+
env: getProjectRuntimeEnv(projectDir)
|
|
682
|
+
});
|
|
618
683
|
console.log(chalk.green(isVi
|
|
619
684
|
? `✅ Da tu dong approve pairing code Zalo: ${pairingCode}`
|
|
620
685
|
: `✅ Automatically approved the Zalo pairing code: ${pairingCode}`));
|
|
@@ -645,11 +710,12 @@ async function runNativeZaloPersonalLoginFlow({ isVi, projectDir }) {
|
|
|
645
710
|
// ignore stale project QR cleanup failures
|
|
646
711
|
}
|
|
647
712
|
|
|
648
|
-
const child = spawn('openclaw', ['channels', 'login', '--channel', 'zalouser', '--verbose'], {
|
|
649
|
-
cwd: projectDir,
|
|
650
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
651
|
-
shell: process.platform === 'win32'
|
|
652
|
-
|
|
713
|
+
const child = spawn('openclaw', ['channels', 'login', '--channel', 'zalouser', '--verbose'], {
|
|
714
|
+
cwd: projectDir,
|
|
715
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
716
|
+
shell: process.platform === 'win32',
|
|
717
|
+
env: getProjectRuntimeEnv(projectDir),
|
|
718
|
+
});
|
|
653
719
|
|
|
654
720
|
let loginSucceeded = false;
|
|
655
721
|
let approvedPairingCode = null;
|
|
@@ -712,14 +778,14 @@ async function runNativeZaloPersonalLoginFlow({ isVi, projectDir }) {
|
|
|
712
778
|
}
|
|
713
779
|
}
|
|
714
780
|
|
|
715
|
-
function runPm2Save({ projectDir, isVi }) {
|
|
716
|
-
try {
|
|
717
|
-
execSync('pm2 save', {
|
|
718
|
-
cwd: projectDir,
|
|
719
|
-
stdio: 'inherit',
|
|
720
|
-
shell: true,
|
|
721
|
-
env:
|
|
722
|
-
});
|
|
781
|
+
function runPm2Save({ projectDir, isVi }) {
|
|
782
|
+
try {
|
|
783
|
+
execSync('pm2 save', {
|
|
784
|
+
cwd: projectDir,
|
|
785
|
+
stdio: 'inherit',
|
|
786
|
+
shell: true,
|
|
787
|
+
env: getProjectRuntimeEnv(projectDir)
|
|
788
|
+
});
|
|
723
789
|
} catch {
|
|
724
790
|
console.log(chalk.yellow(isVi
|
|
725
791
|
? '⚠️ PM2 save khong hoan tat. Bot van co the dang chay, nhung hay thu chay lai `pm2 save` sau.'
|
|
@@ -727,9 +793,9 @@ function runPm2Save({ projectDir, isVi }) {
|
|
|
727
793
|
}
|
|
728
794
|
}
|
|
729
795
|
|
|
730
|
-
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
731
|
-
const routerAppName = `${appName}-9router`;
|
|
732
|
-
const routerLaunch = resolveNative9RouterDesktopLaunch();
|
|
796
|
+
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
797
|
+
const routerAppName = `${appName}-9router`;
|
|
798
|
+
const routerLaunch = resolveNative9RouterDesktopLaunch();
|
|
733
799
|
execFileSync('pm2', [
|
|
734
800
|
'start',
|
|
735
801
|
routerLaunch.command,
|
|
@@ -741,11 +807,11 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
|
741
807
|
'none',
|
|
742
808
|
'--',
|
|
743
809
|
...routerLaunch.args
|
|
744
|
-
], {
|
|
745
|
-
cwd: projectDir,
|
|
746
|
-
stdio: 'inherit',
|
|
747
|
-
env:
|
|
748
|
-
});
|
|
810
|
+
], {
|
|
811
|
+
cwd: projectDir,
|
|
812
|
+
stdio: 'inherit',
|
|
813
|
+
env: getProjectRuntimeEnv(projectDir, routerLaunch.env)
|
|
814
|
+
});
|
|
749
815
|
if (syncScriptPath) {
|
|
750
816
|
const syncAppName = `${appName}-9router-sync`;
|
|
751
817
|
const normalizedSyncScriptPath = syncScriptPath.replace(/\\/g, '/');
|
|
@@ -759,19 +825,19 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
|
759
825
|
projectDir.replace(/\\/g, '/'),
|
|
760
826
|
'--interpreter',
|
|
761
827
|
process.execPath
|
|
762
|
-
], {
|
|
763
|
-
cwd: projectDir,
|
|
764
|
-
stdio: 'inherit',
|
|
765
|
-
env:
|
|
766
|
-
});
|
|
828
|
+
], {
|
|
829
|
+
cwd: projectDir,
|
|
830
|
+
stdio: 'inherit',
|
|
831
|
+
env: getProjectRuntimeEnv(projectDir)
|
|
832
|
+
});
|
|
767
833
|
} catch {
|
|
768
834
|
try {
|
|
769
|
-
execSync(`nohup "${process.execPath}" "${normalizedSyncScriptPath}" >/tmp/${syncAppName}.log 2>&1 &`, {
|
|
770
|
-
cwd: projectDir,
|
|
771
|
-
stdio: 'ignore',
|
|
772
|
-
shell: true,
|
|
773
|
-
env:
|
|
774
|
-
});
|
|
835
|
+
execSync(`nohup "${process.execPath}" "${normalizedSyncScriptPath}" >/tmp/${syncAppName}.log 2>&1 &`, {
|
|
836
|
+
cwd: projectDir,
|
|
837
|
+
stdio: 'ignore',
|
|
838
|
+
shell: true,
|
|
839
|
+
env: getProjectRuntimeEnv(projectDir)
|
|
840
|
+
});
|
|
775
841
|
console.log(chalk.yellow(isVi
|
|
776
842
|
? `⚠️ PM2 khong khoi dong duoc sync helper. Da fallback sang background node: /tmp/${syncAppName}.log`
|
|
777
843
|
: `⚠️ PM2 could not start the sync helper. Fell back to a background node process: /tmp/${syncAppName}.log`));
|
|
@@ -787,24 +853,21 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
|
787
853
|
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${routerAppName}` : ` View logs: pm2 logs ${routerAppName}`));
|
|
788
854
|
}
|
|
789
855
|
|
|
790
|
-
async function
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
return false;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
856
|
+
async function ensureProjectRuntimeDirs(projectDir, isVi) {
|
|
857
|
+
try {
|
|
858
|
+
await fs.ensureDir(getProjectOpenClawHome(projectDir));
|
|
859
|
+
await fs.ensureDir(getProject9RouterDataDir(projectDir));
|
|
860
|
+
console.log(chalk.green(`\n✅ ${isVi
|
|
861
|
+
? 'Runtime project đã sẵn sàng trong thư mục đã chọn.'
|
|
862
|
+
: 'The project runtime directories are ready inside the chosen folder.'}`));
|
|
863
|
+
return true;
|
|
864
|
+
} catch {
|
|
865
|
+
console.log(chalk.yellow(`\n⚠️ ${isVi
|
|
866
|
+
? `Không thể tạo runtime folders trong project. Hãy tự kiểm tra: ${projectDir}`
|
|
867
|
+
: `Could not create the runtime folders inside the project. Check: ${projectDir}`}`));
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
808
871
|
|
|
809
872
|
function buildTelegramPostInstallChecklist({ isVi, bots, groupId }) {
|
|
810
873
|
const botList = bots.map((bot, idx) => `- **${bot?.name || `Bot ${idx + 1}`}** — token: ${String(bot?.token || '').slice(0, 10)}...`).join('\n');
|
|
@@ -1766,20 +1829,20 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1766
1829
|
|
|
1767
1830
|
const sharedConfig = {
|
|
1768
1831
|
meta: { lastTouchedVersion: '2026.3.24' },
|
|
1769
|
-
agents: {
|
|
1770
|
-
defaults: {
|
|
1832
|
+
agents: {
|
|
1833
|
+
defaults: {
|
|
1771
1834
|
model: { primary: modelsPrimary, fallbacks: [] },
|
|
1772
1835
|
compaction: { mode: 'safeguard' },
|
|
1773
1836
|
timeoutSeconds: provider.isLocal ? 900 : 120,
|
|
1774
1837
|
...(provider.isLocal ? { llm: { idleTimeoutSeconds: 300 } } : {}),
|
|
1775
1838
|
},
|
|
1776
|
-
list: agentMetas.map((meta) => ({
|
|
1777
|
-
id: meta.agentId,
|
|
1778
|
-
name: meta.name,
|
|
1779
|
-
workspace:
|
|
1780
|
-
agentDir:
|
|
1781
|
-
model: { primary: modelsPrimary, fallbacks: [] },
|
|
1782
|
-
})),
|
|
1839
|
+
list: agentMetas.map((meta) => ({
|
|
1840
|
+
id: meta.agentId,
|
|
1841
|
+
name: meta.name,
|
|
1842
|
+
workspace: `${deployMode === 'native' ? getNativeOpenClawRootDir(projectDir) : '/root/.openclaw'}/${meta.workspaceDir}`,
|
|
1843
|
+
agentDir: `${deployMode === 'native' ? getNativeOpenClawRootDir(projectDir) : '/root/.openclaw'}/agents/${meta.agentId}/agent`,
|
|
1844
|
+
model: { primary: modelsPrimary, fallbacks: [] },
|
|
1845
|
+
})),
|
|
1783
1846
|
},
|
|
1784
1847
|
...(providerKey === '9router' ? {
|
|
1785
1848
|
models: {
|
|
@@ -1843,12 +1906,12 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1843
1906
|
},
|
|
1844
1907
|
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' },
|
|
1845
1908
|
},
|
|
1846
|
-
};
|
|
1847
|
-
sharedConfig.plugins = {
|
|
1848
|
-
entries: {
|
|
1849
|
-
[
|
|
1850
|
-
},
|
|
1851
|
-
};
|
|
1909
|
+
};
|
|
1910
|
+
sharedConfig.plugins = {
|
|
1911
|
+
entries: {
|
|
1912
|
+
[TELEGRAM_RELAY_PLUGIN_RUNTIME_ID]: { enabled: true },
|
|
1913
|
+
},
|
|
1914
|
+
};
|
|
1852
1915
|
|
|
1853
1916
|
if (hasBrowserDesktop) {
|
|
1854
1917
|
sharedConfig.browser = {
|
|
@@ -1876,22 +1939,22 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1876
1939
|
` name: '${botName || 'openclaw-multibot'}',`,
|
|
1877
1940
|
` script: 'openclaw',`,
|
|
1878
1941
|
` args: 'gateway run',`,
|
|
1879
|
-
` cwd: '${projectDir.replace(/\\/g, '/')}',`,
|
|
1880
|
-
` interpreter: 'none',`,
|
|
1881
|
-
` autorestart: true,`,
|
|
1882
|
-
` watch: false,`,
|
|
1883
|
-
` env: { NODE_ENV: 'production' }`,
|
|
1884
|
-
' },',
|
|
1885
|
-
' {',
|
|
1942
|
+
` cwd: '${projectDir.replace(/\\/g, '/')}',`,
|
|
1943
|
+
` interpreter: 'none',`,
|
|
1944
|
+
` autorestart: true,`,
|
|
1945
|
+
` watch: false,`,
|
|
1946
|
+
` env: { NODE_ENV: 'production', OPENCLAW_HOME: '${path.join(projectDir, '.openclaw').replace(/\\/g, '/')}', DATA_DIR: '${path.join(projectDir, '.9router').replace(/\\/g, '/')}' }`,
|
|
1947
|
+
' },',
|
|
1948
|
+
' {',
|
|
1886
1949
|
` name: '${botName || 'openclaw-multibot'}-auto-approve',`,
|
|
1887
1950
|
` script: 'sh',`,
|
|
1888
1951
|
` args: '-c "while true; do npx --yes openclaw devices approve --latest 2>/dev/null || true; sleep 5; done"',`,
|
|
1889
|
-
` cwd: '${projectDir.replace(/\\/g, '/')}',`,
|
|
1890
|
-
` interpreter: 'none',`,
|
|
1891
|
-
` autorestart: true,`,
|
|
1892
|
-
` watch: false,`,
|
|
1893
|
-
` env: { NODE_ENV: 'production' }`,
|
|
1894
|
-
' }',
|
|
1952
|
+
` cwd: '${projectDir.replace(/\\/g, '/')}',`,
|
|
1953
|
+
` interpreter: 'none',`,
|
|
1954
|
+
` autorestart: true,`,
|
|
1955
|
+
` watch: false,`,
|
|
1956
|
+
` env: { NODE_ENV: 'production', OPENCLAW_HOME: '${path.join(projectDir, '.openclaw').replace(/\\/g, '/')}', DATA_DIR: '${path.join(projectDir, '.9router').replace(/\\/g, '/')}' }`,
|
|
1957
|
+
' }',
|
|
1895
1958
|
].join('\n');
|
|
1896
1959
|
const ecosystemContent = [
|
|
1897
1960
|
'// PM2 ecosystem — run: pm2 start ecosystem.config.js',
|
|
@@ -1956,7 +2019,7 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
1956
2019
|
const relayTargetNames = otherAgents.length ? otherAgents.map((peer) => `\`${peer.name}\``).join(', ') : '`bot khac`';
|
|
1957
2020
|
const relayTargetIds = otherAgents.length ? otherAgents.map((peer) => `\`${peer.agentId}\``).join(', ') : '`agent-khac`';
|
|
1958
2021
|
const agentsMd = `# ${isVi ? 'Huong dan van hanh' : 'Operating Manual'}\n\n## ${isVi ? 'Vai tro' : 'Role'}\n${isVi ? `Ban la **${meta.name}**, chuyen ve ${meta.desc}.` : `You are **${meta.name}**, focused on ${meta.desc}.`}\n\n## ${isVi ? 'Khi nao nen tra loi' : 'When To Reply'}\n- ${isVi ? `Coi user dang goi ban neu tin nhan co mot trong cac alias: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}.` : `Treat the message as addressed to you when it includes one of your aliases: ${ownAliases.map((alias) => `\`${alias}\``).join(', ')}.`}\n- ${isVi ? 'Neu user tag username Telegram cua ban thi luon tra loi.' : 'Always reply when your Telegram username is tagged.'}\n- ${isVi ? 'Reaction xac nhan se duoc gateway tu dong tha bang `👍` ngay khi nhan tin; khong can tu tha bang tay neu da thay ack.' : 'The gateway will auto-ack with `👍` as soon as a message arrives; do not manually duplicate the reaction if the ack already appeared.'}\n- ${isVi ? `Neu user dang goi ro bot khac ${relayTargetNames} thi khong cuop loi.` : `If the message is clearly calling another bot such as ${relayTargetNames}, do not hijack it.`}\n\n## ${isVi ? 'Phoi hop' : 'Coordination'}\n- ${isVi ? 'Dung `TEAM.md` lam nguon su that cho vai tro cua ca doi.' : 'Use `TEAM.md` as the source of truth for team roles.'}\n- ${isVi ? `Neu user bao ban hoi, chuyen viec, xin y kien, hoac phoi hop voi ${otherAgents.length ? otherAgents.map((peer) => peer.name).join(', ') : 'bot khac'}, hay dung agent-to-agent noi bo ngay trong turn hien tai.` : `If the user asks you to consult, delegate to, or coordinate with ${otherAgents.length ? otherAgents.map((peer) => peer.name).join(', ') : 'another bot'}, use internal agent-to-agent messaging in the same turn.`}\n- ${isVi ? '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.' : '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- ${isVi ? `Khi handoff, phai goi dung agent id ky thuat ${relayTargetIds}, khong dung ten hien thi.` : `When handing off, use the exact technical agent id ${relayTargetIds}, not the display name.`}\n- ${isVi ? '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.' : 'If you are the target bot receiving a handoff, publish the real answer immediately 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- ${isVi ? 'Khong bao user phai tag lai bot kia neu ban co the hoi noi bo duoc.' : 'Do not ask the user to tag the other bot again if you can consult internally.'}\n`;
|
|
1959
|
-
const toolsMd = `# ${isVi ? 'Huong dan dung tool' : 'Tool Usage Guide'}\n\n${skillListStr}\n\n- ${isVi ? 'Tom tat ket qua tool thay vi dump raw output.' : 'Summarize tool output instead of dumping raw output.'}\n- ${isVi ? `Workspace cua ban la
|
|
2022
|
+
const toolsMd = `# ${isVi ? 'Huong dan dung tool' : 'Tool Usage Guide'}\n\n${skillListStr}\n\n- ${isVi ? 'Tom tat ket qua tool thay vi dump raw output.' : 'Summarize tool output instead of dumping raw output.'}\n- ${isVi ? `Workspace cua ban la \`${getGeneratedWorkspaceRoot(deployMode, projectDir)}/${meta.workspaceDir}/\`.` : `Your workspace is \`${getGeneratedWorkspaceRoot(deployMode, projectDir)}/${meta.workspaceDir}/\`.`}\n- ${isVi ? 'Telegram da bat `ackReaction`, `replyToMode:first`, `actions.sendMessage`, va `actions.reactions`.' : 'Telegram is configured with `ackReaction`, `replyToMode:first`, `actions.sendMessage`, and `actions.reactions`.'}\n- ${isVi ? 'Khi can relay public bang account cua minh sau internal handoff, uu tien dung chinh outbound Telegram action thay vi tra loi mo ho.' : 'When you need to publish a public relay from your own account after an internal handoff, prefer the Telegram outbound action over an ambiguous plain-text reply.'}\n`;
|
|
1960
2023
|
const relayMd = isVi
|
|
1961
2024
|
? `# 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`
|
|
1962
2025
|
: `# Telegram Relay Playbook\n\n## Goal\n- Let the caller bot consult the target bot internally, then have the target bot publish the real answer with its own Telegram account.\n\n## Protocol\n1. The caller bot sends one short acknowledgement.\n2. The caller bot hands off internally using the exact agent id from \`TEAM.md\`.\n3. The target bot publishes the real answer into the same chat/thread.\n4. If \`[[reply_to_current]]\` or Telegram send/sendMessage is available, prefer it so the answer attaches to the original user turn.\n5. Only the caller bot may summarize as fallback when the handoff clearly fails.\n`;
|
|
@@ -2140,8 +2203,8 @@ ${hasBrowserDesktop ? ` extra_hosts:
|
|
|
2140
2203
|
const skillListStr = selectedSkillNamesForMd.length > 0 ? selectedSkillNamesForMd.join('\n') : isVi ? '- _(Chưa có skill nào)_' : '- _(No skills installed)_';
|
|
2141
2204
|
|
|
2142
2205
|
const toolsMd = isVi
|
|
2143
|
-
? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillListStr}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi → thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output\n\n## Quy ước\n- Web Search: chỉ dùng khi cần thông tin realtime hoặc user yêu cầu\n- Browser: chỉ mở trang khi user yêu cầu cụ thể\n- Memory: tự ghi nhớ thông vị tự nhiên, không cần user nhắc\n\n## ⏰ Cron / Lên lịch nhắc nhở\n- OpenClaw CÓ hỗ trợ tool hệ thống để chạy Cron Job.\n- Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool hệ thống để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.\n- Ghi chú lỗi: Không điền "current" vào thư mục Session khi thao tác tool. Bỏ qua việc tra cứu file docs nội bộ ('cron-jobs.mdx') — hãy tin tưởng khả năng sử dụng tool của bạn.\n\n## 📁 File & Workspace\n- Bot có thể đọc/ghi file trong thư mục workspace:
|
|
2144
|
-
: `# Tool Usage Guide\n\n## Installed Skills\n${skillListStr}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error → retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data\n\n## Conventions\n- Web Search: only use when needing real-time info or user explicitly asks\n- Browser: only open pages when user specifically requests\n- Memory: proactively remember important info without user prompting\n\n## ⏰ Cron / Scheduled Tasks\n- OpenClaw natively supports system tools for Cron Jobs.\n- When the user asks to schedule tasks or reminders, use built-in tools automatically. Do NOT ask users to run manual crontab on the host.\n- Do NOT use "current" as a sessionKey for session tools.\n\n## 📁 File & Workspace\n- Bot can read/write files in workspace:
|
|
2206
|
+
? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillListStr}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi → thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output\n\n## Quy ước\n- Web Search: chỉ dùng khi cần thông tin realtime hoặc user yêu cầu\n- Browser: chỉ mở trang khi user yêu cầu cụ thể\n- Memory: tự ghi nhớ thông vị tự nhiên, không cần user nhắc\n\n## ⏰ Cron / Lên lịch nhắc nhở\n- OpenClaw CÓ hỗ trợ tool hệ thống để chạy Cron Job.\n- Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool hệ thống để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.\n- Ghi chú lỗi: Không điền "current" vào thư mục Session khi thao tác tool. Bỏ qua việc tra cứu file docs nội bộ ('cron-jobs.mdx') — hãy tin tưởng khả năng sử dụng tool của bạn.\n\n## 📁 File & Workspace\n- Bot có thể đọc/ghi file trong thư mục workspace: \`${getGeneratedWorkspaceRoot(deployMode, projectDir)}/workspace/\`\n- Dùng để lưu notes, scripts, cấu hình tạm\n\n## 🛠️ Tool Error Handling\n- Retry tối đa 2 lần nếu tool lỗi network\n- Nếu vẫn lỗi: báo user kèm mô tả lỗi cụ thể và gợi ý workaround\n`
|
|
2207
|
+
: `# Tool Usage Guide\n\n## Installed Skills\n${skillListStr}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error → retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data\n\n## Conventions\n- Web Search: only use when needing real-time info or user explicitly asks\n- Browser: only open pages when user specifically requests\n- Memory: proactively remember important info without user prompting\n\n## ⏰ Cron / Scheduled Tasks\n- OpenClaw natively supports system tools for Cron Jobs.\n- When the user asks to schedule tasks or reminders, use built-in tools automatically. Do NOT ask users to run manual crontab on the host.\n- Do NOT use "current" as a sessionKey for session tools.\n\n## 📁 File & Workspace\n- Bot can read/write files in workspace: \`${getGeneratedWorkspaceRoot(deployMode, projectDir)}/workspace/\`\n\n## 🛠️ Tool Error Handling\n- Retry up to 2 times on network errors\n- If still failing: report to user with specific error description and workaround\n`;
|
|
2145
2208
|
|
|
2146
2209
|
const memoryMd = `# ${isVi ? 'Bộ nhớ dài hạn' : 'Long-term Memory'}\n\n> File này lưu những điều quan trọng cần nhớ xuyên suốt các phiên hội thoại.\n\n## Ghi chú\n- _(Chưa có gì)_\n\n---`;
|
|
2147
2210
|
|
|
@@ -2210,7 +2273,7 @@ const { chromium } = require('playwright');
|
|
|
2210
2273
|
})();
|
|
2211
2274
|
`;
|
|
2212
2275
|
await fs.writeFile(path.join(loopBotDir, '.openclaw', 'workspace', 'browser-tool.js'), browserToolJs);
|
|
2213
|
-
const browserMd = `# Browser Automation (Desktop Mode)\n\nBot controls your actual Chrome on screen. Every action is visible!\n\n## Usage\n\`\`\`bash\nnode /
|
|
2276
|
+
const browserMd = `# Browser Automation (Desktop Mode)\n\nBot controls your actual Chrome on screen. Every action is visible!\n\n## Usage\n\`\`\`bash\nnode ${getGeneratedWorkspaceRoot(deployMode, projectDir)}/workspace/browser-tool.js status\nnode ${getGeneratedWorkspaceRoot(deployMode, projectDir)}/workspace/browser-tool.js open "https://google.com"\nnode ${getGeneratedWorkspaceRoot(deployMode, projectDir)}/workspace/browser-tool.js get_text\nnode ${getGeneratedWorkspaceRoot(deployMode, projectDir)}/workspace/browser-tool.js fill "input[name='q']" "search"\nnode ${getGeneratedWorkspaceRoot(deployMode, projectDir)}/workspace/browser-tool.js press "Enter"\n\`\`\`\n\n## MANDATORY RULES\n- NEVER refuse to open the browser when user asks.\n- If ECONNREFUSED: tell user to run start-chrome-debug.bat first.\n`;
|
|
2214
2277
|
await fs.writeFile(path.join(loopBotDir, '.openclaw', 'workspace', 'BROWSER.md'), browserMd);
|
|
2215
2278
|
} else if (hasBrowserServer) {
|
|
2216
2279
|
const browserServerMd = `# Browser Automation (Headless Server Mode)\n\nBot uses a headless Chromium instance running inside the Docker container. No GUI needed!\n\n## Notes\n- Running on Ubuntu Server / VPS (no GUI required)\n- Uses Playwright + Headless Chromium installed inside Docker\n- For Cloudflare bypass, switch to Desktop mode (requires Windows/Mac with Chrome)\n`;
|
|
@@ -2394,24 +2457,7 @@ fi
|
|
|
2394
2457
|
}
|
|
2395
2458
|
}
|
|
2396
2459
|
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
const homedir = os.homedir();
|
|
2400
|
-
const globalClawDir = path.join(homedir, '.openclaw');
|
|
2401
|
-
const localClawDir = path.join(projectDir, '.openclaw');
|
|
2402
|
-
try {
|
|
2403
|
-
await fs.ensureDir(globalClawDir);
|
|
2404
|
-
await fs.copy(localClawDir, globalClawDir, { overwrite: true });
|
|
2405
|
-
console.log(chalk.green(`\n✅ ${isVi
|
|
2406
|
-
? `Config đã được sync vào ~/.openclaw/ — openclaw sẵn sàng!`
|
|
2407
|
-
: `Config synced to ~/.openclaw/ — openclaw is ready!`}`));
|
|
2408
|
-
} catch (syncErr) {
|
|
2409
|
-
console.log(chalk.yellow(`\n⚠️ ${isVi
|
|
2410
|
-
? `Không thể tự sync config. Chạy thủ công:\n cp -rn ${localClawDir}/. ${globalClawDir}/`
|
|
2411
|
-
: `Could not auto-sync config. Run manually:\n cp -rn ${localClawDir}/. ${globalClawDir}/`}`));
|
|
2412
|
-
}
|
|
2413
|
-
|
|
2414
|
-
if (isMultiBot && channelKey === 'telegram') {
|
|
2460
|
+
if (isMultiBot && channelKey === 'telegram') {
|
|
2415
2461
|
console.log(chalk.yellow(`\n${isVi ? '📋 Xem hướng dẫn sau cài:' : '📋 Read post-install guide:'} ${path.join(projectDir, 'TELEGRAM-POST-INSTALL.md')}`));
|
|
2416
2462
|
}
|
|
2417
2463
|
} else {
|
|
@@ -2446,7 +2492,7 @@ fi
|
|
|
2446
2492
|
native9RouterSyncScriptPath = await writeNative9RouterSyncScript(projectDir);
|
|
2447
2493
|
}
|
|
2448
2494
|
|
|
2449
|
-
await
|
|
2495
|
+
await ensureProjectRuntimeDirs(projectDir, isVi);
|
|
2450
2496
|
|
|
2451
2497
|
if (isMultiBot && channelKey === 'telegram') {
|
|
2452
2498
|
installRelayPluginForProject(projectDir, isVi);
|
|
@@ -2464,11 +2510,12 @@ fi
|
|
|
2464
2510
|
if (providerKey === '9router') {
|
|
2465
2511
|
startNative9RouterPm2({ isVi, projectDir, appName: botName || 'openclaw-multibot', syncScriptPath: native9RouterSyncScriptPath });
|
|
2466
2512
|
}
|
|
2467
|
-
execSync('pm2 start ecosystem.config.js && pm2 save', {
|
|
2468
|
-
cwd: projectDir,
|
|
2469
|
-
stdio: 'inherit',
|
|
2470
|
-
shell: true
|
|
2471
|
-
|
|
2513
|
+
execSync('pm2 start ecosystem.config.js && pm2 save', {
|
|
2514
|
+
cwd: projectDir,
|
|
2515
|
+
stdio: 'inherit',
|
|
2516
|
+
shell: true,
|
|
2517
|
+
env: getProjectRuntimeEnv(projectDir)
|
|
2518
|
+
});
|
|
2472
2519
|
console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoan tat! Multi-bot native dang chay qua PM2.' : 'Setup complete! Native multi-bot is running via PM2.'}`));
|
|
2473
2520
|
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${botName || 'openclaw-multibot'}` : ` View logs: pm2 logs ${botName || 'openclaw-multibot'}`));
|
|
2474
2521
|
printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
|
|
@@ -2483,11 +2530,24 @@ fi
|
|
|
2483
2530
|
if (channelKey === 'zalo-personal') {
|
|
2484
2531
|
await runNativeZaloPersonalLoginFlow({ isVi, projectDir });
|
|
2485
2532
|
}
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2533
|
+
execFileSync('pm2', [
|
|
2534
|
+
'start',
|
|
2535
|
+
'openclaw',
|
|
2536
|
+
'--name',
|
|
2537
|
+
appName,
|
|
2538
|
+
'--cwd',
|
|
2539
|
+
projectDir.replace(/\\/g, '/'),
|
|
2540
|
+
'--interpreter',
|
|
2541
|
+
'none',
|
|
2542
|
+
'--',
|
|
2543
|
+
'gateway',
|
|
2544
|
+
'run'
|
|
2545
|
+
], {
|
|
2546
|
+
cwd: projectDir,
|
|
2547
|
+
stdio: 'inherit',
|
|
2548
|
+
env: getProjectRuntimeEnv(projectDir)
|
|
2549
|
+
});
|
|
2550
|
+
runPm2Save({ projectDir, isVi });
|
|
2491
2551
|
console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoan tat! Bot native dang chay qua PM2.' : 'Setup complete! Native bot is running via PM2.'}`));
|
|
2492
2552
|
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${appName}` : ` View logs: pm2 logs ${appName}`));
|
|
2493
2553
|
printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
|
|
@@ -2499,16 +2559,17 @@ fi
|
|
|
2499
2559
|
if (providerKey === '9router') {
|
|
2500
2560
|
console.log(chalk.yellow(`\n${isVi ? 'Khoi dong 9Router native (background)...' : 'Starting native 9Router (background)...'}`));
|
|
2501
2561
|
const native9RouterLaunch = resolveNative9RouterDesktopLaunch();
|
|
2502
|
-
spawnBackgroundProcess(native9RouterLaunch.command, native9RouterLaunch.args, {
|
|
2503
|
-
cwd: projectDir,
|
|
2504
|
-
env: native9RouterLaunch.env
|
|
2505
|
-
}).unref();
|
|
2562
|
+
spawnBackgroundProcess(native9RouterLaunch.command, native9RouterLaunch.args, {
|
|
2563
|
+
cwd: projectDir,
|
|
2564
|
+
env: getProjectRuntimeEnv(projectDir, native9RouterLaunch.env)
|
|
2565
|
+
}).unref();
|
|
2506
2566
|
const routerHealth = await waitFor9RouterApiReady();
|
|
2507
|
-
if (native9RouterSyncScriptPath) {
|
|
2508
|
-
spawnBackgroundProcess(process.execPath, [native9RouterSyncScriptPath], {
|
|
2509
|
-
cwd: projectDir
|
|
2510
|
-
|
|
2511
|
-
|
|
2567
|
+
if (native9RouterSyncScriptPath) {
|
|
2568
|
+
spawnBackgroundProcess(process.execPath, [native9RouterSyncScriptPath], {
|
|
2569
|
+
cwd: projectDir,
|
|
2570
|
+
env: getProjectRuntimeEnv(projectDir)
|
|
2571
|
+
}).unref();
|
|
2572
|
+
}
|
|
2512
2573
|
console.log(chalk.gray(isVi
|
|
2513
2574
|
? ' 9Router dashboard: http://localhost:20128/dashboard'
|
|
2514
2575
|
: ' 9Router dashboard: http://localhost:20128/dashboard'));
|
|
@@ -2523,11 +2584,12 @@ fi
|
|
|
2523
2584
|
}
|
|
2524
2585
|
console.log(chalk.yellow(`\n${isVi ? 'Khoi dong native bot (foreground)...' : 'Starting native bot (foreground)...'}`));
|
|
2525
2586
|
const isZaloPersonal = channelKey === 'zalo-personal';
|
|
2526
|
-
const child = spawn('openclaw', ['gateway', 'run'], {
|
|
2527
|
-
cwd: projectDir,
|
|
2528
|
-
stdio: isZaloPersonal ? ['inherit', 'pipe', 'pipe'] : 'inherit',
|
|
2529
|
-
shell: process.platform === 'win32'
|
|
2530
|
-
|
|
2587
|
+
const child = spawn('openclaw', ['gateway', 'run'], {
|
|
2588
|
+
cwd: projectDir,
|
|
2589
|
+
stdio: isZaloPersonal ? ['inherit', 'pipe', 'pipe'] : 'inherit',
|
|
2590
|
+
shell: process.platform === 'win32',
|
|
2591
|
+
env: getProjectRuntimeEnv(projectDir),
|
|
2592
|
+
});
|
|
2531
2593
|
if (isZaloPersonal) {
|
|
2532
2594
|
let approvedPairingCode = null;
|
|
2533
2595
|
const onGatewayChunk = (chunk, target) => {
|