create-openclaw-bot 5.8.4 → 5.8.8
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/README.md +2 -2
- package/README.vi.md +2 -2
- package/dist/server/local-server.js +329 -55
- package/dist/setup/data/index.js +1 -1
- package/dist/setup/shared/bot-config-gen.js +1 -1
- package/dist/setup/shared/common-gen.js +1 -0
- package/dist/setup/shared/docker-gen.js +5 -5
- package/dist/setup/shared/workspace-gen.js +1200 -645
- package/dist/web/app.js +3 -2
- package/package.json +1 -1
- package/dist/setup/data/skills.js +0 -159
|
@@ -13,7 +13,7 @@ function loadSharedModule(modulePath, globalName) {
|
|
|
13
13
|
if (loaded && Object.keys(loaded).length > 0) return loaded;
|
|
14
14
|
return globalThis[globalName] || loaded || {};
|
|
15
15
|
}
|
|
16
|
-
const { buildWorkspaceFileMap, buildCronjobSkillMd, buildInfographicGeneratorSkillMd, buildInfographicGeneratorJs } = loadSharedModule('../setup/shared/workspace-gen.js', '__openclawWorkspace');
|
|
16
|
+
const { buildWorkspaceFileMap, buildCronjobSkillMd, buildInfographicGeneratorSkillMd, buildInfographicGeneratorJs, buildStickerMentionSkillMd, buildStickerMentionJs } = loadSharedModule('../setup/shared/workspace-gen.js', '__openclawWorkspace');
|
|
17
17
|
const { buildOpenclawJson, buildEnvFileContent, buildExecApprovalsJson } = loadSharedModule('../setup/shared/bot-config-gen.js', '__openclawBotConfig');
|
|
18
18
|
const { buildDockerArtifacts } = loadSharedModule('../setup/shared/docker-gen.js', '__openclawDockerGen');
|
|
19
19
|
const { OPENCLAW_NPM_SPEC, NINE_ROUTER_NPM_SPEC, build9RouterProviderConfig, get9RouterBaseUrl } = loadSharedModule('../setup/shared/common-gen.js', '__openclawCommon');
|
|
@@ -22,6 +22,27 @@ const dataExport = loadSharedModule('../setup/data/index.js', '__openclawData');
|
|
|
22
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const WEB_DIR = resolve(__dirname, '../web');
|
|
24
24
|
const SETUP_VERSION = (() => { try { return JSON.parse(fs.readFileSync(resolve(__dirname, '../../package.json'), 'utf8')).version || '0.0.0'; } catch { return '0.0.0'; } })();
|
|
25
|
+
let latestSetupVersionCache = SETUP_VERSION;
|
|
26
|
+
let isFetchingLatestSetup = false;
|
|
27
|
+
|
|
28
|
+
async function fetchLatestSetupVersionBg() {
|
|
29
|
+
if (isFetchingLatestSetup) return;
|
|
30
|
+
isFetchingLatestSetup = true;
|
|
31
|
+
try {
|
|
32
|
+
const resp = await fetch('https://registry.npmjs.org/create-openclaw-bot/latest', { signal: AbortSignal.timeout(4000) });
|
|
33
|
+
if (resp.ok) {
|
|
34
|
+
const data = await resp.json();
|
|
35
|
+
if (data.version) {
|
|
36
|
+
latestSetupVersionCache = data.version;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
} finally {
|
|
41
|
+
isFetchingLatestSetup = false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
fetchLatestSetupVersionBg().catch(() => {});
|
|
45
|
+
|
|
25
46
|
const DEFAULT_PROJECT_NAME = 'openclaw-bot';
|
|
26
47
|
const STATE_FILE = '.openclaw-setup-state.json';
|
|
27
48
|
const DEFAULT_MODEL = 'smart-route';
|
|
@@ -75,6 +96,31 @@ function detectOs() {
|
|
|
75
96
|
return 'linux-desktop';
|
|
76
97
|
}
|
|
77
98
|
|
|
99
|
+
function getRealHomedir() {
|
|
100
|
+
const home = os.homedir();
|
|
101
|
+
if (process.platform === 'win32') return home;
|
|
102
|
+
const sudoUser = process.env.SUDO_USER;
|
|
103
|
+
if (sudoUser && (home === '/root' || home.startsWith('/root/'))) {
|
|
104
|
+
const userHome = process.platform === 'darwin' ? `/Users/${sudoUser}` : `/home/${sudoUser}`;
|
|
105
|
+
if (existsSync(userHome)) {
|
|
106
|
+
return userHome;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return home;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveBinPath(cmd) {
|
|
113
|
+
if (!cmd || cmd.includes('/') || cmd.includes('\\')) return cmd;
|
|
114
|
+
const nodeBinDir = dirname(process.argv[0]);
|
|
115
|
+
const localPath = join(nodeBinDir, process.platform === 'win32' ? `${cmd}.cmd` : cmd);
|
|
116
|
+
if (existsSync(localPath)) return localPath;
|
|
117
|
+
const localExe = join(nodeBinDir, process.platform === 'win32' ? `${cmd}.exe` : cmd);
|
|
118
|
+
if (existsSync(localExe)) return localExe;
|
|
119
|
+
const nodeModulesBin = join(process.cwd(), 'node_modules', '.bin', process.platform === 'win32' ? `${cmd}.cmd` : cmd);
|
|
120
|
+
if (existsSync(nodeModulesBin)) return nodeModulesBin;
|
|
121
|
+
return cmd;
|
|
122
|
+
}
|
|
123
|
+
|
|
78
124
|
// Blacklist of Windows system/large directories that should never be walked
|
|
79
125
|
const SYSTEM_DIR_BLACKLIST = new Set([
|
|
80
126
|
'windows', 'program files', 'program files (x86)', 'programdata',
|
|
@@ -107,7 +153,7 @@ function recommendedMode(osChoice) {
|
|
|
107
153
|
function commandExists(cmd, args = ['--version']) {
|
|
108
154
|
return new Promise((resolve) => {
|
|
109
155
|
const shell = process.platform === 'win32';
|
|
110
|
-
execFile(cmd, args, { windowsHide: true, timeout: 5000, shell }, (err, stdout, stderr) => {
|
|
156
|
+
execFile(resolveBinPath(cmd), args, { windowsHide: true, timeout: 5000, shell }, (err, stdout, stderr) => {
|
|
111
157
|
resolve({ ok: !err, output: String(stdout || stderr || '').trim() });
|
|
112
158
|
});
|
|
113
159
|
});
|
|
@@ -116,7 +162,7 @@ function commandExists(cmd, args = ['--version']) {
|
|
|
116
162
|
function run(cmd, args, opts = {}) {
|
|
117
163
|
return new Promise((resolve, reject) => {
|
|
118
164
|
sendLog(`$ ${cmd} ${args.join(' ')}`);
|
|
119
|
-
const child = spawn(cmd, args, { cwd: opts.cwd, shell: process.platform === 'win32', env: { ...process.env, ...(opts.env || {}) } });
|
|
165
|
+
const child = spawn(resolveBinPath(cmd), args, { cwd: opts.cwd, shell: process.platform === 'win32', env: { ...process.env, ...(opts.env || {}) } });
|
|
120
166
|
let stdout = '';
|
|
121
167
|
let resolved = false;
|
|
122
168
|
child.stdout.on('data', (d) => {
|
|
@@ -146,7 +192,7 @@ function run(cmd, args, opts = {}) {
|
|
|
146
192
|
|
|
147
193
|
function startDetached(cmd, args, opts = {}) {
|
|
148
194
|
sendLog(`$ ${cmd} ${args.join(' ')} &`);
|
|
149
|
-
const child = spawn(cmd, args, {
|
|
195
|
+
const child = spawn(resolveBinPath(cmd), args, {
|
|
150
196
|
cwd: opts.cwd,
|
|
151
197
|
shell: process.platform === 'win32',
|
|
152
198
|
detached: true,
|
|
@@ -154,6 +200,10 @@ function startDetached(cmd, args, opts = {}) {
|
|
|
154
200
|
windowsHide: opts.windowsHide ?? true,
|
|
155
201
|
env: { ...process.env, ...(opts.env || {}) },
|
|
156
202
|
});
|
|
203
|
+
child.on('error', (err) => {
|
|
204
|
+
sendLog(`[error] Failed to start background command "${cmd}": ${err.message}`);
|
|
205
|
+
console.error(`Failed to start background command "${cmd}":`, err);
|
|
206
|
+
});
|
|
157
207
|
child.unref();
|
|
158
208
|
return child.pid;
|
|
159
209
|
}
|
|
@@ -243,7 +293,7 @@ function runCapture(cmd, args, opts = {}) {
|
|
|
243
293
|
return new Promise((resolve) => {
|
|
244
294
|
let stdout = '';
|
|
245
295
|
let stderr = '';
|
|
246
|
-
const child = spawn(cmd, args, {
|
|
296
|
+
const child = spawn(resolveBinPath(cmd), args, {
|
|
247
297
|
cwd: opts.cwd,
|
|
248
298
|
shell: opts.shell ?? process.platform === 'win32',
|
|
249
299
|
windowsHide: opts.windowsHide ?? true,
|
|
@@ -425,6 +475,8 @@ async function detectRuntime(projectDir) {
|
|
|
425
475
|
|
|
426
476
|
async function syncRuntimeState(projectDir) {
|
|
427
477
|
if (!projectDir || !existsSync(join(projectDir, '.openclaw', 'openclaw.json'))) return;
|
|
478
|
+
// Auto-migrate legacy /root/project paths → /home/node/project in openclaw.json
|
|
479
|
+
await migrateContainerPaths(projectDir).catch(() => {});
|
|
428
480
|
await applyResolved9RouterApiKey(projectDir).catch(() => {});
|
|
429
481
|
const rt = await detectRuntime(projectDir).catch(() => null);
|
|
430
482
|
if (!rt) return;
|
|
@@ -444,6 +496,38 @@ async function syncRuntimeState(projectDir) {
|
|
|
444
496
|
}
|
|
445
497
|
}
|
|
446
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Migrate legacy /root/project/ paths to /home/node/project/ in openclaw.json.
|
|
501
|
+
* Old projects may have been created with /root/project/ which doesn't match the
|
|
502
|
+
* Docker volume mount point (/home/node/project/.openclaw).
|
|
503
|
+
* Also clears stale workspace attestation files to prevent WorkspaceVanishedError.
|
|
504
|
+
*/
|
|
505
|
+
async function migrateContainerPaths(projectDir) {
|
|
506
|
+
const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
|
|
507
|
+
if (!existsSync(cfgPath)) return;
|
|
508
|
+
let raw = await fsp.readFile(cfgPath, 'utf8');
|
|
509
|
+
if (!raw.includes('/root/project/')) return;
|
|
510
|
+
// Replace all /root/project/ references with /home/node/project/
|
|
511
|
+
const updated = raw.replace(/\/root\/project\//g, '/home/node/project/');
|
|
512
|
+
if (updated !== raw) {
|
|
513
|
+
await fsp.writeFile(cfgPath, updated, 'utf8');
|
|
514
|
+
sendLog('[migrate] Fixed legacy /root/project/ paths → /home/node/project/ in openclaw.json');
|
|
515
|
+
// Clear stale workspace attestations to avoid WorkspaceVanishedError
|
|
516
|
+
const attestDir = join(projectDir, '.openclaw', 'workspace-attestations');
|
|
517
|
+
if (existsSync(attestDir)) {
|
|
518
|
+
try {
|
|
519
|
+
const files = await fsp.readdir(attestDir);
|
|
520
|
+
for (const f of files) {
|
|
521
|
+
if (f.endsWith('.attested')) {
|
|
522
|
+
await fsp.unlink(join(attestDir, f)).catch(() => {});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
sendLog('[migrate] Cleared stale workspace attestation files');
|
|
526
|
+
} catch {}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
447
531
|
function uniqueSlug(base, used) {
|
|
448
532
|
let out = base;
|
|
449
533
|
let i = 2;
|
|
@@ -525,6 +609,10 @@ function ensureConfigShape(cfg) {
|
|
|
525
609
|
delete agent.desc;
|
|
526
610
|
delete agent.description;
|
|
527
611
|
delete agent.persona;
|
|
612
|
+
// Auto-fix legacy /root/project paths → /home/node/project (Docker container path)
|
|
613
|
+
if (agent.workspace && agent.workspace.includes('/root/project/')) {
|
|
614
|
+
agent.workspace = agent.workspace.replace('/root/project/', '/home/node/project/');
|
|
615
|
+
}
|
|
528
616
|
}
|
|
529
617
|
agent.model = agent.model || { primary: cfg.agents.defaults.model.primary, fallbacks: [] };
|
|
530
618
|
if (!agent.model.primary || agent.model.primary === '9router/smart-route' || agent.model.primary === 'openai/smart-route') agent.model.primary = DEFAULT_MODEL;
|
|
@@ -696,7 +784,7 @@ async function resolveProject9RouterApiKey(projectDir, cfg = null) {
|
|
|
696
784
|
}
|
|
697
785
|
const nativeApiKey = read9RouterApiKeyFromSqlite(join(projectDir || '', '.9router', 'db', 'data.sqlite'));
|
|
698
786
|
if (nativeApiKey) return nativeApiKey;
|
|
699
|
-
const homeApiKey = read9RouterApiKeyFromSqlite(join(
|
|
787
|
+
const homeApiKey = read9RouterApiKeyFromSqlite(join(getRealHomedir(), '.9router', 'db', 'data.sqlite'));
|
|
700
788
|
if (homeApiKey) return homeApiKey;
|
|
701
789
|
return '';
|
|
702
790
|
}
|
|
@@ -954,7 +1042,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
|
|
|
954
1042
|
providerKey: '9router',
|
|
955
1043
|
deployMode: runtime.mode || state.mode || 'docker',
|
|
956
1044
|
osChoice: runtime.os || state.os || detectOs(),
|
|
957
|
-
selectedSkills: [],
|
|
1045
|
+
selectedSkills: ['memory', 'image-gen', 'web-search', 'scheduler'],
|
|
958
1046
|
skills: dataExport.SKILLS || [],
|
|
959
1047
|
agentMetas: [],
|
|
960
1048
|
}));
|
|
@@ -974,7 +1062,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
|
|
|
974
1062
|
cfg.agents.list.push({
|
|
975
1063
|
id: agentId,
|
|
976
1064
|
name: botName,
|
|
977
|
-
workspace: `/
|
|
1065
|
+
workspace: `/home/node/project/.openclaw/${workspaceDir}`,
|
|
978
1066
|
agentDir: `agents/${agentId}/agent`,
|
|
979
1067
|
model: { primary: model === '9router/smart-route' || model === 'openai/smart-route' ? DEFAULT_MODEL : model, fallbacks: [] },
|
|
980
1068
|
});
|
|
@@ -1022,7 +1110,9 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
|
|
|
1022
1110
|
userInfo,
|
|
1023
1111
|
agentWorkspaceDir: workspaceDir,
|
|
1024
1112
|
workspacePath: `.openclaw/${workspaceDir}`,
|
|
1113
|
+
channel,
|
|
1025
1114
|
hasZaloMod: channel === 'zalo-personal',
|
|
1115
|
+
hasZaloSticker: channel === 'zalo-personal',
|
|
1026
1116
|
hasScheduler,
|
|
1027
1117
|
hasImageGen,
|
|
1028
1118
|
});
|
|
@@ -1053,7 +1143,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
|
|
|
1053
1143
|
const userDesc = String(body.userDescription || body.userDesc || '').trim();
|
|
1054
1144
|
const userInfo = [userName ? `- **Tên:** ${userName}` : '', userDesc ? `- **Mô tả:** ${userDesc}` : ''].filter(Boolean).join('\n');
|
|
1055
1145
|
const workspaceDir = workspaceRelForAgent(agent, cfg, projectDir) || `workspace-${agentId}`;
|
|
1056
|
-
agent.workspace = `/
|
|
1146
|
+
agent.workspace = `/home/node/project/.openclaw/${workspaceDir}`;
|
|
1057
1147
|
agent.agentDir = `agents/${agentId}/agent`;
|
|
1058
1148
|
|
|
1059
1149
|
// Find the existing accountId from bindings BEFORE removing them
|
|
@@ -1124,7 +1214,9 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
|
|
|
1124
1214
|
userInfo,
|
|
1125
1215
|
agentWorkspaceDir: workspaceDir,
|
|
1126
1216
|
workspacePath: `.openclaw/${workspaceDir}`,
|
|
1217
|
+
channel,
|
|
1127
1218
|
hasZaloMod: channel === 'zalo-personal',
|
|
1219
|
+
hasZaloSticker: channel === 'zalo-personal',
|
|
1128
1220
|
hasScheduler,
|
|
1129
1221
|
hasImageGen,
|
|
1130
1222
|
});
|
|
@@ -1133,6 +1225,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
|
|
|
1133
1225
|
await fsp.mkdir(dirname(join(wsRoot, name)), { recursive: true });
|
|
1134
1226
|
await fsp.writeFile(join(wsRoot, name), content || '', 'utf8');
|
|
1135
1227
|
}
|
|
1228
|
+
|
|
1136
1229
|
return { ok: true, agentId, channel, workspace: `.openclaw/${workspaceDir}` };
|
|
1137
1230
|
}
|
|
1138
1231
|
|
|
@@ -1196,8 +1289,46 @@ async function waitForGatewayZaloReady(botContainer, projectDir, timeoutMs = 900
|
|
|
1196
1289
|
return ready;
|
|
1197
1290
|
}
|
|
1198
1291
|
|
|
1199
|
-
async function startZaloUserLogin(projectDir, mode = state.mode) {
|
|
1200
|
-
|
|
1292
|
+
async function startZaloUserLogin(projectDir, mode = state.mode, agentId = '') {
|
|
1293
|
+
let profile = 'default';
|
|
1294
|
+
if (agentId) {
|
|
1295
|
+
try {
|
|
1296
|
+
const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
|
|
1297
|
+
if (existsSync(cfgPath)) {
|
|
1298
|
+
const cfg = JSON.parse(await fsp.readFile(cfgPath, 'utf8'));
|
|
1299
|
+
const binding = (cfg.bindings || []).find(b => b.agentId === agentId && b.match?.channel === 'zalouser');
|
|
1300
|
+
if (binding?.match?.accountId) {
|
|
1301
|
+
profile = binding.match.accountId;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
} catch (e) {
|
|
1305
|
+
sendLog(`[zalouser] Warning: Failed to parse openclaw.json: ${e.message}`);
|
|
1306
|
+
}
|
|
1307
|
+
} else if (state.activeBotId) {
|
|
1308
|
+
try {
|
|
1309
|
+
const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
|
|
1310
|
+
if (existsSync(cfgPath)) {
|
|
1311
|
+
const cfg = JSON.parse(await fsp.readFile(cfgPath, 'utf8'));
|
|
1312
|
+
const binding = (cfg.bindings || []).find(b => b.agentId === state.activeBotId && b.match?.channel === 'zalouser');
|
|
1313
|
+
if (binding?.match?.accountId) {
|
|
1314
|
+
profile = binding.match.accountId;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
} catch (e) {}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
const qrPaths = [
|
|
1321
|
+
`/tmp/openclaw/openclaw-zalouser-qr-${profile}.png`,
|
|
1322
|
+
`/tmp/openclaw-1000/openclaw-zalouser-qr-${profile}.png`
|
|
1323
|
+
];
|
|
1324
|
+
if (profile === 'default') {
|
|
1325
|
+
qrPaths.push(
|
|
1326
|
+
'/tmp/openclaw/openclaw-zalouser-qr.png',
|
|
1327
|
+
'/tmp/openclaw-1000/openclaw-zalouser-qr.png',
|
|
1328
|
+
'/tmp/openclaw/openclaw-zalouser-qr-default.png'
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1201
1332
|
if (zaloLoginInFlight) {
|
|
1202
1333
|
setImmediate(async () => {
|
|
1203
1334
|
try {
|
|
@@ -1214,7 +1345,7 @@ async function startZaloUserLogin(projectDir, mode = state.mode) {
|
|
|
1214
1345
|
return { message: 'Zalo login is already running. Keep this modal open...' };
|
|
1215
1346
|
}
|
|
1216
1347
|
zaloLoginInFlight = true;
|
|
1217
|
-
sendLog(
|
|
1348
|
+
sendLog(`[zalouser] Preparing login for profile [${profile}]. QR will be generated for the UI modal.`);
|
|
1218
1349
|
const composeFile = join(projectDir, 'docker', 'openclaw', 'docker-compose.yml');
|
|
1219
1350
|
if ((mode === 'docker' || existsSync(composeFile)) && existsSync(composeFile)) {
|
|
1220
1351
|
const botContainer = getBotContainerName(projectDir);
|
|
@@ -1223,8 +1354,8 @@ async function startZaloUserLogin(projectDir, mode = state.mode) {
|
|
|
1223
1354
|
const checkRegistryScript = `
|
|
1224
1355
|
const fs = require('fs');
|
|
1225
1356
|
try {
|
|
1226
|
-
const dist = '/
|
|
1227
|
-
const inst = '/
|
|
1357
|
+
const dist = '/home/node/project/.openclaw/npm/node_modules/@openclaw/zalouser/dist/index.js';
|
|
1358
|
+
const inst = '/home/node/project/.openclaw/plugins/installs.json';
|
|
1228
1359
|
if (!fs.existsSync(dist)) { console.log('MISSING'); process.exit(0); }
|
|
1229
1360
|
if (!fs.existsSync(inst)) { console.log('MISSING_CHANNELS'); process.exit(0); }
|
|
1230
1361
|
const j = JSON.parse(fs.readFileSync(inst, 'utf8'));
|
|
@@ -1244,17 +1375,17 @@ try {
|
|
|
1244
1375
|
const fixScript = `
|
|
1245
1376
|
const fs=require('fs');
|
|
1246
1377
|
const cp=require('child_process');
|
|
1247
|
-
const cfg='/
|
|
1248
|
-
const bk='/
|
|
1378
|
+
const cfg='/home/node/project/.openclaw/openclaw.json';
|
|
1379
|
+
const bk='/home/node/project/.openclaw/openclaw.json.zalo-backup';
|
|
1249
1380
|
try{if(fs.existsSync(cfg))fs.copyFileSync(cfg,bk);}catch(e){}
|
|
1250
1381
|
// Detect gateway version and pin zalouser plugin to match, preventing createSetupTranslator mismatch
|
|
1251
1382
|
let gatewayVer='';
|
|
1252
1383
|
try{gatewayVer=cp.execSync('openclaw --version 2>/dev/null',{encoding:'utf8'}).trim().replace(/[^0-9.]/g,'');}catch(e){}
|
|
1253
1384
|
const pluginSpec=gatewayVer ? '@openclaw/zalouser@'+gatewayVer : '@openclaw/zalouser';
|
|
1254
1385
|
console.log('Installing plugin via CLI: '+pluginSpec+'...');
|
|
1255
|
-
try{cp.execSync('cd /
|
|
1386
|
+
try{cp.execSync('cd /home/node/project && openclaw plugins install '+pluginSpec+' --force',{stdio:'inherit'});}catch(e){
|
|
1256
1387
|
// Fallback: try without version pin if exact version not found on registry
|
|
1257
|
-
if(gatewayVer){console.log('Pinned version failed, trying latest...');try{cp.execSync('cd /
|
|
1388
|
+
if(gatewayVer){console.log('Pinned version failed, trying latest...');try{cp.execSync('cd /home/node/project && openclaw plugins install @openclaw/zalouser --force',{stdio:'inherit'});}catch(e2){console.error('Install failed');}}
|
|
1258
1389
|
else{console.error('Install failed');}
|
|
1259
1390
|
}
|
|
1260
1391
|
try{
|
|
@@ -1278,10 +1409,10 @@ try{
|
|
|
1278
1409
|
}catch(e){}
|
|
1279
1410
|
try{
|
|
1280
1411
|
console.log('Patching zalouser stability settings...');
|
|
1281
|
-
cp.execSync('ZALO_JS=$(find "/
|
|
1412
|
+
cp.execSync('ZALO_JS=$(find "/home/node/project/.openclaw" -path "*/zalouser/dist/zalo-js*.js" -type f 2>/dev/null | head -1); if [ -n "$ZALO_JS" ]; then sed -i "s/LISTENER_WATCHDOG_MAX_GAP_MS = 35e3/LISTENER_WATCHDOG_MAX_GAP_MS = 120e3/g" "$ZALO_JS"; echo "Patched watchdog gap to 120s"; fi', {shell:true,stdio:'inherit'});
|
|
1282
1413
|
}catch(e){}
|
|
1283
1414
|
try{
|
|
1284
|
-
const ep = '/
|
|
1415
|
+
const ep = '/home/node/project/docker/openclaw/entrypoint.sh';
|
|
1285
1416
|
if (fs.existsSync(ep)) {
|
|
1286
1417
|
let content = fs.readFileSync(ep, 'utf8');
|
|
1287
1418
|
if (!content.includes('zalo-monitor')) {
|
|
@@ -1312,11 +1443,12 @@ try{
|
|
|
1312
1443
|
}
|
|
1313
1444
|
|
|
1314
1445
|
// Clean old credentials & QR files inside container
|
|
1315
|
-
const
|
|
1316
|
-
|
|
1446
|
+
const credFile = profile === 'default' ? 'credentials.json' : `credentials-${profile}.json`;
|
|
1447
|
+
const credPath = `/home/node/project/.openclaw/credentials/zalouser/${credFile}`;
|
|
1448
|
+
await runCapture('docker', ['exec', botContainer, 'sh', '-lc', `rm -f ${credPath} ${qrPaths.join(' ')}`], { cwd: projectDir, shell: false }).catch(() => {});
|
|
1317
1449
|
|
|
1318
1450
|
sendLog('[zalouser] Generating Zalo QR. The image will appear automatically.');
|
|
1319
|
-
const loginCmd =
|
|
1451
|
+
const loginCmd = `cd /home/node/project && openclaw channels login --channel zalouser --account ${profile} --verbose`;
|
|
1320
1452
|
|
|
1321
1453
|
// Retry-based login: the zalouser plugin may need time to connect to Zalo servers.
|
|
1322
1454
|
// The CLI often exits with "Still preparing QR" on the first attempt.
|
|
@@ -1507,6 +1639,33 @@ async function recreateDockerBot(projectDir) {
|
|
|
1507
1639
|
sendLog(`[docker] Recreating ${serviceName} to reload openclaw.json/.env...`);
|
|
1508
1640
|
await run('docker', ['compose', '-f', composeFile, 'up', '-d', '--build', '--force-recreate', serviceName], { cwd: projectDir });
|
|
1509
1641
|
await waitForDockerContainer(containerName);
|
|
1642
|
+
|
|
1643
|
+
// Automatically run Zalo sticker-mention patch if skill is enabled and agent is zalo-personal
|
|
1644
|
+
try {
|
|
1645
|
+
const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
|
|
1646
|
+
if (existsSync(cfgPath)) {
|
|
1647
|
+
const cfg = JSON.parse(await fsp.readFile(cfgPath, 'utf8'));
|
|
1648
|
+
const stickerMentionOn = !!cfg.skills?.entries?.['sticker-mention']?.enabled;
|
|
1649
|
+
if (stickerMentionOn) {
|
|
1650
|
+
for (const a of cfg.agents?.list || []) {
|
|
1651
|
+
const binding = (cfg.bindings || []).find((b) => b.agentId === a.id);
|
|
1652
|
+
const channel = binding?.match?.channel || 'telegram';
|
|
1653
|
+
if (channel === 'zalo-personal') {
|
|
1654
|
+
const workspaceDir = workspaceRelForAgent(a, cfg, projectDir) || `workspace-${a.id}`;
|
|
1655
|
+
const mentionsJsPath = join(projectDir, '.openclaw', workspaceDir, 'skills/sticker-mention/mentions.js');
|
|
1656
|
+
if (existsSync(mentionsJsPath)) {
|
|
1657
|
+
sendLog(`[zalo-patch] Automatically running mentions.js inside container ${containerName}...`);
|
|
1658
|
+
const patchCmd = await runCapture('docker', ['exec', containerName, 'node', `/home/node/project/.openclaw/${workspaceDir}/skills/sticker-mention/mentions.js`], { cwd: projectDir, shell: false });
|
|
1659
|
+
sendLog(`[zalo-patch] Output: ${patchCmd.stdout || ''} ${patchCmd.stderr || ''}`);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
} catch (err) {
|
|
1666
|
+
sendLog(`[zalo-patch] Failed to auto-run mentions.js: ${err.message}`);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1510
1669
|
return true;
|
|
1511
1670
|
}
|
|
1512
1671
|
|
|
@@ -1567,7 +1726,7 @@ async function writeCoreProject({ projectDir, osChoice, mode, gatewayPort = 1878
|
|
|
1567
1726
|
await fsp.mkdir(openclawHome, { recursive: true });
|
|
1568
1727
|
await fsp.mkdir(join(openclawHome, 'plugin-runtime-deps'), { recursive: true });
|
|
1569
1728
|
|
|
1570
|
-
const selectedSkills = ['memory', 'image-gen', 'web-search'];
|
|
1729
|
+
const selectedSkills = ['memory', 'image-gen', 'web-search', 'scheduler'];
|
|
1571
1730
|
const agentMetas = [];
|
|
1572
1731
|
const common = { channelKey: 'telegram', providerKey: '9router', model: DEFAULT_MODEL, deployMode: mode, osChoice, selectedSkills, skills: dataExport.SKILLS || [], agentMetas, gatewayPort, routerPort };
|
|
1573
1732
|
const cfg = buildOpenclawJson(common);
|
|
@@ -1631,7 +1790,7 @@ async function installCore({ osChoice, mode, projectDir, gatewayPort = 18789, ro
|
|
|
1631
1790
|
await fsp.mkdir(dockerDir, { recursive: true });
|
|
1632
1791
|
const envContent = existsSync(rootEnvPath)
|
|
1633
1792
|
? await fsp.readFile(rootEnvPath, 'utf8')
|
|
1634
|
-
: buildEnvFileContent({ channelKey: 'telegram', providerKey: '9router', deployMode: mode, osChoice, selectedSkills: [], skills: dataExport.SKILLS || [], agentMetas: [], apiKey: '', botToken: '' });
|
|
1793
|
+
: buildEnvFileContent({ channelKey: 'telegram', providerKey: '9router', deployMode: mode, osChoice, selectedSkills: ['memory', 'image-gen', 'web-search', 'scheduler'], skills: dataExport.SKILLS || [], agentMetas: [], apiKey: '', botToken: '' });
|
|
1635
1794
|
await fsp.writeFile(dockerEnvPath, envContent, 'utf8');
|
|
1636
1795
|
sendLog(`Docker env ready: ${dockerEnvPath}`);
|
|
1637
1796
|
await run('docker', ['compose', 'up', '-d', '--build'], { cwd: dockerDir });
|
|
@@ -1750,37 +1909,86 @@ async function loadSavedState(rootProjectDir) {
|
|
|
1750
1909
|
}
|
|
1751
1910
|
}
|
|
1752
1911
|
|
|
1912
|
+
function isRestrictedSystemDir(dirPath) {
|
|
1913
|
+
if (!dirPath) return true;
|
|
1914
|
+
const lower = resolve(dirPath).toLowerCase();
|
|
1915
|
+
|
|
1916
|
+
if (SYSTEM_DIR_BLACKLIST.has(basename(lower))) return true;
|
|
1917
|
+
|
|
1918
|
+
const winDir = process.env.SystemRoot ? resolve(process.env.SystemRoot).toLowerCase() : 'c:\\windows';
|
|
1919
|
+
const programFiles = process.env.ProgramFiles ? resolve(process.env.ProgramFiles).toLowerCase() : 'c:\\program files';
|
|
1920
|
+
const programFilesX86 = process.env['ProgramFiles(x86)'] ? resolve(process.env['ProgramFiles(x86)']).toLowerCase() : 'c:\\program files (x86)';
|
|
1921
|
+
|
|
1922
|
+
if (lower.startsWith(winDir) || lower.startsWith(programFiles) || lower.startsWith(programFilesX86)) {
|
|
1923
|
+
return true;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
if (lower.includes(':\\users\\') || lower.endsWith(':\\users')) {
|
|
1927
|
+
const home = resolve(getRealHomedir()).toLowerCase();
|
|
1928
|
+
if (lower !== home && !lower.startsWith(home + '\\') && !lower.startsWith(home + '/')) {
|
|
1929
|
+
return true;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
if (process.platform !== 'win32') {
|
|
1934
|
+
const unixBlacklist = new Set([
|
|
1935
|
+
'usr', 'var', 'proc', 'sys', 'dev', 'etc', 'sbin', 'bin', 'lib', 'lib64', 'run', 'tmp', 'boot', 'lost+found', 'srv', 'mnt', 'media', 'opt'
|
|
1936
|
+
]);
|
|
1937
|
+
if (unixBlacklist.has(basename(lower))) return true;
|
|
1938
|
+
|
|
1939
|
+
if (lower.startsWith('/home') || lower.startsWith('/root')) {
|
|
1940
|
+
const realHome = resolve(getRealHomedir()).toLowerCase();
|
|
1941
|
+
if (lower !== realHome && !lower.startsWith(realHome + '/')) {
|
|
1942
|
+
return true;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
return false;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1753
1950
|
async function findLatestProject(rootProjectDir) {
|
|
1951
|
+
const realHome = getRealHomedir();
|
|
1754
1952
|
const roots = [
|
|
1755
1953
|
process.env.OPENCLAW_PROJECT_DIR,
|
|
1756
1954
|
process.env.OPENCLAW_HOME ? dirname(process.env.OPENCLAW_HOME) : '',
|
|
1757
1955
|
rootProjectDir,
|
|
1758
1956
|
join(rootProjectDir, DEFAULT_PROJECT_NAME),
|
|
1759
1957
|
dirname(rootProjectDir),
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1958
|
+
realHome,
|
|
1959
|
+
join(realHome, 'Documents'),
|
|
1960
|
+
].filter(Boolean);
|
|
1961
|
+
|
|
1763
1962
|
const drives = await getAvailableDrives();
|
|
1764
1963
|
for (const drive of drives) {
|
|
1765
1964
|
const entries = await fsp.readdir(drive, { withFileTypes: true }).catch(() => []);
|
|
1766
1965
|
for (const e of entries) {
|
|
1767
1966
|
if (e.isDirectory() && !e.name.startsWith('$') && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
|
|
1768
|
-
|
|
1967
|
+
const fullPath = join(drive, e.name);
|
|
1968
|
+
if (!isRestrictedSystemDir(fullPath)) {
|
|
1969
|
+
roots.push(fullPath);
|
|
1970
|
+
}
|
|
1769
1971
|
}
|
|
1770
1972
|
}
|
|
1771
1973
|
}
|
|
1772
1974
|
const candidates = [];
|
|
1975
|
+
const seen = new Set();
|
|
1773
1976
|
async function walk(dir, depth = 0) {
|
|
1774
1977
|
if (!dir || depth > 2 || !existsSync(dir)) return;
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1978
|
+
const full = resolve(dir);
|
|
1979
|
+
if (isRestrictedSystemDir(full)) return;
|
|
1980
|
+
if (seen.has(full)) return;
|
|
1981
|
+
seen.add(full);
|
|
1982
|
+
|
|
1983
|
+
if (existsSync(join(full, '.openclaw', 'openclaw.json'))) {
|
|
1984
|
+
const st = await fsp.stat(join(full, '.openclaw', 'openclaw.json')).catch(() => null);
|
|
1985
|
+
if (st) candidates.push({ dir: full, mtimeMs: st.mtimeMs });
|
|
1778
1986
|
return;
|
|
1779
1987
|
}
|
|
1780
|
-
const entries = await fsp.readdir(
|
|
1988
|
+
const entries = await fsp.readdir(full, { withFileTypes: true }).catch(() => []);
|
|
1781
1989
|
for (const e of entries) {
|
|
1782
1990
|
if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules' && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
|
|
1783
|
-
await walk(join(
|
|
1991
|
+
await walk(join(full, e.name), depth + 1);
|
|
1784
1992
|
}
|
|
1785
1993
|
}
|
|
1786
1994
|
}
|
|
@@ -1790,21 +1998,34 @@ async function findLatestProject(rootProjectDir) {
|
|
|
1790
1998
|
}
|
|
1791
1999
|
|
|
1792
2000
|
async function discoverProjects(rootProjectDir) {
|
|
2001
|
+
const realHome = getRealHomedir();
|
|
1793
2002
|
const roots = [
|
|
1794
2003
|
process.env.OPENCLAW_PROJECT_DIR,
|
|
1795
2004
|
rootProjectDir,
|
|
1796
2005
|
dirname(rootProjectDir),
|
|
1797
2006
|
process.env.OPENCLAW_HOME ? dirname(process.env.OPENCLAW_HOME) : '',
|
|
1798
|
-
|
|
1799
|
-
|
|
2007
|
+
realHome,
|
|
2008
|
+
join(realHome, 'Documents'),
|
|
2009
|
+
].filter(Boolean);
|
|
2010
|
+
|
|
1800
2011
|
const drives = await getAvailableDrives();
|
|
1801
|
-
for (const drive of drives)
|
|
2012
|
+
for (const drive of drives) {
|
|
2013
|
+
const entries = await fsp.readdir(drive, { withFileTypes: true }).catch(() => []);
|
|
2014
|
+
for (const e of entries) {
|
|
2015
|
+
if (e.isDirectory() && !e.name.startsWith('$') && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
|
|
2016
|
+
const fullPath = join(drive, e.name);
|
|
2017
|
+
if (!isRestrictedSystemDir(fullPath)) {
|
|
2018
|
+
roots.push(fullPath);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
1802
2023
|
const seen = new Set();
|
|
1803
2024
|
const hits = [];
|
|
1804
2025
|
async function walk(dir, depth = 0) {
|
|
1805
2026
|
if (!dir || depth > 2 || !existsSync(dir)) return;
|
|
1806
2027
|
const full = resolve(dir);
|
|
1807
|
-
if (full
|
|
2028
|
+
if (isRestrictedSystemDir(full)) return;
|
|
1808
2029
|
if (seen.has(full)) return;
|
|
1809
2030
|
seen.add(full);
|
|
1810
2031
|
const cfgPath = join(full, '.openclaw', 'openclaw.json');
|
|
@@ -1832,7 +2053,7 @@ async function discoverProjects(rootProjectDir) {
|
|
|
1832
2053
|
const entries = await fsp.readdir(full, { withFileTypes: true }).catch(() => []);
|
|
1833
2054
|
for (const e of entries) {
|
|
1834
2055
|
if (!e.isDirectory()) continue;
|
|
1835
|
-
if (e.name === 'node_modules' || e.name.startsWith('.
|
|
2056
|
+
if (e.name === 'node_modules' || e.name.startsWith('.') || SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) continue;
|
|
1836
2057
|
await walk(join(full, e.name), depth + 1);
|
|
1837
2058
|
}
|
|
1838
2059
|
}
|
|
@@ -1907,9 +2128,10 @@ async function connectPickedProject(projectName, rootProjectDir) {
|
|
|
1907
2128
|
|
|
1908
2129
|
async function deleteProjectFolder(projectDir, rootProjectDir) {
|
|
1909
2130
|
const resolved = resolve(String(projectDir || ''));
|
|
1910
|
-
const home = resolve(
|
|
2131
|
+
const home = resolve(getRealHomedir());
|
|
2132
|
+
const rootHome = resolve(os.homedir());
|
|
1911
2133
|
if (!existsSync(join(resolved, '.openclaw', 'openclaw.json'))) throw httpError(404, 'openclaw.json not found in selected project');
|
|
1912
|
-
if (resolved === home || /^[A-Za-z]:\\?$/.test(resolved)) throw httpError(403, 'Refusing to delete home/root folder');
|
|
2134
|
+
if (resolved === home || resolved === rootHome || /^[A-Za-z]:\\?$/.test(resolved)) throw httpError(403, 'Refusing to delete home/root folder');
|
|
1913
2135
|
const projects = await discoverProjects(rootProjectDir).catch(() => []);
|
|
1914
2136
|
const meta = projects.find((p) => resolve(p.projectDir) === resolved);
|
|
1915
2137
|
if (!meta || !meta.botCount) throw httpError(403, 'Refusing to delete a folder that is not a detected bot project');
|
|
@@ -2020,6 +2242,12 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
2020
2242
|
}
|
|
2021
2243
|
|
|
2022
2244
|
if (kind === 'skill' && id === 'cron') {
|
|
2245
|
+
cfg.skills = cfg.skills || { entries: {} };
|
|
2246
|
+
cfg.skills.entries = cfg.skills.entries || {};
|
|
2247
|
+
delete cfg.skills.entries['cron'];
|
|
2248
|
+
cfg.skills.entries['cronjob'] = cfg.skills.entries['cronjob'] || {};
|
|
2249
|
+
cfg.skills.entries['cronjob'].enabled = !!enabled;
|
|
2250
|
+
|
|
2023
2251
|
if (enabled) {
|
|
2024
2252
|
cfg.tools = cfg.tools || { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } };
|
|
2025
2253
|
cfg.tools.alsoAllow = Array.from(new Set([...(cfg.tools.alsoAllow || []), 'group:automation']));
|
|
@@ -2103,6 +2331,51 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
2103
2331
|
}
|
|
2104
2332
|
}
|
|
2105
2333
|
|
|
2334
|
+
if (kind === 'skill' && id === 'sticker-mention') {
|
|
2335
|
+
cfg.skills = cfg.skills || { entries: {} };
|
|
2336
|
+
cfg.skills.entries = cfg.skills.entries || {};
|
|
2337
|
+
cfg.skills.entries['sticker-mention'] = cfg.skills.entries['sticker-mention'] || {};
|
|
2338
|
+
cfg.skills.entries['sticker-mention'].enabled = !!enabled;
|
|
2339
|
+
|
|
2340
|
+
for (const a of cfg.agents.list) {
|
|
2341
|
+
const sf = await readWorkspaceText(projectDir, a, 'skills/sticker-mention/SKILL.md');
|
|
2342
|
+
const jsf = await readWorkspaceText(projectDir, a, 'skills/sticker-mention/mentions.js');
|
|
2343
|
+
if (enabled) {
|
|
2344
|
+
await fsp.mkdir(dirname(sf.file), { recursive: true });
|
|
2345
|
+
await fsp.writeFile(sf.file, buildStickerMentionSkillMd(), 'utf8');
|
|
2346
|
+
await fsp.writeFile(jsf.file, buildStickerMentionJs(), 'utf8');
|
|
2347
|
+
} else {
|
|
2348
|
+
const binding = (cfg.bindings || []).find((b) => b.agentId === a.id);
|
|
2349
|
+
const channel = binding?.match?.channel || 'telegram';
|
|
2350
|
+
if (channel === 'zalo-personal' && existsSync(jsf.file)) {
|
|
2351
|
+
try {
|
|
2352
|
+
const hasDocker = existsSync(join(projectDir, 'docker', 'openclaw', 'docker-compose.yml'));
|
|
2353
|
+
if (hasDocker) {
|
|
2354
|
+
const botContainer = getBotContainerName(projectDir);
|
|
2355
|
+
const workspaceDir = workspaceRelForAgent(a, cfg, projectDir) || `workspace-${a.id}`;
|
|
2356
|
+
sendLog('[zalo-patch] Running restore before disabling skill...');
|
|
2357
|
+
await runCapture('docker', ['exec', botContainer, 'node', `/home/node/project/.openclaw/${workspaceDir}/skills/sticker-mention/mentions.js`, '--restore'], { cwd: projectDir, shell: false });
|
|
2358
|
+
}
|
|
2359
|
+
} catch (e) {
|
|
2360
|
+
sendLog(`[zalo-patch] Restore failed: ${e.message}`);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
if (existsSync(sf.file)) await fsp.rm(sf.file, { force: true });
|
|
2364
|
+
if (existsSync(jsf.file)) await fsp.rm(jsf.file, { force: true });
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// Write cfgPath early so recreation reads updated openclaw.json
|
|
2369
|
+
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
2370
|
+
|
|
2371
|
+
// Recreate container to apply updated openclaw.json
|
|
2372
|
+
const hasDocker = existsSync(join(projectDir, 'docker', 'openclaw', 'docker-compose.yml'));
|
|
2373
|
+
if (hasDocker) {
|
|
2374
|
+
sendLog(`[docker] Sticker & Mention skill toggled to ${enabled}. Recreating containers...`);
|
|
2375
|
+
await recreateDockerBot(projectDir).catch((err) => sendLog(`[docker] Warning: Failed to recreate container: ${err.message}`));
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2106
2379
|
if (kind === 'plugin') {
|
|
2107
2380
|
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2108
2381
|
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
@@ -2160,7 +2433,7 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
2160
2433
|
const botContainer = getBotContainerName(projectDir);
|
|
2161
2434
|
sendLog(`[plugin] Installing/updating clawhub:${id} inside container ${botContainer}...`);
|
|
2162
2435
|
|
|
2163
|
-
const cmd = `cd /
|
|
2436
|
+
const cmd = `cd /home/node/project && openclaw plugins install clawhub:${id} --force`;
|
|
2164
2437
|
const cmdOut = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cmd], { cwd: projectDir, shell: false });
|
|
2165
2438
|
|
|
2166
2439
|
if (cmdOut) {
|
|
@@ -2316,7 +2589,7 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
2316
2589
|
const cfg = existsSync(cfgPath) ? ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8').catch(() => '{}'))) : {};
|
|
2317
2590
|
const aid = agentId || cfg.agents?.list?.[0]?.id || 'bot';
|
|
2318
2591
|
const browserOn = !!cfg.browser?.enabled;
|
|
2319
|
-
const cronOn = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
|
|
2592
|
+
const cronOn = !!cfg.skills?.entries?.['cronjob']?.enabled || !!cfg.skills?.entries?.['cron']?.enabled || !!(cfg.tools?.alsoAllow || []).includes('group:automation');
|
|
2320
2593
|
const fresh = cfg;
|
|
2321
2594
|
const freshSaved = {};
|
|
2322
2595
|
const installsPath = join(projectDir || '', '.openclaw', 'plugins', 'installs.json');
|
|
@@ -2345,6 +2618,7 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
2345
2618
|
);
|
|
2346
2619
|
const imageGenOn = !!cfg.skills?.entries?.['image-gen']?.enabled;
|
|
2347
2620
|
const webSearchOn = isEnabled(['duckduckgo']);
|
|
2621
|
+
const stickerMentionOn = !!cfg.skills?.entries?.['sticker-mention']?.enabled;
|
|
2348
2622
|
const aliases = {
|
|
2349
2623
|
browser: ['openclaw-browser-automation', 'browser-automation'],
|
|
2350
2624
|
zalo: ['openclaw-zalo-mod', 'zalo-mod'],
|
|
@@ -2356,6 +2630,7 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
2356
2630
|
'skill:cron': cronOn,
|
|
2357
2631
|
'skill:image-gen': imageGenOn,
|
|
2358
2632
|
'skill:web-search': webSearchOn,
|
|
2633
|
+
'skill:sticker-mention': stickerMentionOn,
|
|
2359
2634
|
'plugin:openclaw-browser-automation': isEnabled(aliases.browser),
|
|
2360
2635
|
'plugin:openclaw-zalo-mod': isEnabled(aliases.zalo),
|
|
2361
2636
|
'plugin:openclaw-facebook-crawler': isEnabled(aliases.crawler),
|
|
@@ -2425,14 +2700,8 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2425
2700
|
};
|
|
2426
2701
|
const projects = await discoverProjects(rootProjectDir).catch(() => []);
|
|
2427
2702
|
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
const resp = await fetch('https://registry.npmjs.org/create-openclaw-bot/latest', { signal: AbortSignal.timeout(3000) });
|
|
2431
|
-
if (resp.ok) {
|
|
2432
|
-
const data = await resp.json();
|
|
2433
|
-
if (data.version) latestSetupVersion = data.version;
|
|
2434
|
-
}
|
|
2435
|
-
} catch (e) {}
|
|
2703
|
+
fetchLatestSetupVersionBg().catch(() => {});
|
|
2704
|
+
const latestSetupVersion = latestSetupVersionCache;
|
|
2436
2705
|
|
|
2437
2706
|
return json(res, {
|
|
2438
2707
|
os: osChoice,
|
|
@@ -2581,7 +2850,7 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2581
2850
|
const token = String(body.token || '').trim();
|
|
2582
2851
|
sendLog(`[telegram] Registering Telegram channel via CLI inside ${botContainer}...`);
|
|
2583
2852
|
try {
|
|
2584
|
-
const regResult = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', `cd /
|
|
2853
|
+
const regResult = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', `cd /home/node/project && openclaw channels add telegram --token "${token}"`], { cwd: projectDir, shell: false });
|
|
2585
2854
|
sendLog(`[telegram] CLI registration output:\n${regResult.stdout}\n${regResult.stderr}`);
|
|
2586
2855
|
sendLog(`[telegram] Restarting ${botContainer} container to load the registered channel...`);
|
|
2587
2856
|
await restartDockerBotContainer(projectDir).catch((err) => sendLog(`[telegram] Container restart failed: ${err.message}`));
|
|
@@ -2598,7 +2867,7 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2598
2867
|
// Delay login start to let the recreated container fully boot gateway + plugins
|
|
2599
2868
|
setTimeout(async () => {
|
|
2600
2869
|
try {
|
|
2601
|
-
const login = await startZaloUserLogin(projectDir, state.mode);
|
|
2870
|
+
const login = await startZaloUserLogin(projectDir, state.mode, result.agentId);
|
|
2602
2871
|
if (login?.qrDataUrl) sendLog(`[zalouser:qr] ${login.qrDataUrl}`);
|
|
2603
2872
|
if (login?.message) sendLog(`[zalouser] ${login.message}`);
|
|
2604
2873
|
} catch (err) {
|
|
@@ -2618,10 +2887,12 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2618
2887
|
return json(res, result);
|
|
2619
2888
|
}
|
|
2620
2889
|
if (url.pathname === '/api/zalo/login' && req.method === 'POST') {
|
|
2621
|
-
const
|
|
2890
|
+
const body = await readJson(req).catch(() => ({}));
|
|
2891
|
+
const agentId = body.agentId || '';
|
|
2892
|
+
const projectDir = await resolveProjectDir(rootProjectDir, body);
|
|
2622
2893
|
setImmediate(async () => {
|
|
2623
2894
|
try {
|
|
2624
|
-
const login = await startZaloUserLogin(projectDir, state.mode);
|
|
2895
|
+
const login = await startZaloUserLogin(projectDir, state.mode, agentId);
|
|
2625
2896
|
if (login?.qrDataUrl) sendLog(`[zalouser:qr] ${login.qrDataUrl}`);
|
|
2626
2897
|
if (login?.message) sendLog(`[zalouser] ${login.message}`);
|
|
2627
2898
|
} catch (err) {
|
|
@@ -2704,6 +2975,9 @@ function openUrl(url) {
|
|
|
2704
2975
|
const cmd = process.platform === 'win32' ? 'cmd' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
2705
2976
|
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
2706
2977
|
const child = spawn(cmd, args, { detached: true, stdio: 'ignore', shell: false, windowsHide: true });
|
|
2978
|
+
child.on('error', (err) => {
|
|
2979
|
+
sendLog(`[openUrl] Warning: Could not open browser automatically (${err.message}). Please navigate to ${url} manually.`);
|
|
2980
|
+
});
|
|
2707
2981
|
child.unref();
|
|
2708
2982
|
}
|
|
2709
2983
|
|