create-openclaw-bot 5.8.0 → 5.8.2
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 +167 -159
- package/README.vi.md +172 -164
- package/dist/cli.js +125 -112
- package/dist/legacy-cli.js +11 -11
- package/dist/server/local-server.js +372 -70
- package/dist/setup/data/index.js +0 -1
- package/dist/setup/data/plugins.js +8 -1
- package/dist/setup/data/skills.js +2 -10
- package/dist/setup/shared/docker-gen.js +576 -576
- package/dist/setup/shared/workspace-gen.js +813 -526
- package/dist/setup.js +367 -324
- package/dist/web/app.js +1276 -1106
- package/dist/web/styles.css +1054 -286
- package/package.json +5 -5
|
@@ -21,6 +21,7 @@ const dataExport = loadSharedModule('../setup/data/index.js', '__openclawData');
|
|
|
21
21
|
|
|
22
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const WEB_DIR = resolve(__dirname, '../web');
|
|
24
|
+
const SETUP_VERSION = (() => { try { return JSON.parse(fs.readFileSync(resolve(__dirname, '../../package.json'), 'utf8')).version || '0.0.0'; } catch { return '0.0.0'; } })();
|
|
24
25
|
const DEFAULT_PROJECT_NAME = 'openclaw-bot';
|
|
25
26
|
const STATE_FILE = '.openclaw-setup-state.json';
|
|
26
27
|
const DEFAULT_MODEL = 'smart-route';
|
|
@@ -410,6 +411,12 @@ async function syncRuntimeState(projectDir) {
|
|
|
410
411
|
state.mode = state.mode || rt.mode;
|
|
411
412
|
state.syncSource = rt.syncSource || 'config';
|
|
412
413
|
state.installed = true;
|
|
414
|
+
// Auto-sync Docker files if outdated
|
|
415
|
+
if (rt.mode === 'docker') {
|
|
416
|
+
await syncDockerInfra(projectDir).catch((err) =>
|
|
417
|
+
sendLog(`[sync] Docker infra sync skipped: ${err.message}`)
|
|
418
|
+
);
|
|
419
|
+
}
|
|
413
420
|
}
|
|
414
421
|
|
|
415
422
|
function uniqueSlug(base, used) {
|
|
@@ -1321,6 +1328,74 @@ function getBotContainerName(projectDir) {
|
|
|
1321
1328
|
return 'openclaw-bot';
|
|
1322
1329
|
}
|
|
1323
1330
|
|
|
1331
|
+
async function syncDockerInfra(projectDir, force = false) {
|
|
1332
|
+
const dockerDir = join(projectDir, 'docker', 'openclaw');
|
|
1333
|
+
if (!existsSync(join(dockerDir, 'docker-compose.yml'))) return false;
|
|
1334
|
+
|
|
1335
|
+
// Check existing entrypoint version stamp
|
|
1336
|
+
const entrypointPath = join(dockerDir, 'entrypoint.sh');
|
|
1337
|
+
const existingEntrypoint = existsSync(entrypointPath)
|
|
1338
|
+
? await fsp.readFile(entrypointPath, 'utf8').catch(() => '') : '';
|
|
1339
|
+
const existingVersion = (existingEntrypoint.match(/# openclaw-setup v([\d.]+)/) || [])[1] || '0.0.0';
|
|
1340
|
+
|
|
1341
|
+
// Only regenerate if version differs OR force is true
|
|
1342
|
+
if (existingVersion === SETUP_VERSION && !force) return false;
|
|
1343
|
+
|
|
1344
|
+
// Read existing compose to preserve customizations
|
|
1345
|
+
const compose = await readComposeText(projectDir);
|
|
1346
|
+
const botContainer = parseComposeServiceContainerName(compose, 'ai-bot') || `openclaw-${slugify(basename(projectDir))}`;
|
|
1347
|
+
const routerContainer = parseComposeServiceContainerName(compose, '9router') || `9router-${slugify(basename(projectDir))}`;
|
|
1348
|
+
const composeName = (compose.match(/^name:\s*(\S+)/m) || [])[1] || `oc-${slugify(basename(projectDir))}`;
|
|
1349
|
+
const gatewayPort = state.gatewayPort || 18789;
|
|
1350
|
+
const routerPort = state.routerPort || 20128;
|
|
1351
|
+
|
|
1352
|
+
// Detect features from openclaw.json
|
|
1353
|
+
const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
|
|
1354
|
+
let hasZalo = false;
|
|
1355
|
+
try {
|
|
1356
|
+
const cfg = JSON.parse(await fsp.readFile(cfgPath, 'utf8'));
|
|
1357
|
+
hasZalo = !!cfg.channels?.zalouser?.enabled;
|
|
1358
|
+
} catch {}
|
|
1359
|
+
|
|
1360
|
+
// Regenerate with detected settings
|
|
1361
|
+
const docker = buildDockerArtifacts({
|
|
1362
|
+
is9Router: true,
|
|
1363
|
+
openClawNpmSpec: OPENCLAW_NPM_SPEC,
|
|
1364
|
+
openClawRuntimePackages: '',
|
|
1365
|
+
allSkills: [],
|
|
1366
|
+
dockerfilePlugins: [],
|
|
1367
|
+
gatewayPort,
|
|
1368
|
+
routerPort,
|
|
1369
|
+
singleComposeName: composeName,
|
|
1370
|
+
singleAppContainerName: botContainer,
|
|
1371
|
+
singleRouterContainerName: routerContainer,
|
|
1372
|
+
runtimeCommandParts: [
|
|
1373
|
+
hasZalo ? 'ensure_zalouser' : '',
|
|
1374
|
+
'while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done >/dev/null 2>&1 &',
|
|
1375
|
+
].filter(Boolean),
|
|
1376
|
+
plainSingleExtraHosts: true,
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
// Inject version stamp into entrypoint
|
|
1380
|
+
let entryScript = docker.entrypointScript || '';
|
|
1381
|
+
entryScript = entryScript.replace('#!/bin/sh', `#!/bin/sh\n# openclaw-setup v${SETUP_VERSION}`);
|
|
1382
|
+
|
|
1383
|
+
// Write updated files preserving env_file path convention
|
|
1384
|
+
const newCompose = String(docker.compose || '')
|
|
1385
|
+
.replace(/env_file:\s*\n\s*-\s*\.env/g, 'env_file:\n - ../../.env')
|
|
1386
|
+
.replace(/env_file:\s*\.env/g, 'env_file: ../../.env');
|
|
1387
|
+
|
|
1388
|
+
sendLog(`[sync] Updating Docker infrastructure files (v${existingVersion} \u2192 v${SETUP_VERSION})`);
|
|
1389
|
+
await fsp.writeFile(join(dockerDir, 'Dockerfile'), docker.dockerfile, 'utf8');
|
|
1390
|
+
await fsp.writeFile(join(dockerDir, 'docker-compose.yml'), newCompose, 'utf8');
|
|
1391
|
+
await fsp.writeFile(entrypointPath, entryScript, 'utf8');
|
|
1392
|
+
if (docker.syncScript) await fsp.writeFile(join(dockerDir, 'sync.js'), docker.syncScript, 'utf8');
|
|
1393
|
+
if (docker.patchScript) await fsp.writeFile(join(dockerDir, 'patch-9router.js'), docker.patchScript, 'utf8');
|
|
1394
|
+
|
|
1395
|
+
sendLog(`[sync] Docker files updated to v${SETUP_VERSION}. Next rebuild will use new infrastructure.`);
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1324
1399
|
async function recreateDockerBot(projectDir) {
|
|
1325
1400
|
const composeFile = join(projectDir, 'docker', 'openclaw', 'docker-compose.yml');
|
|
1326
1401
|
if (!existsSync(composeFile)) return false;
|
|
@@ -1329,7 +1404,7 @@ async function recreateDockerBot(projectDir) {
|
|
|
1329
1404
|
const serviceName = getBotServiceName(projectDir);
|
|
1330
1405
|
const containerName = getBotContainerName(projectDir);
|
|
1331
1406
|
sendLog(`[docker] Recreating ${serviceName} to reload openclaw.json/.env...`);
|
|
1332
|
-
await run('docker', ['compose', '-f', composeFile, 'up', '-d', '--force-recreate', serviceName], { cwd: projectDir });
|
|
1407
|
+
await run('docker', ['compose', '-f', composeFile, 'up', '-d', '--build', '--force-recreate', serviceName], { cwd: projectDir });
|
|
1333
1408
|
await waitForDockerContainer(containerName);
|
|
1334
1409
|
return true;
|
|
1335
1410
|
}
|
|
@@ -1343,6 +1418,8 @@ async function updateRuntime(target, projectDir) {
|
|
|
1343
1418
|
await run('docker', ['compose', 'pull', '9router'], { cwd: dockerDir }).catch(() => {});
|
|
1344
1419
|
await run('docker', ['compose', 'up', '-d', '--force-recreate', '9router'], { cwd: dockerDir });
|
|
1345
1420
|
} else {
|
|
1421
|
+
// Ensure Docker files are current before rebuilding
|
|
1422
|
+
await syncDockerInfra(projectDir).catch(() => {});
|
|
1346
1423
|
const serviceName = getBotServiceName(projectDir);
|
|
1347
1424
|
const containerName = getBotContainerName(projectDir);
|
|
1348
1425
|
await run('docker', ['compose', 'build', '--no-cache', serviceName], { cwd: dockerDir });
|
|
@@ -1443,7 +1520,8 @@ async function writeCoreProject({ projectDir, osChoice, mode, gatewayPort = 1878
|
|
|
1443
1520
|
sendLog(`[writeCoreProject] Writing docker files to ${dockerDir} (compose ${compose.length} bytes, routerPort=${routerPort})`);
|
|
1444
1521
|
await fsp.writeFile(join(dockerDir, 'Dockerfile'), docker.dockerfile, 'utf8');
|
|
1445
1522
|
await fsp.writeFile(join(dockerDir, 'docker-compose.yml'), compose, 'utf8');
|
|
1446
|
-
|
|
1523
|
+
const entryScript = (docker.entrypointScript || docker.entrypoint || '').replace('#!/bin/sh', `#!/bin/sh\n# openclaw-setup v${SETUP_VERSION}`);
|
|
1524
|
+
await fsp.writeFile(join(dockerDir, 'entrypoint.sh'), entryScript, 'utf8');
|
|
1447
1525
|
// Write 9router helper scripts as separate files (mounted as volumes)
|
|
1448
1526
|
if (docker.syncScript) await fsp.writeFile(join(dockerDir, 'sync.js'), docker.syncScript, 'utf8');
|
|
1449
1527
|
if (docker.patchScript) await fsp.writeFile(join(dockerDir, 'patch-9router.js'), docker.patchScript, 'utf8');
|
|
@@ -1825,43 +1903,125 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
1825
1903
|
defaultProfile: 'host-chrome',
|
|
1826
1904
|
profiles: { 'host-chrome': { cdpUrl: 'http://127.0.0.1:9222', color: '#4285F4' } },
|
|
1827
1905
|
};
|
|
1828
|
-
const
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
const
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1906
|
+
const isHeadlessServer = process.platform === 'linux';
|
|
1907
|
+
const docVariant = 'cli-server';
|
|
1908
|
+
|
|
1909
|
+
for (const a of cfg.agents.list) {
|
|
1910
|
+
const wm = buildWorkspaceFileMap({
|
|
1911
|
+
isVi: true,
|
|
1912
|
+
botName: a.name || a.id,
|
|
1913
|
+
botDesc: '',
|
|
1914
|
+
hasBrowser: false,
|
|
1915
|
+
hasScheduler: true,
|
|
1916
|
+
workspacePath: `.openclaw/${workspaceRelForAgent(a, cfg, projectDir)}/`,
|
|
1917
|
+
agentWorkspaceDir: workspaceRelForAgent(a, cfg, projectDir),
|
|
1918
|
+
variant: cfg.agents.list.length > 1 ? 'relay' : 'single',
|
|
1919
|
+
browserDocVariant: docVariant,
|
|
1920
|
+
});
|
|
1921
|
+
const browserDoc = wm['BROWSER.md'] || '# BROWSER';
|
|
1922
|
+
const browserTool = wm['browser-tool.js'] || '';
|
|
1923
|
+
const bf = await readWorkspaceText(projectDir, a, 'BROWSER.md');
|
|
1924
|
+
await fsp.writeFile(bf.file, browserDoc, 'utf8');
|
|
1925
|
+
const bt = await readWorkspaceText(projectDir, a, 'browser-tool.js');
|
|
1926
|
+
if (browserTool) await fsp.writeFile(bt.file, browserTool, 'utf8');
|
|
1927
|
+
|
|
1928
|
+
const af = await readWorkspaceText(projectDir, a, 'AGENTS.md');
|
|
1929
|
+
const agentsManaged = upsertManagedBlock(af.content, 'BROWSER_LINK', '- Browser docs: `BROWSER.md`');
|
|
1930
|
+
await fsp.writeFile(af.file, agentsManaged, 'utf8');
|
|
1931
|
+
|
|
1932
|
+
// Add to TOOLS.md
|
|
1933
|
+
const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
|
|
1934
|
+
const browserGuide = isHeadlessServer
|
|
1935
|
+
? `## 🌐 Browser Automation
|
|
1936
|
+
- Xem hướng dẫn chi tiết tại **BROWSER.md**
|
|
1937
|
+
- Script điều khiển: \`browser-tool.js\`
|
|
1938
|
+
- Chế độ hiện tại: Chạy ngầm độc lập qua Docker hoặc Xvfb trên VPS.
|
|
1939
|
+
- **Tìm kiếm Web:** Nếu không có công cụ Web Search (hoặc Web Search không khả dụng/bị lỗi), hãy **luôn sử dụng ngay công cụ terminal (exec/run_command) để chạy lệnh: \`node search-tool.js "<từ khóa>" 5\`**! Lệnh này sẽ tự động chạy ngầm qua DuckDuckGo/Google/Bing bằng trình duyệt ngầm tàng hình của bạn và trả về kết quả JSON sạch ngay lập tức. Tuyệt đối KHÔNG được mở trình duyệt thủ công, chụp ảnh màn hình hay click tìm kiếm bằng tay từng bước!
|
|
1940
|
+
- Nếu browser lỗi, thử lại 1 lần rồi mới báo user với lỗi cụ thể`
|
|
1941
|
+
: `## 🌐 Browser Automation
|
|
1942
|
+
- Xem hướng dẫn chi tiết tại **BROWSER.md**
|
|
1943
|
+
- Script điều khiển: \`browser-tool.js\`
|
|
1944
|
+
- Chế độ hiện tại:
|
|
1945
|
+
- **Mặc định:** Chạy ngầm độc lập qua Docker hoặc Server.
|
|
1946
|
+
- **Chế độ xem Chrome thật:** Chạy file \`start-chrome-debug.bat\` / \`start-chrome-debug.sh\` trên host trước để bot kết nối điều khiển trực quan.
|
|
1947
|
+
- Kết nối mặc định: \`http://127.0.0.1:9222\`
|
|
1948
|
+
- **Tìm kiếm Web:** Nếu không có công cụ Web Search (hoặc Web Search không khả dụng/bị lỗi), hãy **luôn sử dụng ngay công cụ terminal (exec/run_command) để chạy lệnh: \`node search-tool.js "<từ khóa>" 5\`**! Lệnh này sẽ tự động chạy ngầm qua DuckDuckGo/Google/Bing bằng trình duyệt ngầm tàng hình của bạn và trả về kết quả JSON sạch ngay lập tức. Tuyệt đối KHÔNG được mở trình duyệt thủ công, chụp ảnh màn hình hay click tìm kiếm bằng tay từng bước!
|
|
1949
|
+
- Nếu browser lỗi, thử lại 1 lần rồi mới báo user với lỗi cụ thể`;
|
|
1950
|
+
await fsp.writeFile(tf.file, upsertManagedBlock(tf.content, 'BROWSER_GUIDE', browserGuide), 'utf8');
|
|
1951
|
+
}
|
|
1838
1952
|
} else {
|
|
1839
1953
|
delete cfg.browser;
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1954
|
+
for (const a of cfg.agents.list) {
|
|
1955
|
+
const bf = await readWorkspaceText(projectDir, a, 'BROWSER.md');
|
|
1956
|
+
if (existsSync(bf.file)) await fsp.rm(bf.file, { force: true });
|
|
1957
|
+
const bt = await readWorkspaceText(projectDir, a, 'browser-tool.js');
|
|
1958
|
+
if (existsSync(bt.file)) await fsp.rm(bt.file, { force: true });
|
|
1959
|
+
|
|
1960
|
+
const af = await readWorkspaceText(projectDir, a, 'AGENTS.md');
|
|
1961
|
+
await fsp.writeFile(af.file, removeManagedBlock(af.content, 'BROWSER_LINK'), 'utf8');
|
|
1962
|
+
|
|
1963
|
+
// Remove from TOOLS.md
|
|
1964
|
+
const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
|
|
1965
|
+
await fsp.writeFile(tf.file, removeManagedBlock(tf.content, 'BROWSER_GUIDE'), 'utf8');
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
// Write cfgPath early so syncDockerInfra reads the updated openclaw.json
|
|
1970
|
+
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
1971
|
+
|
|
1972
|
+
// Force Docker Infrastructure sync and container recreation
|
|
1973
|
+
const hasDocker = existsSync(join(projectDir, 'docker', 'openclaw', 'docker-compose.yml'));
|
|
1974
|
+
if (hasDocker) {
|
|
1975
|
+
sendLog(`[docker] Browser skill toggled to ${enabled}. Regenerating Dockerfiles...`);
|
|
1976
|
+
await syncDockerInfra(projectDir, true).catch((err) => sendLog(`[docker] Warning: Failed to sync docker infra: ${err.message}`));
|
|
1977
|
+
sendLog(`[docker] Rebuilding and recreating containers...`);
|
|
1978
|
+
await recreateDockerBot(projectDir).catch((err) => sendLog(`[docker] Warning: Failed to recreate container: ${err.message}`));
|
|
1846
1979
|
}
|
|
1847
1980
|
}
|
|
1848
1981
|
|
|
1849
1982
|
if (kind === 'skill' && id === 'cron') {
|
|
1850
|
-
const tf = await readWorkspaceText(projectDir, agent, 'TOOLS.md');
|
|
1851
1983
|
if (enabled) {
|
|
1852
1984
|
cfg.tools = cfg.tools || { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } };
|
|
1853
1985
|
cfg.tools.alsoAllow = Array.from(new Set([...(cfg.tools.alsoAllow || []), 'group:automation']));
|
|
1854
1986
|
cfg.commands = cfg.commands || {};
|
|
1855
1987
|
cfg.commands.ownerAllowFrom = Array.from(new Set([...(cfg.commands.ownerAllowFrom || []), '*']));
|
|
1856
|
-
const cronGuide = `## Cron (
|
|
1857
|
-
-
|
|
1858
|
-
-
|
|
1859
|
-
-
|
|
1860
|
-
|
|
1988
|
+
const cronGuide = `## ⏰ Cron / Lên lịch nhắc nhở (tool: \`cron\`)
|
|
1989
|
+
- **Tên tool chính xác:** Tên công cụ là \`cron\` (tuyệt đối không nhầm là \`native\` hay command line bên ngoài).
|
|
1990
|
+
- **Khi tạo cronjob mới (action \`add\`):**
|
|
1991
|
+
- **TUYỆT ĐỐI KHÔNG điền trường \`agentId\`** trong object \`job\` (hãy bỏ qua/omitted trường này). Hệ thống OpenClaw sẽ tự động gán chính xác ID của bạn vào job đó.
|
|
1992
|
+
- Tuyệt đối **không tự điền** \`agentId\` là \`"bot"\` hay \`"main"\`, vì làm vậy sẽ khiến cronjob thuộc về agent khác và bạn sẽ mất quyền kiểm soát/xóa nó sau này.
|
|
1993
|
+
- **Khi user yêu cầu tắt/bật/xóa cronjob:**
|
|
1994
|
+
1. **Bước 1 (Tìm kiếm):** Gọi tool \`cron\` với action \`list\` (và \`includeDisabled: true\`) để xem danh sách tất cả cronjob đang chạy trên hệ thống và tìm đúng \`jobId\` phù hợp với yêu cầu.
|
|
1995
|
+
2. **Bước 2 (Xử lý):**
|
|
1996
|
+
- Để xóa: Gọi action \`remove\` với \`id\` tìm được.
|
|
1997
|
+
- Để tắt/tạm dừng: Gọi action \`update\` với \`id\` và patch \`{"enabled": false}\`.
|
|
1998
|
+
- Để bật lại: Gọi action \`update\` với \`id\` và patch \`{"enabled": true}\`.
|
|
1999
|
+
3. **Tuyên bố trung thực:** Tuyệt đối không bao giờ trả lời "đã xóa" hay "không có" dựa trên suy đoán của bản thân mà chưa gọi tool \`cron\` để kiểm tra thực tế.
|
|
2000
|
+
- 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 \`cron\` (action \`add\`) để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.
|
|
2001
|
+
- Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
|
|
2002
|
+
- **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố \`group:\` trước ID của group (ví dụ: \`group:3815464776067464419\` hoặc \`group:xxxx\`). Tuyệt đối không được chỉ điền ID thuần túy vì hệ thống sẽ hiểu nhầm đó là một DM chat cá nhân (direct message) và gửi sai địa chỉ.
|
|
2003
|
+
- Bỏ qua việc tra cứu docs nội bộ như \`cron-jobs.mdx\`; tin tưởng khả năng dùng tool hiện có để hoàn thành yêu cầu.`;
|
|
2004
|
+
for (const a of cfg.agents.list) {
|
|
2005
|
+
const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
|
|
2006
|
+
await fsp.writeFile(tf.file, upsertManagedBlock(tf.content, 'CRON_GUIDE', cronGuide), 'utf8');
|
|
2007
|
+
}
|
|
1861
2008
|
} else {
|
|
1862
2009
|
if (cfg.tools?.alsoAllow) cfg.tools.alsoAllow = cfg.tools.alsoAllow.filter((x) => x !== 'group:automation');
|
|
1863
2010
|
if (cfg.commands?.ownerAllowFrom) cfg.commands.ownerAllowFrom = cfg.commands.ownerAllowFrom.filter((x) => x !== '*');
|
|
1864
|
-
|
|
2011
|
+
for (const a of cfg.agents.list) {
|
|
2012
|
+
const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
|
|
2013
|
+
await fsp.writeFile(tf.file, removeManagedBlock(tf.content, 'CRON_GUIDE'), 'utf8');
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// Write cfgPath early so recreation reads updated openclaw.json
|
|
2018
|
+
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
2019
|
+
|
|
2020
|
+
// Recreate container to apply updated openclaw.json tools/commands rules
|
|
2021
|
+
const hasDocker = existsSync(join(projectDir, 'docker', 'openclaw', 'docker-compose.yml'));
|
|
2022
|
+
if (hasDocker) {
|
|
2023
|
+
sendLog(`[docker] Cron skill toggled to ${enabled}. Recreating containers...`);
|
|
2024
|
+
await recreateDockerBot(projectDir).catch((err) => sendLog(`[docker] Warning: Failed to recreate container: ${err.message}`));
|
|
1865
2025
|
}
|
|
1866
2026
|
}
|
|
1867
2027
|
|
|
@@ -1869,9 +2029,10 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
1869
2029
|
cfg.plugins = cfg.plugins || { entries: {} };
|
|
1870
2030
|
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
1871
2031
|
const pluginAliasMap = {
|
|
2032
|
+
'openclaw-browser-automation': ['browser-automation', 'openclaw-browser-automation'],
|
|
1872
2033
|
'openclaw-zalo-mod': ['zalo-mod', 'openclaw-zalo-mod'],
|
|
1873
|
-
'openclaw-
|
|
1874
|
-
'openclaw-facebook-poster': ['openclaw-facebook-poster', 'openclaw-
|
|
2034
|
+
'openclaw-facebook-crawler': ['openclaw-facebook-crawler', 'openclaw-n8n-facebook-crawler', 'n8n-facebook-crawler'],
|
|
2035
|
+
'openclaw-n8n-facebook-poster': ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
|
|
1875
2036
|
};
|
|
1876
2037
|
const aliases = pluginAliasMap[id] || [id];
|
|
1877
2038
|
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
@@ -1881,12 +2042,9 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
1881
2042
|
cfg.plugins.entries[existingKey].hooks = cfg.plugins.entries[existingKey].hooks || {};
|
|
1882
2043
|
cfg.plugins.entries[existingKey].hooks.allowConversationAccess = true;
|
|
1883
2044
|
}
|
|
1884
|
-
//
|
|
2045
|
+
// Only add the canonical config key to allow list (not all aliases)
|
|
1885
2046
|
cfg.plugins.allow = cfg.plugins.allow || [];
|
|
1886
|
-
|
|
1887
|
-
for (const aid of allowIds) {
|
|
1888
|
-
if (!cfg.plugins.allow.includes(aid)) cfg.plugins.allow.push(aid);
|
|
1889
|
-
}
|
|
2047
|
+
if (!cfg.plugins.allow.includes(existingKey)) cfg.plugins.allow.push(existingKey);
|
|
1890
2048
|
}
|
|
1891
2049
|
|
|
1892
2050
|
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
@@ -1904,33 +2062,76 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
1904
2062
|
|
|
1905
2063
|
if (composeDir) {
|
|
1906
2064
|
const botContainer = getBotContainerName(projectDir);
|
|
2065
|
+
|
|
2066
|
+
// 1. Temporarily disable the plugin in openclaw.json and restart container to unlock files
|
|
2067
|
+
const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
|
|
2068
|
+
const pluginAliasMap = {
|
|
2069
|
+
'openclaw-browser-automation': ['browser-automation', 'openclaw-browser-automation'],
|
|
2070
|
+
'openclaw-zalo-mod': ['zalo-mod', 'openclaw-zalo-mod'],
|
|
2071
|
+
'openclaw-facebook-crawler': ['openclaw-facebook-crawler', 'openclaw-n8n-facebook-crawler', 'n8n-facebook-crawler'],
|
|
2072
|
+
'openclaw-n8n-facebook-poster': ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
|
|
2073
|
+
};
|
|
2074
|
+
const aliases = pluginAliasMap[id] || [id];
|
|
2075
|
+
|
|
2076
|
+
if (existsSync(cfgPath)) {
|
|
2077
|
+
try {
|
|
2078
|
+
const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
|
|
2079
|
+
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2080
|
+
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
2081
|
+
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
2082
|
+
|
|
2083
|
+
if (cfg.plugins.entries[existingKey]?.enabled) {
|
|
2084
|
+
sendLog(`[plugin] Temporarily disabling ${existingKey} and restarting bot to release file locks...`);
|
|
2085
|
+
cfg.plugins.entries[existingKey].enabled = false;
|
|
2086
|
+
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
2087
|
+
await run('docker', ['restart', botContainer], { shell: false }).catch(() => {});
|
|
2088
|
+
// Sleep 2 seconds to let container fully boot and release locks
|
|
2089
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2090
|
+
}
|
|
2091
|
+
} catch (_) {}
|
|
2092
|
+
}
|
|
2093
|
+
|
|
1907
2094
|
sendLog(`[plugin] Installing clawhub:${id} inside container ${botContainer}...`);
|
|
1908
2095
|
|
|
1909
2096
|
let installSuccess = true;
|
|
1910
|
-
const
|
|
1911
|
-
|
|
2097
|
+
const cleanCmd = `cd /root/project && (openclaw plugins uninstall ${id} --force 2>/dev/null || true) && (openclaw plugins uninstall ${id.replace('openclaw-', '')} --force 2>/dev/null || true) && rm -rf .openclaw/extensions/${id} .openclaw/extensions/${id.replace('openclaw-', '')} && openclaw plugins install clawhub:${id}`;
|
|
2098
|
+
const cmdOut = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cleanCmd], { cwd: projectDir, shell: false });
|
|
2099
|
+
|
|
2100
|
+
if (cmdOut) {
|
|
2101
|
+
for (const line of `${cmdOut.stdout}\n${cmdOut.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(line);
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
if (cmdOut.code !== 0) {
|
|
1912
2105
|
const folderExists = aliases.some((a) => existsSync(join(projectDir, '.openclaw', 'extensions', a)));
|
|
1913
2106
|
if (folderExists) {
|
|
1914
2107
|
sendLog(`[plugin] Warning: installation reported errors, but plugin folder successfully written. Proceeding.`);
|
|
1915
|
-
return { stdout: '', stderr: err.message };
|
|
1916
2108
|
} else {
|
|
1917
2109
|
installSuccess = false;
|
|
1918
|
-
|
|
2110
|
+
// Re-enable in config on failure to restore state
|
|
2111
|
+
if (existsSync(cfgPath)) {
|
|
2112
|
+
try {
|
|
2113
|
+
const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
|
|
2114
|
+
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2115
|
+
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
2116
|
+
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
2117
|
+
cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
|
|
2118
|
+
cfg.plugins.entries[existingKey].enabled = true;
|
|
2119
|
+
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
2120
|
+
} catch (_) {}
|
|
2121
|
+
}
|
|
2122
|
+
throw new Error(cmdOut.stderr || cmdOut.stdout || `Failed to install plugin ${id} inside container.`);
|
|
1919
2123
|
}
|
|
1920
|
-
});
|
|
1921
|
-
if (cmdOut) {
|
|
1922
|
-
for (const line of `${cmdOut.stdout}\n${cmdOut.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(line);
|
|
1923
2124
|
}
|
|
1924
2125
|
|
|
1925
|
-
const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
|
|
1926
2126
|
if (existsSync(cfgPath)) {
|
|
1927
2127
|
const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
|
|
1928
2128
|
cfg.plugins = cfg.plugins || { entries: {} };
|
|
1929
2129
|
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
1930
2130
|
const pluginAliasMap = {
|
|
1931
|
-
'openclaw-
|
|
1932
|
-
'openclaw-
|
|
1933
|
-
'openclaw-facebook-
|
|
2131
|
+
'openclaw-browser-automation': ['browser-automation', 'openclaw-browser-automation'],
|
|
2132
|
+
'openclaw-zalo-mod': ['zalo-mod', 'openclaw-zalo-mod'],
|
|
2133
|
+
'openclaw-facebook-crawler': ['openclaw-facebook-crawler', 'openclaw-n8n-facebook-crawler', 'n8n-facebook-crawler'],
|
|
2134
|
+
'openclaw-n8n-facebook-poster': ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
|
|
1934
2135
|
};
|
|
1935
2136
|
const aliases = pluginAliasMap[id] || [id];
|
|
1936
2137
|
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
@@ -1946,12 +2147,8 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
1946
2147
|
cfg.plugins.entries[existingKey].config.dashboardPort = gwPort + 1;
|
|
1947
2148
|
}
|
|
1948
2149
|
}
|
|
1949
|
-
//
|
|
1950
|
-
cfg.plugins.allow
|
|
1951
|
-
const allowIds = aliases.concat(existingKey);
|
|
1952
|
-
for (const aid of allowIds) {
|
|
1953
|
-
if (!cfg.plugins.allow.includes(aid)) cfg.plugins.allow.push(aid);
|
|
1954
|
-
}
|
|
2150
|
+
// Only add the canonical config key to allow list (not all aliases)
|
|
2151
|
+
if (!cfg.plugins.allow.includes(existingKey)) cfg.plugins.allow.push(existingKey);
|
|
1955
2152
|
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
1956
2153
|
}
|
|
1957
2154
|
|
|
@@ -1977,14 +2174,23 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
1977
2174
|
}
|
|
1978
2175
|
}
|
|
1979
2176
|
|
|
1980
|
-
|
|
1981
|
-
|
|
2177
|
+
// Browser-automation plugin needs Docker rebuild for Playwright/Chromium deps
|
|
2178
|
+
const isBrowserPlugin = id === 'openclaw-browser-automation' || id === 'browser-automation';
|
|
2179
|
+
if (isBrowserPlugin && composeDir) {
|
|
2180
|
+
sendLog(`[plugin] Browser plugin requires Docker rebuild for Playwright/Chromium...`);
|
|
2181
|
+
const svcName = getBotServiceName(projectDir);
|
|
2182
|
+
await run('docker', ['compose', '-f', join(composeDir, 'docker-compose.yml'), 'up', '-d', '--build', '--force-recreate', svcName], { shell: false }).catch((err) => {
|
|
2183
|
+
sendLog(`[plugin] Docker rebuild failed: ${err.message}. Falling back to restart...`);
|
|
2184
|
+
return run('docker', ['restart', botContainer], { shell: false });
|
|
2185
|
+
});
|
|
2186
|
+
} else if (isZaloMod && composeDir) {
|
|
1982
2187
|
// Use docker compose up to apply new port mappings from docker-compose.yml
|
|
1983
2188
|
const svcName = getBotServiceName(projectDir);
|
|
1984
2189
|
await run('docker', ['compose', '-f', join(composeDir, 'docker-compose.yml'), 'up', '-d', '--force-recreate', '--no-deps', svcName], { shell: false }).catch(() =>
|
|
1985
2190
|
run('docker', ['restart', botContainer], { shell: false })
|
|
1986
2191
|
);
|
|
1987
2192
|
} else {
|
|
2193
|
+
sendLog(`[plugin] Restarting docker container to apply plugin...`);
|
|
1988
2194
|
await run('docker', ['restart', botContainer], { shell: false });
|
|
1989
2195
|
}
|
|
1990
2196
|
} else {
|
|
@@ -2016,9 +2222,9 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
2016
2222
|
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2017
2223
|
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
2018
2224
|
const pluginAliasMap = {
|
|
2019
|
-
'openclaw-zalo-mod': ['
|
|
2020
|
-
'openclaw-
|
|
2021
|
-
'openclaw-facebook-poster': ['openclaw-facebook-poster', 'openclaw-
|
|
2225
|
+
'openclaw-zalo-mod': ['zalo-mod', 'openclaw-zalo-mod'],
|
|
2226
|
+
'openclaw-facebook-crawler': ['openclaw-facebook-crawler', 'openclaw-n8n-facebook-crawler', 'n8n-facebook-crawler'],
|
|
2227
|
+
'openclaw-n8n-facebook-poster': ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
|
|
2022
2228
|
};
|
|
2023
2229
|
const aliases = pluginAliasMap[id] || [id];
|
|
2024
2230
|
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
@@ -2041,15 +2247,36 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
2041
2247
|
return { ok: true };
|
|
2042
2248
|
}
|
|
2043
2249
|
|
|
2250
|
+
async function getInstalledPluginVersion(projectDir, aliases = []) {
|
|
2251
|
+
if (!projectDir) return '';
|
|
2252
|
+
try {
|
|
2253
|
+
const instPath = join(projectDir, '.openclaw', 'plugins', 'installs.json');
|
|
2254
|
+
if (existsSync(instPath)) {
|
|
2255
|
+
const j = JSON.parse(await fsp.readFile(instPath, 'utf8'));
|
|
2256
|
+
const found = (j.plugins || []).find(p => aliases.some(a => String(p.pluginId || '').toLowerCase() === String(a).toLowerCase()));
|
|
2257
|
+
if (found && found.version) return found.version;
|
|
2258
|
+
}
|
|
2259
|
+
} catch (e) {}
|
|
2260
|
+
|
|
2261
|
+
for (const alias of aliases) {
|
|
2262
|
+
try {
|
|
2263
|
+
const pkgPath = join(projectDir, '.openclaw', 'extensions', alias, 'package.json');
|
|
2264
|
+
if (existsSync(pkgPath)) {
|
|
2265
|
+
const pkg = JSON.parse(await fsp.readFile(pkgPath, 'utf8'));
|
|
2266
|
+
if (pkg.version) return pkg.version;
|
|
2267
|
+
}
|
|
2268
|
+
} catch (e) {}
|
|
2269
|
+
}
|
|
2270
|
+
return '';
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2044
2273
|
async function getFeatureFlags(projectDir, agentId = '') {
|
|
2045
2274
|
const cfgPath = join(projectDir || '', '.openclaw', 'openclaw.json');
|
|
2046
2275
|
const cfg = existsSync(cfgPath) ? ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8').catch(() => '{}'))) : {};
|
|
2047
2276
|
const aid = agentId || cfg.agents?.list?.[0]?.id || 'bot';
|
|
2048
2277
|
const browserOn = !!cfg.browser?.enabled;
|
|
2049
2278
|
const cronOn = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
|
|
2050
|
-
|
|
2051
|
-
if (!cronOn) await applyFeatureToggle(projectDir, aid, 'skill', 'cron', true).catch(() => {});
|
|
2052
|
-
const fresh = existsSync(cfgPath) ? ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8').catch(() => '{}'))) : {};
|
|
2279
|
+
const fresh = cfg;
|
|
2053
2280
|
const freshSaved = {};
|
|
2054
2281
|
const installsPath = join(projectDir || '', '.openclaw', 'plugins', 'installs.json');
|
|
2055
2282
|
const installs = existsSync(installsPath) ? JSON.parse(await fsp.readFile(installsPath, 'utf8').catch(() => '{}')) : {};
|
|
@@ -2076,16 +2303,18 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
2076
2303
|
allowSet.has(a)
|
|
2077
2304
|
);
|
|
2078
2305
|
const aliases = {
|
|
2306
|
+
browser: ['openclaw-browser-automation', 'browser-automation'],
|
|
2079
2307
|
zalo: ['openclaw-zalo-mod', 'zalo-mod'],
|
|
2080
|
-
crawler: ['openclaw-
|
|
2081
|
-
poster: ['openclaw-facebook-poster', 'openclaw-
|
|
2308
|
+
crawler: ['openclaw-facebook-crawler', 'openclaw-n8n-facebook-crawler', 'n8n-facebook-crawler'],
|
|
2309
|
+
poster: ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
|
|
2082
2310
|
};
|
|
2083
2311
|
const flags = {
|
|
2084
|
-
'skill:browser':
|
|
2085
|
-
'skill:cron':
|
|
2312
|
+
'skill:browser': browserOn,
|
|
2313
|
+
'skill:cron': cronOn,
|
|
2314
|
+
'plugin:openclaw-browser-automation': isEnabled(aliases.browser),
|
|
2086
2315
|
'plugin:openclaw-zalo-mod': isEnabled(aliases.zalo),
|
|
2087
|
-
'plugin:openclaw-
|
|
2088
|
-
'plugin:openclaw-facebook-poster': isEnabled(aliases.poster),
|
|
2316
|
+
'plugin:openclaw-facebook-crawler': isEnabled(aliases.crawler),
|
|
2317
|
+
'plugin:openclaw-n8n-facebook-poster': isEnabled(aliases.poster),
|
|
2089
2318
|
};
|
|
2090
2319
|
const extensionsDir = join(projectDir || '', '.openclaw', 'extensions');
|
|
2091
2320
|
const extensionDirExists = (aliases = []) =>
|
|
@@ -2093,11 +2322,18 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
2093
2322
|
const isActuallyInstalled = (aliases = []) =>
|
|
2094
2323
|
extensionDirExists(aliases) || isInstalledByRecord(aliases);
|
|
2095
2324
|
const installed = {
|
|
2325
|
+
'plugin:openclaw-browser-automation': isActuallyInstalled(aliases.browser),
|
|
2096
2326
|
'plugin:openclaw-zalo-mod': isActuallyInstalled(aliases.zalo),
|
|
2097
|
-
'plugin:openclaw-
|
|
2098
|
-
'plugin:openclaw-facebook-poster': isActuallyInstalled(aliases.poster),
|
|
2327
|
+
'plugin:openclaw-facebook-crawler': isActuallyInstalled(aliases.crawler),
|
|
2328
|
+
'plugin:openclaw-n8n-facebook-poster': isActuallyInstalled(aliases.poster),
|
|
2329
|
+
};
|
|
2330
|
+
const versions = {
|
|
2331
|
+
'plugin:openclaw-browser-automation': await getInstalledPluginVersion(projectDir, aliases.browser),
|
|
2332
|
+
'plugin:openclaw-zalo-mod': await getInstalledPluginVersion(projectDir, aliases.zalo),
|
|
2333
|
+
'plugin:openclaw-facebook-crawler': await getInstalledPluginVersion(projectDir, aliases.crawler),
|
|
2334
|
+
'plugin:openclaw-n8n-facebook-poster': await getInstalledPluginVersion(projectDir, aliases.poster),
|
|
2099
2335
|
};
|
|
2100
|
-
return { flags, installed };
|
|
2336
|
+
return { flags, installed, versions };
|
|
2101
2337
|
}
|
|
2102
2338
|
|
|
2103
2339
|
async function serveStatic(req, res) {
|
|
@@ -2129,7 +2365,12 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2129
2365
|
}
|
|
2130
2366
|
if (url.pathname === '/api/system' && req.method === 'GET') {
|
|
2131
2367
|
const osChoice = detectOs();
|
|
2132
|
-
const [nodeStatus, npmStatus, dockerStatus, currentVersions] = await Promise.all([
|
|
2368
|
+
const [nodeStatus, npmStatus, dockerStatus, currentVersions] = await Promise.all([
|
|
2369
|
+
commandExists('node'),
|
|
2370
|
+
commandExists('npm'),
|
|
2371
|
+
commandExists('docker', ['version', '--format', '{{.Server.Version}}']),
|
|
2372
|
+
getCurrentRuntimeVersions()
|
|
2373
|
+
]);
|
|
2133
2374
|
const projectDir = state.projectDir && existsSync(join(state.projectDir, '.openclaw', 'openclaw.json')) ? state.projectDir : null;
|
|
2134
2375
|
const projectVersions = await resolveProjectRuntimeVersions(projectDir, state.mode).catch(() => null);
|
|
2135
2376
|
const mergedVersions = {
|
|
@@ -2138,7 +2379,38 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2138
2379
|
node: projectVersions?.node || currentVersions.node || String(nodeStatus?.output || '').trim(),
|
|
2139
2380
|
};
|
|
2140
2381
|
const projects = await discoverProjects(rootProjectDir).catch(() => []);
|
|
2141
|
-
|
|
2382
|
+
|
|
2383
|
+
let latestSetupVersion = SETUP_VERSION;
|
|
2384
|
+
try {
|
|
2385
|
+
const resp = await fetch('https://registry.npmjs.org/create-openclaw-bot/latest', { signal: AbortSignal.timeout(3000) });
|
|
2386
|
+
if (resp.ok) {
|
|
2387
|
+
const data = await resp.json();
|
|
2388
|
+
if (data.version) latestSetupVersion = data.version;
|
|
2389
|
+
}
|
|
2390
|
+
} catch (e) {}
|
|
2391
|
+
|
|
2392
|
+
return json(res, {
|
|
2393
|
+
os: osChoice,
|
|
2394
|
+
platform: process.platform,
|
|
2395
|
+
arch: process.arch,
|
|
2396
|
+
recommendedMode: recommendedMode(osChoice),
|
|
2397
|
+
node: nodeStatus,
|
|
2398
|
+
npm: npmStatus,
|
|
2399
|
+
docker: dockerStatus,
|
|
2400
|
+
versions: {
|
|
2401
|
+
desiredOpenclaw: OPENCLAW_NPM_SPEC,
|
|
2402
|
+
desiredNineRouter: NINE_ROUTER_NPM_SPEC,
|
|
2403
|
+
currentOpenclaw: mergedVersions.openclaw,
|
|
2404
|
+
currentNineRouter: mergedVersions.nineRouter,
|
|
2405
|
+
currentNode: mergedVersions.node,
|
|
2406
|
+
openclaw: mergedVersions.openclaw,
|
|
2407
|
+
nineRouter: mergedVersions.nineRouter,
|
|
2408
|
+
node: mergedVersions.node,
|
|
2409
|
+
setup: SETUP_VERSION,
|
|
2410
|
+
latestSetup: latestSetupVersion
|
|
2411
|
+
},
|
|
2412
|
+
projects
|
|
2413
|
+
});
|
|
2142
2414
|
}
|
|
2143
2415
|
if (url.pathname === '/api/projects/discover' && req.method === 'GET') {
|
|
2144
2416
|
return json(res, { ok: true, projects: await discoverProjects(rootProjectDir).catch(() => []) });
|
|
@@ -2219,6 +2491,35 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2219
2491
|
sendLog(`[update] ${target} update completed (${result.mode})`);
|
|
2220
2492
|
return json(res, result);
|
|
2221
2493
|
}
|
|
2494
|
+
if (url.pathname === '/api/setup/update' && req.method === 'POST') {
|
|
2495
|
+
sendLog('[update-setup] Starting update of Setup Wizard...');
|
|
2496
|
+
const isGit = existsSync(resolve(rootProjectDir, '.git'));
|
|
2497
|
+
if (isGit) {
|
|
2498
|
+
sendLog('[update-setup] Git repository detected. Pulling latest code and building...');
|
|
2499
|
+
setImmediate(async () => {
|
|
2500
|
+
try {
|
|
2501
|
+
await run('git', ['pull'], { cwd: rootProjectDir });
|
|
2502
|
+
await run('npm', ['install'], { cwd: rootProjectDir });
|
|
2503
|
+
await run('npm', ['run', 'build'], { cwd: rootProjectDir });
|
|
2504
|
+
sendLog('[update-setup] Setup Wizard updated successfully! Please restart the installer.');
|
|
2505
|
+
} catch (err) {
|
|
2506
|
+
sendLog(`[update-setup] Error updating: ${err.message}`);
|
|
2507
|
+
}
|
|
2508
|
+
});
|
|
2509
|
+
return json(res, { ok: true, mode: 'git' });
|
|
2510
|
+
} else {
|
|
2511
|
+
sendLog('[update-setup] Global npm package installation detected. Updating via npm...');
|
|
2512
|
+
setImmediate(async () => {
|
|
2513
|
+
try {
|
|
2514
|
+
await run('npm', ['install', '-g', 'create-openclaw-bot@latest'], { cwd: rootProjectDir });
|
|
2515
|
+
sendLog('[update-setup] Setup Wizard updated successfully! Please restart the installer.');
|
|
2516
|
+
} catch (err) {
|
|
2517
|
+
sendLog(`[update-setup] Error updating: ${err.message}`);
|
|
2518
|
+
}
|
|
2519
|
+
});
|
|
2520
|
+
return json(res, { ok: true, mode: 'npm' });
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2222
2523
|
if (url.pathname === '/api/bot/create' && req.method === 'POST') {
|
|
2223
2524
|
const body = await readJson(req);
|
|
2224
2525
|
const projectDir = await resolveProjectDir(rootProjectDir, body);
|
|
@@ -2311,9 +2612,10 @@ async function handler(req, res, rootProjectDir) {
|
|
|
2311
2612
|
{ name: 'Cron', slug: 'cron' },
|
|
2312
2613
|
],
|
|
2313
2614
|
plugins: [
|
|
2615
|
+
{ name: 'openclaw-browser-automation', package: 'openclaw-browser-automation' },
|
|
2314
2616
|
{ name: 'openclaw-zalo-mod', package: 'openclaw-zalo-mod' },
|
|
2315
|
-
{ name: 'openclaw-
|
|
2316
|
-
{ name: 'openclaw-facebook-poster', package: 'openclaw-facebook-poster' },
|
|
2617
|
+
{ name: 'openclaw-facebook-crawler', package: 'openclaw-facebook-crawler' },
|
|
2618
|
+
{ name: 'openclaw-n8n-facebook-poster', package: 'openclaw-n8n-facebook-poster' },
|
|
2317
2619
|
]
|
|
2318
2620
|
});
|
|
2319
2621
|
if (url.pathname === '/api/features' && req.method === 'GET') {
|