create-openclaw-bot 5.8.3 β†’ 5.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  # 🦞 OpenClaw Setup
4
4
 
5
5
  <p align="center">
6
- <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.3-0EA5E9?style=for-the-badge" alt="Version 5.8.3" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.4-0EA5E9?style=for-the-badge" alt="Version 5.8.4" /></a>
7
7
  <a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
8
8
  <a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
9
9
  <a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
@@ -23,7 +23,7 @@ A next-generation **Web UI Setup** and management dashboard that automates 100%
23
23
 
24
24
  ---
25
25
 
26
- ## πŸ†• What's New in v5.8.3
26
+ ## πŸ†• What's New in v5.8.4
27
27
 
28
28
  - πŸ”„ **Smart Header Update Button**: Instantly upgrades the setup wizard from the UI! The button queries the npm registry dynamically and only reveals itself when a newer release is published.
29
29
  - πŸ“‘ **Live Log-Streaming Upgrade**: Kicking off the update automatically executes the migration (running `git pull && npm install && npm run build` for Git clones, or `npm install -g create-openclaw-bot` for NPM installs) while streaming standard outputs in real-time straight to the dashboard's Logs terminal.
package/README.vi.md CHANGED
@@ -3,7 +3,7 @@
3
3
  # 🦞 OpenClaw Setup
4
4
 
5
5
  <p align="center">
6
- <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.3-0EA5E9?style=for-the-badge" alt="Version 5.8.3" /></a>
6
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.4-0EA5E9?style=for-the-badge" alt="Version 5.8.4" /></a>
7
7
  <a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
8
8
  <a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
9
9
  <a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
@@ -23,7 +23,7 @@ TrΓ¬nh cΓ i Δ‘αΊ·t vΓ  quαΊ£n trα»‹ **Web UI Setup** thαΊΏ hệ mα»›i giΓΊp tα»±
23
23
 
24
24
  ---
25
25
 
26
- ## πŸ†• CΓ³ gΓ¬ mα»›i trong v5.8.3
26
+ ## πŸ†• CΓ³ gΓ¬ mα»›i trong v5.8.4
27
27
 
28
28
  - πŸ”„ **NΓΊt cαΊ­p nhαΊ­t Header thΓ΄ng minh**: NΓ’ng cαΊ₯p trα»±c tiαΊΏp setup wizard tα»« giao diện! NΓΊt cαΊ­p nhαΊ­t tα»± truy vαΊ₯n npm registry vΓ  chỉ hiển thα»‹ khi cΓ³ phiΓͺn bαΊ£n mα»›i hΖ‘n.
29
29
  - πŸ“‘ **NΓ’ng cαΊ₯p Stream Log trα»±c tiαΊΏp**: Khởi chαΊ‘y cαΊ­p nhαΊ­t sαΊ½ tα»± Δ‘α»™ng nΓ’ng cαΊ₯p (chαΊ‘y `git pull && npm install && npm run build` cho bαΊ£n Git, hoαΊ·c `npm install -g create-openclaw-bot` cho bαΊ£n NPM) vΓ  Δ‘αΊ©y dΓ²ng log theo thời gian thα»±c về tab Logs.
package/dist/cli.js CHANGED
@@ -32,21 +32,17 @@ const args = process.argv.slice(2);
32
32
  if (isLocalRepo()) {
33
33
  const { startLocalInstaller } = await import('./server/local-server.js');
34
34
 
35
- if (args[0] === 'legacy' || args.includes('--legacy')) {
36
- await import('./legacy-cli.js');
37
- } else {
38
- const noOpen = args.includes('--no-open');
39
- const hostArg = args.find((arg) => arg.startsWith('--host='));
40
- const portArg = args.find((arg) => arg.startsWith('--port='));
41
- const projectDirArg = args.find((arg) => arg.startsWith('--project-dir='));
42
-
43
- await startLocalInstaller({
44
- openBrowser: !noOpen,
45
- host: hostArg ? hostArg.slice('--host='.length) : '127.0.0.1',
46
- preferredPort: portArg ? Number(portArg.slice('--port='.length)) : 51789,
47
- projectDir: projectDirArg ? projectDirArg.slice('--project-dir='.length) : process.cwd(),
48
- });
49
- }
35
+ const noOpen = args.includes('--no-open');
36
+ const hostArg = args.find((arg) => arg.startsWith('--host='));
37
+ const portArg = args.find((arg) => arg.startsWith('--port='));
38
+ const projectDirArg = args.find((arg) => arg.startsWith('--project-dir='));
39
+
40
+ await startLocalInstaller({
41
+ openBrowser: !noOpen,
42
+ host: hostArg ? hostArg.slice('--host='.length) : '127.0.0.1',
43
+ preferredPort: portArg ? Number(portArg.slice('--port='.length)) : 51789,
44
+ projectDir: projectDirArg ? projectDirArg.slice('--project-dir='.length) : process.cwd(),
45
+ });
50
46
  } else {
51
47
  console.log('\n============================================================');
52
48
  console.log(' 🦞 OpenClaw Setup β€” Auto-downloader & Installer');
@@ -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 } = loadSharedModule('../setup/shared/workspace-gen.js', '__openclawWorkspace');
16
+ const { buildWorkspaceFileMap, buildCronjobSkillMd, buildInfographicGeneratorSkillMd, buildInfographicGeneratorJs } = 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');
@@ -27,6 +27,7 @@ const STATE_FILE = '.openclaw-setup-state.json';
27
27
  const DEFAULT_MODEL = 'smart-route';
28
28
  const logClients = new Set();
29
29
  let zaloLoginInFlight = false;
30
+ let activeServerInstance = null;
30
31
  const state = {
31
32
  installing: false,
32
33
  installed: false,
@@ -901,7 +902,32 @@ async function buildBotStatus() {
901
902
  resolveProjectRuntimeVersions(state.projectDir, state.mode).catch(() => ({ openclaw: '', nineRouter: '', node: process.version || '' })),
902
903
  ]);
903
904
  const credentials = await readBotCredentials(state.projectDir).catch(() => ({ openclawToken: '', nineRouterApiKey: '' }));
904
- return { ...state, gatewayStatus, routerStatus, bots, credentials, runtimeVersions };
905
+
906
+ let activeModel = 'smart-route';
907
+ let activeProvider = '9Router';
908
+ if (state.projectDir) {
909
+ const cfgPath = join(state.projectDir, '.openclaw', 'openclaw.json');
910
+ if (existsSync(cfgPath)) {
911
+ try {
912
+ const raw = await fsp.readFile(cfgPath, 'utf8');
913
+ const cfg = JSON.parse(raw);
914
+ const modelStr = cfg.agents?.defaults?.model?.primary || cfg.agents?.list?.[0]?.model?.primary || 'smart-route';
915
+ if (modelStr.includes('/')) {
916
+ const parts = modelStr.split('/');
917
+ activeProvider = parts[0];
918
+ activeModel = parts.slice(1).join('/');
919
+ } else {
920
+ activeModel = modelStr;
921
+ activeProvider = cfg.models?.providers?.openai ? 'openai' : '9router';
922
+ }
923
+ } catch (e) {}
924
+ }
925
+ }
926
+
927
+ const cap = (s) => String(s).toLowerCase() === 'openai' ? 'OpenAI' : String(s).toLowerCase() === '9router' ? '9Router' : s;
928
+ activeProvider = cap(activeProvider);
929
+
930
+ return { ...state, gatewayStatus, routerStatus, bots, credentials, runtimeVersions, activeModel, activeProvider };
905
931
  }
906
932
 
907
933
  async function createBotInProject(projectDir, body = {}, runtime = {}) {
@@ -933,10 +959,16 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
933
959
  agentMetas: [],
934
960
  }));
935
961
 
936
- const existingAgentCount = cfg.agents.list.length;
937
962
  const used = new Set(cfg.agents.list.map((a) => a.id));
938
963
  const botName = uniqueDisplayName(requestedBotName, new Set(cfg.agents.list.map((a) => a.name || a.id)));
939
- const agentId = uniqueSlug(slugify(botName), used);
964
+ let agentId = body.agentId ? String(body.agentId).trim().toLowerCase().replace(/[^a-z0-9-_]+/g, '-') : '';
965
+ if (!agentId) {
966
+ agentId = uniqueSlug(slugify(botName), used);
967
+ } else {
968
+ if (used.has(agentId)) {
969
+ throw httpError(400, `Bot ID "${agentId}" Δ‘Γ£ tα»“n tαΊ‘i. Vui lΓ²ng chọn ID khΓ‘c.`);
970
+ }
971
+ }
940
972
  const workspaceDir = `workspace-${agentId}`;
941
973
  const model = cfg.agents.defaults?.model?.primary || cfg.agents.list[0]?.model?.primary || DEFAULT_MODEL;
942
974
  cfg.agents.list.push({
@@ -980,6 +1012,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
980
1012
  await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
981
1013
 
982
1014
  const hasScheduler = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
1015
+ const hasImageGen = !!(cfg.skills?.entries?.['image-gen']?.enabled);
983
1016
  const files = buildWorkspaceFileMap({
984
1017
  isVi: true,
985
1018
  botName,
@@ -991,6 +1024,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
991
1024
  workspacePath: `.openclaw/${workspaceDir}`,
992
1025
  hasZaloMod: channel === 'zalo-personal',
993
1026
  hasScheduler,
1027
+ hasImageGen,
994
1028
  });
995
1029
  const wsRoot = join(openclawHome, workspaceDir);
996
1030
  for (const [name, content] of Object.entries(files)) {
@@ -1080,6 +1114,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
1080
1114
  }
1081
1115
 
1082
1116
  const hasScheduler = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
1117
+ const hasImageGen = !!(cfg.skills?.entries?.['image-gen']?.enabled);
1083
1118
  const files = buildWorkspaceFileMap({
1084
1119
  isVi: true,
1085
1120
  botName,
@@ -1091,6 +1126,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
1091
1126
  workspacePath: `.openclaw/${workspaceDir}`,
1092
1127
  hasZaloMod: channel === 'zalo-personal',
1093
1128
  hasScheduler,
1129
+ hasImageGen,
1094
1130
  });
1095
1131
  const wsRoot = join(projectDir, '.openclaw', workspaceDir);
1096
1132
  for (const [name, content] of Object.entries(files)) {
@@ -1531,10 +1567,9 @@ async function writeCoreProject({ projectDir, osChoice, mode, gatewayPort = 1878
1531
1567
  await fsp.mkdir(openclawHome, { recursive: true });
1532
1568
  await fsp.mkdir(join(openclawHome, 'plugin-runtime-deps'), { recursive: true });
1533
1569
 
1534
- const selectedSkills = [];
1535
- const botName = 'OpenClaw Bot';
1536
- const agentMetas = [{ agentId: 'bot', name: botName, description: 'Personal OpenClaw assistant' }];
1537
- const common = { botName, channelKey: 'telegram', providerKey: '9router', model: DEFAULT_MODEL, deployMode: mode, osChoice, selectedSkills, skills: dataExport.SKILLS || [], agentMetas, gatewayPort, routerPort };
1570
+ const selectedSkills = ['memory', 'image-gen', 'web-search'];
1571
+ const agentMetas = [];
1572
+ const common = { channelKey: 'telegram', providerKey: '9router', model: DEFAULT_MODEL, deployMode: mode, osChoice, selectedSkills, skills: dataExport.SKILLS || [], agentMetas, gatewayPort, routerPort };
1538
1573
  const cfg = buildOpenclawJson(common);
1539
1574
  const env = buildEnvFileContent({ ...common, apiKey: '', botToken: '' });
1540
1575
  const approvals = buildExecApprovalsJson({ agentMetas });
@@ -1543,26 +1578,6 @@ async function writeCoreProject({ projectDir, osChoice, mode, gatewayPort = 1878
1543
1578
  await fsp.writeFile(join(projectDir, '.env'), env, 'utf8');
1544
1579
  await fsp.writeFile(join(openclawHome, 'exec-approvals.json'), JSON.stringify(approvals, null, 2), 'utf8');
1545
1580
 
1546
- const workspaceDir = 'workspace-bot';
1547
- const workspace = buildWorkspaceFileMap({
1548
- isVi: true,
1549
- botName,
1550
- channelKey: 'telegram',
1551
- providerKey: '9router',
1552
- selectedSkills,
1553
- skillsCatalog: dataExport.SKILLS || [],
1554
- agentMetas,
1555
- deployMode: mode,
1556
- osChoice,
1557
- agentWorkspaceDir: workspaceDir,
1558
- workspacePath: `.openclaw/${workspaceDir}`,
1559
- });
1560
- const wsRoot = join(openclawHome, workspaceDir);
1561
- for (const [name, content] of Object.entries(workspace)) {
1562
- await fsp.mkdir(dirname(join(wsRoot, name)), { recursive: true });
1563
- await fsp.writeFile(join(wsRoot, name), content || '', 'utf8');
1564
- }
1565
-
1566
1581
  if (mode === 'docker') {
1567
1582
  const projectName = slugify(basename(projectDir)) || 'bot';
1568
1583
  const docker = buildDockerArtifacts({
@@ -1616,7 +1631,7 @@ async function installCore({ osChoice, mode, projectDir, gatewayPort = 18789, ro
1616
1631
  await fsp.mkdir(dockerDir, { recursive: true });
1617
1632
  const envContent = existsSync(rootEnvPath)
1618
1633
  ? await fsp.readFile(rootEnvPath, 'utf8')
1619
- : buildEnvFileContent({ botName: 'OpenClaw Bot', channelKey: 'telegram', providerKey: '9router', deployMode: mode, osChoice, selectedSkills: [], skills: dataExport.SKILLS || [], agentMetas: [{ agentId: 'bot', name: 'OpenClaw Bot', description: 'Personal OpenClaw assistant' }], apiKey: '', botToken: '' });
1634
+ : buildEnvFileContent({ channelKey: 'telegram', providerKey: '9router', deployMode: mode, osChoice, selectedSkills: [], skills: dataExport.SKILLS || [], agentMetas: [], apiKey: '', botToken: '' });
1620
1635
  await fsp.writeFile(dockerEnvPath, envContent, 'utf8');
1621
1636
  sendLog(`Docker env ready: ${dockerEnvPath}`);
1622
1637
  await run('docker', ['compose', 'up', '-d', '--build'], { cwd: dockerDir });
@@ -1971,72 +1986,23 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
1971
1986
  const k = `${kind}:${id}`;
1972
1987
 
1973
1988
  if (kind === 'skill' && id === 'browser') {
1989
+ delete cfg.browser;
1990
+ cfg.plugins = cfg.plugins || { entries: {} };
1991
+ cfg.plugins.entries = cfg.plugins.entries || {};
1992
+ const aliases = ['browser-automation', 'openclaw-browser-automation'];
1993
+ const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
1994
+ cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
1995
+ cfg.plugins.entries[existingKey].enabled = !!enabled;
1996
+ cfg.plugins.allow = cfg.plugins.allow || [];
1974
1997
  if (enabled) {
1975
- cfg.browser = {
1976
- enabled: true,
1977
- defaultProfile: 'host-chrome',
1978
- profiles: { 'host-chrome': { cdpUrl: 'http://127.0.0.1:9222', color: '#4285F4' } },
1979
- };
1980
- const isHeadlessServer = process.platform === 'linux';
1981
- const docVariant = 'cli-server';
1982
-
1983
- for (const a of cfg.agents.list) {
1984
- const wm = buildWorkspaceFileMap({
1985
- isVi: true,
1986
- botName: a.name || a.id,
1987
- botDesc: '',
1988
- hasBrowser: false,
1989
- hasScheduler: true,
1990
- workspacePath: `.openclaw/${workspaceRelForAgent(a, cfg, projectDir)}/`,
1991
- agentWorkspaceDir: workspaceRelForAgent(a, cfg, projectDir),
1992
- variant: cfg.agents.list.length > 1 ? 'relay' : 'single',
1993
- browserDocVariant: docVariant,
1994
- });
1995
- const browserDoc = wm['BROWSER.md'] || '# BROWSER';
1996
- const browserTool = wm['browser-tool.js'] || '';
1997
- const bf = await readWorkspaceText(projectDir, a, 'BROWSER.md');
1998
- await fsp.writeFile(bf.file, browserDoc, 'utf8');
1999
- const bt = await readWorkspaceText(projectDir, a, 'browser-tool.js');
2000
- if (browserTool) await fsp.writeFile(bt.file, browserTool, 'utf8');
2001
-
2002
- const af = await readWorkspaceText(projectDir, a, 'AGENTS.md');
2003
- const agentsManaged = upsertManagedBlock(af.content, 'BROWSER_LINK', '- Browser docs: `BROWSER.md`');
2004
- await fsp.writeFile(af.file, agentsManaged, 'utf8');
2005
-
2006
- // Add to TOOLS.md
2007
- const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
2008
- const browserGuide = isHeadlessServer
2009
- ? `## 🌐 Browser Automation
2010
- - Xem hΖ°α»›ng dαΊ«n chi tiαΊΏt tαΊ‘i **BROWSER.md**
2011
- - Script Δ‘iều khiển: \`browser-tool.js\`
2012
- - ChαΊΏ Δ‘α»™ hiện tαΊ‘i: ChαΊ‘y ngαΊ§m Δ‘α»™c lαΊ­p qua Docker hoαΊ·c Xvfb trΓͺn VPS.
2013
- - **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!
2014
- - NαΊΏu browser lα»—i, thα»­ lαΊ‘i 1 lαΊ§n rα»“i mα»›i bΓ‘o user vα»›i lα»—i cα»₯ thể`
2015
- : `## 🌐 Browser Automation
2016
- - Xem hΖ°α»›ng dαΊ«n chi tiαΊΏt tαΊ‘i **BROWSER.md**
2017
- - Script Δ‘iều khiển: \`browser-tool.js\`
2018
- - ChαΊΏ Δ‘α»™ hiện tαΊ‘i:
2019
- - **MαΊ·c Δ‘α»‹nh:** ChαΊ‘y ngαΊ§m Δ‘α»™c lαΊ­p qua Docker hoαΊ·c Server.
2020
- - **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.
2021
- - KαΊΏt nα»‘i mαΊ·c Δ‘α»‹nh: \`http://127.0.0.1:9222\`
2022
- - **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!
2023
- - NαΊΏu browser lα»—i, thα»­ lαΊ‘i 1 lαΊ§n rα»“i mα»›i bΓ‘o user vα»›i lα»—i cα»₯ thể`;
2024
- await fsp.writeFile(tf.file, upsertManagedBlock(tf.content, 'BROWSER_GUIDE', browserGuide), 'utf8');
2025
- }
1998
+ if (!cfg.plugins.allow.includes(existingKey)) cfg.plugins.allow.push(existingKey);
2026
1999
  } else {
2027
- delete cfg.browser;
2000
+ cfg.plugins.allow = cfg.plugins.allow.filter((x) => x !== existingKey);
2028
2001
  for (const a of cfg.agents.list) {
2029
2002
  const bf = await readWorkspaceText(projectDir, a, 'BROWSER.md');
2030
2003
  if (existsSync(bf.file)) await fsp.rm(bf.file, { force: true });
2031
2004
  const bt = await readWorkspaceText(projectDir, a, 'browser-tool.js');
2032
2005
  if (existsSync(bt.file)) await fsp.rm(bt.file, { force: true });
2033
-
2034
- const af = await readWorkspaceText(projectDir, a, 'AGENTS.md');
2035
- await fsp.writeFile(af.file, removeManagedBlock(af.content, 'BROWSER_LINK'), 'utf8');
2036
-
2037
- // Remove from TOOLS.md
2038
- const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
2039
- await fsp.writeFile(tf.file, removeManagedBlock(tf.content, 'BROWSER_GUIDE'), 'utf8');
2040
2006
  }
2041
2007
  }
2042
2008
 
@@ -2059,37 +2025,17 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
2059
2025
  cfg.tools.alsoAllow = Array.from(new Set([...(cfg.tools.alsoAllow || []), 'group:automation']));
2060
2026
  cfg.commands = cfg.commands || {};
2061
2027
  cfg.commands.ownerAllowFrom = Array.from(new Set([...(cfg.commands.ownerAllowFrom || []), '*']));
2062
- const cronGuide = `## ⏰ Cron / LΓͺn lα»‹ch nhαΊ―c nhở (tool: \`cron\`)
2063
- - **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).
2064
- - **β›” TUYỆT ĐỐI KHΓ”NG sα»­a trα»±c tiαΊΏp file JSON** nhΖ° \`jobs.json\`, \`jobs-state.json\` trong thΖ° mα»₯c \`.openclaw/cron/\`. Dα»― liệu cron được lΖ°u trong SQLite database, file JSON chỉ lΓ  legacy format Δ‘Γ£ ngΖ°ng hα»— trợ. Mọi thao tΓ‘c PHαΊ’I thΓ΄ng qua tool \`cron\`.
2065
- - **Khi tαΊ‘o cronjob mα»›i (action \`add\`):**
2066
- - **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 Δ‘Γ³.
2067
- - 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.
2068
- - **Session:** LuΓ΄n dΓΉng \`sessionTarget: "isolated"\` cho cΓ‘c job chαΊ‘y nền (bΓ‘o cΓ‘o, nhαΊ―c nhở, gα»­i tin nhαΊ―n tα»± Δ‘α»™ng). Chỉ dΓΉng \`"main"\` cho system event/reminder ngαΊ―n.
2069
- - **Timezone:** LuΓ΄n chỉ Δ‘α»‹nh timezone rΓ΅ rΓ ng bαΊ±ng trường \`tz\` (vΓ­ dα»₯: \`"Asia/Ho_Chi_Minh"\`). NαΊΏu khΓ΄ng chỉ Δ‘α»‹nh, hệ thα»‘ng sαΊ½ dΓΉng timezone cα»§a Gateway host (thường lΓ  UTC) vΓ  job sαΊ½ chαΊ‘y sai giờ.
2070
- - **Delivery:** Đối vα»›i job cαΊ§n gα»­i kαΊΏt quαΊ£ ra chat, set \`delivery.mode: "announce"\` kΓ¨m \`delivery.channel\` vΓ  \`delivery.to\`.
2071
- - **Khi user yΓͺu cαΊ§u tαΊ―t/bαΊ­t/xΓ³a cronjob:**
2072
- 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.
2073
- 2. **BΖ°α»›c 2 (Xα»­ lΓ½):**
2074
- - Để xΓ³a: Gọi action \`remove\` vα»›i \`id\` tΓ¬m được.
2075
- - Để tαΊ―t/tαΊ‘m dα»«ng: Gọi action \`update\` vα»›i \`id\` vΓ  patch \`{"enabled": false}\`.
2076
- - Để bαΊ­t lαΊ‘i: Gọi action \`update\` vα»›i \`id\` vΓ  patch \`{"enabled": true}\`.
2077
- 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αΊΏ.
2078
- - 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.
2079
- - Khi thao tΓ‘c tool cho cron/scheduler, **khΓ΄ng Δ‘iền \`current\` vΓ o thΖ° mα»₯c Session**.
2080
- - **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α»‘ thΓ­ch hợp trΖ°α»›c ID cα»§a group. Vα»›i kΓͺnh Telegram/Matrix/Discord/Slack, dΓΉng tiền tα»‘ \`group:\` (vΓ­ dα»₯: \`group:123456\`). RIÊNG vα»›i kΓͺnh Zalo (\`zalouser\`), **bαΊ―t buα»™c** phαΊ£i sα»­ dα»₯ng tiền tα»‘ \`g:\` (vΓ­ dα»₯: \`g:3815464776067464419\`) để trΓ‘nh bα»‹ OpenClaw core lược bỏ tiền tα»‘ vΓ  gα»­i nhαΊ§m vΓ o DM chat cΓ‘ nhΓ’n.
2081
- - **One-shot job:** DΓΉng schedule kind \`"at"\` vα»›i ISO 8601 timestamp. Job sαΊ½ tα»± xΓ³a sau khi chαΊ‘y thΓ nh cΓ΄ng trα»« khi set \`deleteAfterRun: false\`.
2082
- - 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.`;
2083
2028
  for (const a of cfg.agents.list) {
2084
- const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
2085
- await fsp.writeFile(tf.file, upsertManagedBlock(tf.content, 'CRON_GUIDE', cronGuide), 'utf8');
2029
+ const sf = await readWorkspaceText(projectDir, a, 'skills/cronjob/SKILL.md');
2030
+ await fsp.mkdir(dirname(sf.file), { recursive: true });
2031
+ await fsp.writeFile(sf.file, buildCronjobSkillMd(true), 'utf8');
2086
2032
  }
2087
2033
  } else {
2088
2034
  if (cfg.tools?.alsoAllow) cfg.tools.alsoAllow = cfg.tools.alsoAllow.filter((x) => x !== 'group:automation');
2089
2035
  if (cfg.commands?.ownerAllowFrom) cfg.commands.ownerAllowFrom = cfg.commands.ownerAllowFrom.filter((x) => x !== '*');
2090
2036
  for (const a of cfg.agents.list) {
2091
- const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
2092
- await fsp.writeFile(tf.file, removeManagedBlock(tf.content, 'CRON_GUIDE'), 'utf8');
2037
+ const sf = await readWorkspaceText(projectDir, a, 'skills/cronjob/SKILL.md');
2038
+ if (existsSync(sf.file)) await fsp.rm(sf.file, { force: true });
2093
2039
  }
2094
2040
  }
2095
2041
 
@@ -2104,6 +2050,59 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
2104
2050
  }
2105
2051
  }
2106
2052
 
2053
+ if (kind === 'skill' && id === 'image-gen') {
2054
+ cfg.skills = cfg.skills || { entries: {} };
2055
+ cfg.skills.entries = cfg.skills.entries || {};
2056
+ cfg.skills.entries['image-gen'] = cfg.skills.entries['image-gen'] || {};
2057
+ cfg.skills.entries['image-gen'].enabled = !!enabled;
2058
+
2059
+ for (const a of cfg.agents.list) {
2060
+ const sf = await readWorkspaceText(projectDir, a, 'skills/infographic-generator/SKILL.md');
2061
+ const js = await readWorkspaceText(projectDir, a, 'skills/infographic-generator/image-generator.js');
2062
+ if (enabled) {
2063
+ await fsp.mkdir(dirname(sf.file), { recursive: true });
2064
+ await fsp.writeFile(sf.file, buildInfographicGeneratorSkillMd(), 'utf8');
2065
+ await fsp.writeFile(js.file, buildInfographicGeneratorJs(), 'utf8');
2066
+ } else {
2067
+ if (existsSync(sf.file)) await fsp.rm(sf.file, { force: true });
2068
+ if (existsSync(js.file)) await fsp.rm(js.file, { force: true });
2069
+ }
2070
+ }
2071
+
2072
+ // Write cfgPath early so recreation reads updated openclaw.json
2073
+ await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
2074
+
2075
+ // Recreate container to apply updated openclaw.json
2076
+ const hasDocker = existsSync(join(projectDir, 'docker', 'openclaw', 'docker-compose.yml'));
2077
+ if (hasDocker) {
2078
+ sendLog(`[docker] Infographic skill toggled to ${enabled}. Recreating containers...`);
2079
+ await recreateDockerBot(projectDir).catch((err) => sendLog(`[docker] Warning: Failed to recreate container: ${err.message}`));
2080
+ }
2081
+ }
2082
+
2083
+ if (kind === 'skill' && id === 'web-search') {
2084
+ cfg.plugins = cfg.plugins || { entries: {} };
2085
+ cfg.plugins.entries = cfg.plugins.entries || {};
2086
+ cfg.plugins.entries['duckduckgo'] = cfg.plugins.entries['duckduckgo'] || {};
2087
+ cfg.plugins.entries['duckduckgo'].enabled = !!enabled;
2088
+ cfg.plugins.allow = cfg.plugins.allow || [];
2089
+ if (enabled) {
2090
+ if (!cfg.plugins.allow.includes('duckduckgo')) cfg.plugins.allow.push('duckduckgo');
2091
+ } else {
2092
+ cfg.plugins.allow = cfg.plugins.allow.filter((x) => x !== 'duckduckgo');
2093
+ }
2094
+
2095
+ // Write cfgPath early so recreation reads updated openclaw.json
2096
+ await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
2097
+
2098
+ // Recreate container to apply updated openclaw.json
2099
+ const hasDocker = existsSync(join(projectDir, 'docker', 'openclaw', 'docker-compose.yml'));
2100
+ if (hasDocker) {
2101
+ sendLog(`[docker] Web Search skill toggled to ${enabled}. Recreating containers...`);
2102
+ await recreateDockerBot(projectDir).catch((err) => sendLog(`[docker] Warning: Failed to recreate container: ${err.message}`));
2103
+ }
2104
+ }
2105
+
2107
2106
  if (kind === 'plugin') {
2108
2107
  cfg.plugins = cfg.plugins || { entries: {} };
2109
2108
  cfg.plugins.entries = cfg.plugins.entries || {};
@@ -2344,6 +2343,8 @@ async function getFeatureFlags(projectDir, agentId = '') {
2344
2343
  Array.from(installedSpecs).some((spec) => spec.includes(a)) ||
2345
2344
  allowSet.has(a)
2346
2345
  );
2346
+ const imageGenOn = !!cfg.skills?.entries?.['image-gen']?.enabled;
2347
+ const webSearchOn = isEnabled(['duckduckgo']);
2347
2348
  const aliases = {
2348
2349
  browser: ['openclaw-browser-automation', 'browser-automation'],
2349
2350
  zalo: ['openclaw-zalo-mod', 'zalo-mod'],
@@ -2353,6 +2354,8 @@ async function getFeatureFlags(projectDir, agentId = '') {
2353
2354
  const flags = {
2354
2355
  'skill:browser': browserOn,
2355
2356
  'skill:cron': cronOn,
2357
+ 'skill:image-gen': imageGenOn,
2358
+ 'skill:web-search': webSearchOn,
2356
2359
  'plugin:openclaw-browser-automation': isEnabled(aliases.browser),
2357
2360
  'plugin:openclaw-zalo-mod': isEnabled(aliases.zalo),
2358
2361
  'plugin:openclaw-facebook-crawler': isEnabled(aliases.crawler),
@@ -2544,6 +2547,7 @@ async function handler(req, res, rootProjectDir) {
2544
2547
  await run('npm', ['install'], { cwd: rootProjectDir });
2545
2548
  await run('npm', ['run', 'build'], { cwd: rootProjectDir });
2546
2549
  sendLog('[update-setup] Setup Wizard updated successfully! Please restart the installer.');
2550
+ restartInstaller();
2547
2551
  } catch (err) {
2548
2552
  sendLog(`[update-setup] Error updating: ${err.message}`);
2549
2553
  }
@@ -2555,6 +2559,7 @@ async function handler(req, res, rootProjectDir) {
2555
2559
  try {
2556
2560
  await run('npm', ['install', '-g', 'create-openclaw-bot@latest'], { cwd: rootProjectDir });
2557
2561
  sendLog('[update-setup] Setup Wizard updated successfully! Please restart the installer.');
2562
+ restartInstaller();
2558
2563
  } catch (err) {
2559
2564
  sendLog(`[update-setup] Error updating: ${err.message}`);
2560
2565
  }
@@ -2652,6 +2657,8 @@ async function handler(req, res, rootProjectDir) {
2652
2657
  skills: [
2653
2658
  { name: 'Browser', slug: 'browser' },
2654
2659
  { name: 'Cron', slug: 'cron' },
2660
+ { name: 'TαΊ‘o αΊ£nh Infographic', slug: 'image-gen' },
2661
+ { name: 'Web Search', slug: 'web-search' },
2655
2662
  ],
2656
2663
  plugins: [
2657
2664
  { name: 'openclaw-browser-automation', package: 'openclaw-browser-automation' },
@@ -2700,13 +2707,42 @@ function openUrl(url) {
2700
2707
  child.unref();
2701
2708
  }
2702
2709
 
2710
+ function restartInstaller() {
2711
+ sendLog('[update-setup] Restarting Setup Wizard to apply update...');
2712
+ setTimeout(() => {
2713
+ try {
2714
+ if (activeServerInstance) {
2715
+ activeServerInstance.close();
2716
+ }
2717
+
2718
+ const entryFile = process.argv[1];
2719
+ const args = process.argv.slice(2);
2720
+
2721
+ if (!args.includes('--no-open')) {
2722
+ args.push('--no-open');
2723
+ }
2724
+
2725
+ const child = spawn(process.argv[0], [entryFile, ...args], {
2726
+ detached: true,
2727
+ stdio: 'inherit',
2728
+ shell: process.platform === 'win32'
2729
+ });
2730
+ child.unref();
2731
+
2732
+ process.exit(0);
2733
+ } catch (err) {
2734
+ sendLog(`[update-setup] Failed to restart: ${err.message}`);
2735
+ }
2736
+ }, 2000);
2737
+ }
2738
+
2703
2739
  export async function startLocalInstaller({ host = '127.0.0.1', preferredPort = 51789, openBrowser = true, projectDir = process.cwd() } = {}) {
2704
2740
  const port = await findPort(host, preferredPort);
2705
2741
  const server = http.createServer((req, res) => handler(req, res, projectDir));
2742
+ activeServerInstance = server;
2706
2743
  await new Promise((resolve) => server.listen(port, host, resolve));
2707
2744
  const url = `http://${host}:${port}`;
2708
2745
  console.log(`OpenClaw Setup UI: ${url}`);
2709
- console.log('Legacy CLI: create-openclaw-bot legacy');
2710
2746
  if (openBrowser) openUrl(url);
2711
2747
  }
2712
2748
 
@@ -53,7 +53,7 @@
53
53
  userInfo: '',
54
54
  securityRules: '',
55
55
  plugins: [],
56
- skills: ['memory'],
56
+ skills: ['memory', 'image-gen', 'web-search'],
57
57
  // Persisted credential inputs (Bug 1+2 fix)
58
58
  botToken: '',
59
59
  apiKey: '',
@@ -43,12 +43,10 @@
43
43
  },
44
44
  {
45
45
  id: 'image-gen',
46
- name: 'Image Generation',
46
+ name: 'TαΊ‘o αΊ£nh Infographic',
47
47
  icon: '🎨',
48
- descVi: 'TαΊ‘o αΊ£nh bαΊ±ng AI (DALLΒ·E, Flux, Midjourney...)', descEn: 'Generate images via AI (DALL-E, Flux, Midjourney...)',
48
+ descVi: 'TαΊ‘o αΊ£nh infographic, poster tα»± Δ‘α»™ng qua 9Router', descEn: 'Generate infographic & poster images via 9Router',
49
49
  slug: 'image-gen',
50
- noteVi: 'DΓΉng chung OPENAI_API_KEY (DALL-E) hoαΊ·c thΓͺm FLUX_API_KEY', noteEn: 'Uses OPENAI_API_KEY (DALL-E) or FLUX_API_KEY',
51
- envVars: ['# FLUX_API_KEY=<your_flux_key> # chỉ cαΊ§n nαΊΏu dΓΉng Flux'],
52
50
  },
53
51
  {
54
52
  id: 'code-interpreter',
@@ -140,18 +140,24 @@
140
140
  }
141
141
 
142
142
  // ── bindings (multi-bot or Zalo) ─────────────────────────────────────────
143
- if (isMultiBot && channelKey === 'telegram') {
143
+ if (agentMetas.length > 0 && isMultiBot && channelKey === 'telegram') {
144
144
  cfg.bindings = agentMetas.map((meta) => ({
145
145
  agentId: meta.agentId,
146
146
  match: { channel: 'telegram', accountId: meta.accountId || 'default' },
147
147
  }));
148
+ } else {
149
+ cfg.bindings = [];
148
150
  }
149
151
 
150
152
  // ── channels ─────────────────────────────────────────────────────────────
151
- cfg.channels = buildChannelConfig({
152
- channelKey, isMultiBot, groupId, agentMetas, botName: agentMetas[0]?.name || 'Bot',
153
- agentId: agentMetas[0]?.agentId || 'bot',
154
- });
153
+ if (agentMetas.length > 0) {
154
+ cfg.channels = buildChannelConfig({
155
+ channelKey, isMultiBot, groupId, agentMetas, botName: agentMetas[0]?.name || 'Bot',
156
+ agentId: agentMetas[0]?.agentId || 'bot',
157
+ });
158
+ } else {
159
+ cfg.channels = {};
160
+ }
155
161
 
156
162
  // ── tools ────────────────────────────────────────────────────────────────
157
163
  cfg.tools = { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } };
@@ -179,16 +185,7 @@
179
185
  auth: { mode: 'token', token: generateToken() },
180
186
  };
181
187
 
182
- // ── browser ──────────────────────────────────────────────────────────────
183
- if (hasBrowserDesktop) {
184
- cfg.browser = {
185
- enabled: true,
186
- defaultProfile: 'host-chrome',
187
- profiles: { 'host-chrome': { cdpUrl: 'http://127.0.0.1:9222', color: '#4285F4' } },
188
- };
189
- } else if (hasBrowserServer) {
190
- cfg.browser = { enabled: true };
191
- }
188
+ // ── browser (delegated to browser-automation plugin) ────────────────────
192
189
 
193
190
  // ── skills ───────────────────────────────────────────────────────────────
194
191
  const skillEntries = buildSkillsEntries(skills, selectedSkills);
@@ -206,7 +203,7 @@
206
203
  cfg.plugins = pluginsConfig.plugins;
207
204
 
208
205
  // ── bindings for zalouser ────────────────────────────────────────────────
209
- if (isZaloPersonal(channelKey)) {
206
+ if (agentMetas.length > 0 && isZaloPersonal(channelKey)) {
210
207
  cfg.bindings = cfg.bindings || [];
211
208
  const firstAgentId = agentMetas[0]?.agentId || 'bot';
212
209
  if (!cfg.bindings.some(b => b.match && b.match.channel === 'zalouser')) {
@@ -317,6 +314,14 @@
317
314
  allow.push('zalouser');
318
315
  }
319
316
 
317
+ // DuckDuckGo search plugin for web-search
318
+ if (selectedSkills.includes('web-search') || selectedSkills.includes('web_search')) {
319
+ entries['duckduckgo'] = { enabled: true };
320
+ if (!allow.includes('duckduckgo')) {
321
+ allow.push('duckduckgo');
322
+ }
323
+ }
324
+
320
325
  const plugins = { entries };
321
326
  plugins.allow = allow;
322
327