create-openclaw-bot 5.7.1 → 5.7.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/dist/setup.js CHANGED
@@ -952,21 +952,34 @@ const workspaceRoot = /** @type {OpenClawWorkspaceRoot} */ (
952
952
  return `# Identity\n\n- **Name:** ${name}\n- **Role:** ${desc}${emoji ? `\n- **Emoji:** ${emoji}` : ''}\n\n---\n\nI am **${name}**. When asked my name, I answer: _\"I'm ${name}\"_.${richAiNote ? "\nI don't pretend to be human — I'm an AI, and I'm proud of it." : ''}`;
953
953
  }
954
954
 
955
+ function buildZaloSoulSection(isVi, botName) {
956
+ const name = botName || 'Bot';
957
+ if (isVi) {
958
+ return `\n\n**RULE — Zalo Group: Phản hồi theo chế độ Silent Mode:**\nKhi nhận tin từ \`channel: zalouser\` và \`group_id\` có giá trị:\n\n- Nếu tin nhắn chứa \`@${name}\` → **LUÔN reply** (bất kể silent mode).\n- Nếu tin nhắn bắt đầu bằng \`/\` (slash command) → KHÔNG reply, plugin đã xử lý rồi.\n- Tin thường trong group (không mention, không slash):\n - Nếu **Silent Mode BẬT** → tin này KHÔNG đến được bot (plugin đã chặn).\n - Nếu **Silent Mode TẮT** → tin này ĐẾN ĐƯỢC bot → **reply bình thường** như DM.\n- DM (không có group_id) → reply bình thường.`;
959
+ }
960
+ return `\n\n**RULE — Zalo Group: Reply based on Silent Mode:**\nWhen receiving messages from \`channel: zalouser\` with a \`group_id\`:\n\n- If the message contains \`@${name}\` → **ALWAYS reply** (regardless of silent mode).\n- If the message starts with \`/\` (slash command) → DO NOT reply, the plugin already handled it.\n- Regular group messages (no mention, no slash):\n - If **Silent Mode is ON** → this message does NOT reach the bot (plugin blocks it).\n - If **Silent Mode is OFF** → this message DOES reach the bot → **reply normally** like DM.\n- DM (no group_id) → reply normally.`;
961
+ }
962
+
955
963
  function buildSoulDoc(options = {}) {
956
- const { isVi = true, persona = '', variant = 'wizard' } = options;
964
+ const { isVi = true, persona = '', variant = 'wizard', hasZaloMod = false, botName = 'Bot' } = options;
965
+ let doc;
957
966
  if (variant === 'cli-simple') {
958
- return isVi
967
+ doc = isVi
959
968
  ? `# Tính cách\n\n${persona || 'Thân thiện, rõ ràng, giải quyết việc thẳng vào mục tiêu.'}\n`
960
969
  : `# Soul\n\n${persona || 'Friendly, clear, and outcome-focused.'}\n`;
961
- }
962
- if (variant === 'cli-rich') {
963
- return isVi
970
+ } else if (variant === 'cli-rich') {
971
+ doc = isVi
964
972
  ? `# Tính cách\n\n**Hữu ích thật sự.** Bỏ qua câu nệ — cứ giúp thẳng.\n**Có cá tính.** Trợ lý không có cá tính thì chỉ là công cụ.\n\n## Phong cách\n- Tự nhiên, gần gũi như bạn bè\n- Trực tiếp, không parrot câu hỏi.${persona ? `\n\n## Custom Rules\n${persona}` : ''}`
965
973
  : `# Soul\n\n**Be genuinely helpful.** Skip filler and help directly.\n**Have personality.** An assistant without personality is just a tool.\n\n## Style\n- Natural and approachable\n- Direct, do not parrot the prompt.${persona ? `\n\n## Custom Rules\n${persona}` : ''}`;
974
+ } else {
975
+ doc = isVi
976
+ ? `# Tính cách\n\n**Hữu ích thật sự.** Bỏ qua câu nệ, cứ giúp thẳng.\n**Có cá tính.** Trợ lý không có cá tính thì chỉ là công cụ.\n\n## Phong cách\n- Tự nhiên, gần gũi\n- Trực tiếp, ngắn gọn${persona ? `\n\n## Custom Rules\n${persona}` : ''}`
977
+ : `# Soul\n\n**Be genuinely helpful.** Skip filler and just help.\n**Have personality.** An assistant with no personality is just a tool.\n\n## Style\n- Natural and concise\n- Direct and practical${persona ? `\n\n## Custom Rules\n${persona}` : ''}`;
966
978
  }
967
- return isVi
968
- ? `# Tính cách\n\n**Hữu ích thật sự.** Bỏ qua câu nệ, cứ giúp thẳng.\n**Có cá tính.** Trợ lý không có cá tính thì chỉ là công cụ.\n\n## Phong cách\n- Tự nhiên, gần gũi\n- Trực tiếp, ngắn gọn${persona ? `\n\n## Custom Rules\n${persona}` : ''}`
969
- : `# Soul\n\n**Be genuinely helpful.** Skip filler and just help.\n**Have personality.** An assistant with no personality is just a tool.\n\n## Style\n- Natural and concise\n- Direct and practical${persona ? `\n\n## Custom Rules\n${persona}` : ''}`;
979
+ if (hasZaloMod) {
980
+ doc += buildZaloSoulSection(isVi, botName);
981
+ }
982
+ return doc;
970
983
  }
971
984
 
972
985
  function buildTeamDoc(options = {}) {
@@ -1241,6 +1254,7 @@ const CDP_URL = 'http://127.0.0.1:9222';
1241
1254
  agentWorkspaceDir = 'workspace',
1242
1255
  hasBrowser = false,
1243
1256
  hasScheduler = false,
1257
+ hasZaloMod = false,
1244
1258
  browserDocVariant = '',
1245
1259
  } = options;
1246
1260
 
@@ -1268,19 +1282,25 @@ const CDP_URL = 'http://127.0.0.1:9222';
1268
1282
  : `\n\n## \u23F0 Cron / Scheduled Tasks\n- OpenClaw natively supports system tools for Cron Jobs.\n- When the user asks to schedule tasks or reminders, use the built-in tools automatically. Do NOT ask users to run crontab or Task Scheduler manually on the host.\n- When operating cron/scheduler tools, do **not** put \`current\` into the Session directory.\n- Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`)
1269
1283
  : '';
1270
1284
 
1285
+ const zaloModSection = hasZaloMod
1286
+ ? (isVi
1287
+ ? `\n\n## 💬 Zalo Group — Slash Commands (xử lý bởi plugin)\n\nPlugin \`zalo-mod\` tự động xử lý các slash command sau trong group. Bot KHÔNG cần reply cho chúng:\n\n| Command | Mô tả |\n|---------|-------|\n| \`/rules status\` | Xem cấu hình bot |\n| \`/rules silent-on/off\` | Bật/tắt silent mode |\n| \`/rules welcome-on/off\` | Bật/tắt welcome message |\n| \`/rules tracking-on/off\` | Bật/tắt ghi log chat |\n| \`/noi-quy\` | Hiện nội quy group |\n| \`/menu\` | Danh sách lệnh |\n| \`/groupid\` | Scan và cập nhật config |\n| \`/report\` | Báo cáo hoạt động group |\n\n### Zalo Sticker & Media\n- Sticker Zalo gửi dạng JSON → plugin tự convert thành \`[Sticker]\`\n- Ảnh/video/file trong group: zalouser channel chỉ forward text, media bị drop`
1288
+ : `\n\n## 💬 Zalo Group — Slash Commands (handled by plugin)\n\nThe \`zalo-mod\` plugin automatically handles these slash commands in group. Bot does NOT need to reply:\n\n| Command | Description |\n|---------|-------------|\n| \`/rules status\` | View bot config |\n| \`/rules silent-on/off\` | Toggle silent mode |\n| \`/rules welcome-on/off\` | Toggle welcome message |\n| \`/rules tracking-on/off\` | Toggle chat logging |\n| \`/noi-quy\` | Show group rules |\n| \`/menu\` | List commands |\n| \`/groupid\` | Scan and update config |\n| \`/report\` | Group activity report |\n\n### Zalo Sticker & Media\n- Zalo stickers arrive as JSON → plugin auto-converts to \`[Sticker]\`\n- Images/videos/files in groups: zalouser channel only forwards text, media is dropped`)
1289
+ : '';
1290
+
1271
1291
  const dmOverride = isVi
1272
1292
  ? `\n\n## Override: DM và Telegram reaction\n- Nếu metadata không nói rõ đây là group/supergroup, mặc định xem là chat riêng/DM và trả lời bình thường.\n- Quy tắc im lặng khi không ai được gọi chỉ áp dụng cho group chat, không áp dụng cho DM/chat riêng.\n- Trước MỖI phản hồi Telegram nhìn thấy bởi user, nếu turn hiện tại có tool/action reaction native thì ưu tiên dùng nó để thả \`👍\` trước, trừ khi \`👍\` đã có sẵn.\n- Nếu turn hiện tại không expose tool reaction hoặc chat đích không hỗ trợ reaction thật, cứ trả lời đúng vai trò và không được nói là mình đã thả reaction.`
1273
1293
  : `\n\n## Override: DM and Telegram reaction\n- If metadata does not clearly say this is a group/supergroup, treat it as a private DM and reply normally.\n- The stay-silent rule for unaddressed messages applies only to group chats, never to DMs.\n- Before EVERY user-visible Telegram reply, if the current turn exposes a native reaction tool/action, use it to add \`👍\` first unless \`👍\` is already present.\n- If the reaction tool is unavailable or the target chat does not support real reactions, just reply in-character and do not claim that you reacted.`;
1274
1294
 
1275
1295
  if (variant === 'relay') {
1276
1296
  return isVi
1277
- ? `# Hướng dẫn dùng tool\n\n## Tools có sẵn\n${skillsSection}\n\n## Quy tắc chung\n- Tóm tắt kết quả tool thay vì dump raw output.\n- Mọi bot đều có quyền sử dụng tất cả tool (scheduler, browser, exec). Vai trò (dev/marketing/...) chỉ là persona, KHÔNG giới hạn quyền dùng tool.\n- Workspace của bạn là \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${dmOverride}\n`
1278
- : `# Tool Usage Guide\n\n## Available Tools\n${skillsSection}\n\n## General Rules\n- Summarize tool output instead of dumping raw output.\n- All bots have equal access to all tools (scheduler, browser, exec). Roles (dev/marketing/...) are persona only, NOT tool permissions.\n- Your workspace is \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${dmOverride}\n`;
1297
+ ? `# Hướng dẫn dùng tool\n\n## Tools có sẵn\n${skillsSection}\n\n## Quy tắc chung\n- Tóm tắt kết quả tool thay vì dump raw output.\n- Mọi bot đều có quyền sử dụng tất cả tool (scheduler, browser, exec). Vai trò (dev/marketing/...) chỉ là persona, KHÔNG giới hạn quyền dùng tool.\n- Workspace của bạn là \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${zaloModSection}${dmOverride}\n`
1298
+ : `# Tool Usage Guide\n\n## Available Tools\n${skillsSection}\n\n## General Rules\n- Summarize tool output instead of dumping raw output.\n- All bots have equal access to all tools (scheduler, browser, exec). Roles (dev/marketing/...) are persona only, NOT tool permissions.\n- Your workspace is \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${zaloModSection}${dmOverride}\n`;
1279
1299
  }
1280
1300
 
1281
1301
  return isVi
1282
- ? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillsSection}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi — thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output${browserRef}\n\n## Quy ước\n- Web Search: chỉ dùng khi cần thông tin realtime hoặc user yêu cầu\n- Browser: chỉ mở trang khi user yêu cầu cụ thể\n- Memory: tự ghi nhớ thông tin tự nhiên, không cần user nhắc${cronSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot có thể đọc/ghi file trong thư mục workspace: \`${workspacePath}\`\n- Dùng để lưu notes, scripts, cấu hình tạm\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry tối đa 2 lần nếu tool lỗi network\n- Nếu vẫn lỗi: báo user kèm mô tả lỗi cụ thể và gợi ý workaround${dmOverride}\n`
1283
- : `# Tool Usage Guide\n\n## Installed Skills\n${skillsSection}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error — retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data${browserRef}\n\n## Conventions\n- Web Search: only use when needing real-time info or user explicitly asks\n- Browser: only open pages when user specifically requests\n- Memory: proactively remember important info without user prompting${cronSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot can read/write files in workspace: \`${workspacePath}\`\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry up to 2 times on network errors\n- If still failing: report to user with specific error description and workaround${dmOverride}\n`;
1302
+ ? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillsSection}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi — thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output${browserRef}\n\n## Quy ước\n- Web Search: chỉ dùng khi cần thông tin realtime hoặc user yêu cầu\n- Browser: chỉ mở trang khi user yêu cầu cụ thể\n- Memory: tự ghi nhớ thông tin tự nhiên, không cần user nhắc${cronSection}${zaloModSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot có thể đọc/ghi file trong thư mục workspace: \`${workspacePath}\`\n- Dùng để lưu notes, scripts, cấu hình tạm\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry tối đa 2 lần nếu tool lỗi network\n- Nếu vẫn lỗi: báo user kèm mô tả lỗi cụ thể và gợi ý workaround${dmOverride}\n`
1303
+ : `# Tool Usage Guide\n\n## Installed Skills\n${skillsSection}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error — retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data${browserRef}\n\n## Conventions\n- Web Search: only use when needing real-time info or user explicitly asks\n- Browser: only open pages when user specifically requests\n- Memory: proactively remember important info without user prompting${cronSection}${zaloModSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot can read/write files in workspace: \`${workspacePath}\`\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry up to 2 times on network errors\n- If still failing: report to user with specific error description and workaround${dmOverride}\n`;
1284
1304
  }
1285
1305
  function buildTeamsDoc(options = {}) {
1286
1306
  const {
@@ -1354,20 +1374,21 @@ const CDP_URL = 'http://127.0.0.1:9222';
1354
1374
  teamRosterFormatted = '',
1355
1375
  emoji = '',
1356
1376
  hasScheduler = false,
1377
+ hasZaloMod = false,
1357
1378
  } = opts;
1358
1379
 
1359
1380
  const isMultiBot = variant === 'relay';
1360
1381
 
1361
1382
  const files = {
1362
1383
  'IDENTITY.md': buildIdentityDoc({ isVi, name: botName, desc: botDesc, emoji }),
1363
- 'SOUL.md': buildSoulDoc({ isVi, persona, variant: soulVariant }),
1384
+ 'SOUL.md': buildSoulDoc({ isVi, persona, variant: soulVariant, hasZaloMod, botName }),
1364
1385
  'AGENTS.md': buildAgentsDoc({
1365
1386
  isVi, botName, botDesc, ownAliases, otherAgents, workspacePath,
1366
1387
  variant, includeSecurity: true, replyToDirectMessages: true,
1367
1388
  }),
1368
1389
  'USER.md': buildUserDoc({ isVi, userInfo, variant: userVariant || (isMultiBot ? 'cli-multi' : 'wizard') }),
1369
1390
  'TOOLS.md': buildToolsDoc({
1370
- isVi, skillListStr, workspacePath, variant, agentWorkspaceDir, hasBrowser, hasScheduler, browserDocVariant,
1391
+ isVi, skillListStr, workspacePath, variant, agentWorkspaceDir, hasBrowser, hasScheduler, hasZaloMod, browserDocVariant,
1371
1392
  }),
1372
1393
  'MEMORY.md': buildMemoryDoc({ isVi, variant: memoryVariant }),
1373
1394
  'HEARTBEAT.md': buildHeartbeatDoc({ isVi }),
@@ -1712,11 +1733,11 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
1712
1733
  },
1713
1734
  };
1714
1735
 
1715
- const allow = [];
1736
+ const allow = ['memory-core'];
1716
1737
 
1717
1738
  // zalo-mod plugin for Zalo Personal
1718
1739
  if (isZaloPersonal(channelKey)) {
1719
- allow.push('zalo-mod');
1740
+ allow.push('zalo-mod', 'zalouser');
1720
1741
  entries['zalo-mod'] = {
1721
1742
  enabled: true,
1722
1743
  config: {
@@ -1728,12 +1749,11 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
1728
1749
  spamWindowSeconds: 300,
1729
1750
  },
1730
1751
  };
1752
+ entries.zalouser = { enabled: true };
1731
1753
  }
1732
1754
 
1733
1755
  const plugins = { entries };
1734
- if (allow.length > 0) {
1735
- plugins.allow = allow;
1736
- }
1756
+ plugins.allow = allow;
1737
1757
 
1738
1758
  return { plugins };
1739
1759
  }
@@ -2606,6 +2626,7 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
2606
2626
  selectedModel,
2607
2627
  agentId,
2608
2628
  allSkills = [],
2629
+ dockerfilePlugins = [],
2609
2630
  dockerfileSkillInstallMode = 'none',
2610
2631
  runtimeCommandParts = [],
2611
2632
  volumeMount = '../..:/root/project',
@@ -2639,16 +2660,59 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
2639
2660
  const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
2640
2661
  ? `\n# Install skills (ClawHub)\n${allSkills.map((skill) => `RUN openclaw skills install ${skill} || echo "Warning: Failed to install ${skill} due to rate limits."`).join('\n')}\n`
2641
2662
  : '';
2663
+ const pluginLines = dockerfilePlugins.length > 0
2664
+ ? `\n# Install plugins (ClawHub)\n${dockerfilePlugins.map((p) => `RUN openclaw plugins install ${p} || echo "Warning: Failed to install plugin ${p}"`).join('\n')}\n`
2665
+ : '';
2642
2666
  const patchLine = `RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const files=fs.readdirSync(dir).filter(n=>/\\.js$/.test(n));let patched=0;for(const file of files){const p=path.join(dir,file);let s='';try{s=fs.readFileSync(p,'utf8');}catch{continue;}if(s.includes(to)||!s.includes(from))continue;s=s.replace(from,to);fs.writeFileSync(p,s);patched++;}if(!patched){process.exit(0);}"`;
2643
2667
 
2644
- // Dynamic runtime configuration injection for container internal IPs
2645
- const setupInternalIpScript = `const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://' + entry.address + ':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
2646
- const setupInternalIpB64 = encodeBase64Utf8(setupInternalIpScript);
2668
+ // Dynamic runtime configuration: backup config before any first-run install, restore after.
2669
+ // Missing plugin install may touch openclaw.json, so preserve critical fields.
2670
+ const backupConfigScript = `const fs=require('fs'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)){fs.copyFileSync(p,b);}`;
2671
+ const backupConfigB64 = encodeBase64Utf8(backupConfigScript);
2672
+
2673
+ const restoreConfigScript = `const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)&&fs.existsSync(b)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const bk=JSON.parse(fs.readFileSync(b,'utf8'));const keep=['agents','channels','bindings','commands','models','browser','skills'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://'+entry.address+':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',mode:c.gateway?.mode||bk.gateway?.mode||'local',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));fs.unlinkSync(b);}`;
2674
+ const restoreConfigB64 = encodeBase64Utf8(restoreConfigScript);
2647
2675
 
2648
2676
  const runtimeParts = runtimeCommandParts.filter(Boolean);
2649
- runtimeParts.unshift('export OPENCLAW_HOME="$PWD/.openclaw"');
2650
- runtimeParts.unshift('export OPENCLAW_STATE_DIR="$PWD/.openclaw"');
2651
- runtimeParts.unshift(`node -e 'eval(Buffer.from("${setupInternalIpB64}","base64").toString())'`);
2677
+ const runtimePrelude = [
2678
+ 'export OPENCLAW_HOME="${OPENCLAW_HOME:-$PWD/.openclaw}"',
2679
+ 'export OPENCLAW_STATE_DIR="${OPENCLAW_STATE_DIR:-$OPENCLAW_HOME}"',
2680
+ 'mkdir -p "$OPENCLAW_HOME" "$OPENCLAW_STATE_DIR"',
2681
+ 'if [ "$OPENCLAW_STATE_DIR" != "$OPENCLAW_HOME" ]; then',
2682
+ ' for path in "$OPENCLAW_HOME"/*; do',
2683
+ ' [ -e "$path" ] || continue',
2684
+ ' name="$(basename "$path")"',
2685
+ ' [ "$name" = "plugin-runtime-deps" ] && continue',
2686
+ ' [ "$name" = "logs" ] && continue',
2687
+ ' [ -e "$OPENCLAW_STATE_DIR/$name" ] || ln -s "$path" "$OPENCLAW_STATE_DIR/$name"',
2688
+ ' done',
2689
+ 'fi',
2690
+ 'ensure_plugin() {',
2691
+ ' id="$1"',
2692
+ ' spec="$2"',
2693
+ ' if [ -d "$OPENCLAW_HOME/extensions/$id" ]; then',
2694
+ ' echo "[entrypoint] plugin $id already installed"',
2695
+ ' return 0',
2696
+ ' fi',
2697
+ ' echo "[entrypoint] plugin $id missing; installing $spec"',
2698
+ ' openclaw plugins install "$spec" 2>/dev/null || echo "[entrypoint] warning: failed to install plugin $spec"',
2699
+ '}',
2700
+ 'ensure_skill() {',
2701
+ ' id="$1"',
2702
+ ' if find "$OPENCLAW_HOME" -maxdepth 4 -type d -path "*/skills/$id" -print -quit 2>/dev/null | grep -q .; then',
2703
+ ' echo "[entrypoint] skill $id already installed"',
2704
+ ' return 0',
2705
+ ' fi',
2706
+ ' echo "[entrypoint] skill $id missing; installing"',
2707
+ ' openclaw skills install "$id" 2>/dev/null || echo "[entrypoint] warning: failed to install skill $id"',
2708
+ '}',
2709
+ 'echo "[entrypoint] ensuring runtime assets, then starting gateway"',
2710
+ ];
2711
+ runtimeParts.unshift(...runtimePrelude);
2712
+ // Backup config BEFORE plugin installs (runtimeCommandParts may contain plugin install commands)
2713
+ runtimeParts.unshift(`node -e 'eval(Buffer.from("${backupConfigB64}","base64").toString())'`);
2714
+ // Restore config AFTER plugin installs (which may clobber openclaw.json)
2715
+ runtimeParts.push(`node -e 'eval(Buffer.from("${restoreConfigB64}","base64").toString())'`);
2652
2716
  if (hasBrowser) {
2653
2717
  runtimeParts.push('socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &');
2654
2718
  runtimeParts.push('Xvfb :99 -screen 0 1280x720x24 > /dev/null 2>&1 & DISPLAY=:99 openclaw gateway run');
@@ -2663,7 +2727,7 @@ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /v
2663
2727
  ${browserInstallLines}
2664
2728
  ARG OPENCLAW_VER="${openClawNpmSpec}"
2665
2729
  ARG CACHE_BUST=""
2666
- RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}
2730
+ RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}${pluginLines}
2667
2731
  ${patchLine}
2668
2732
  RUN node -e "require('fs').writeFileSync('/usr/local/bin/openclaw-entrypoint.sh', Buffer.from('${runtimeScriptB64}','base64').toString())" && chmod +x /usr/local/bin/openclaw-entrypoint.sh
2669
2733
  WORKDIR /root/project
@@ -2679,7 +2743,7 @@ CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
2679
2743
  const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64);
2680
2744
  const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
2681
2745
 
2682
- const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/root/project/.openclaw\n';
2746
+ const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/var/lib/openclaw-state\n';
2683
2747
 
2684
2748
  let compose;
2685
2749
  if (isMultiBot) {
@@ -2872,6 +2936,18 @@ ${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''}
2872
2936
  - "18791:18791"`;
2873
2937
  }
2874
2938
 
2939
+ compose = compose.replaceAll(
2940
+ ` - ${volumeMount}`,
2941
+ ` - ${volumeMount}\n - openclaw-state:/var/lib/openclaw-state`
2942
+ );
2943
+ if (compose.includes('\nvolumes:\n')) {
2944
+ if (!compose.includes(' openclaw-state:')) {
2945
+ compose = compose.replace('\nvolumes:\n', '\nvolumes:\n openclaw-state:\n');
2946
+ }
2947
+ } else {
2948
+ compose += '\n\nvolumes:\n openclaw-state:';
2949
+ }
2950
+
2875
2951
  return {
2876
2952
  dockerfile,
2877
2953
  compose,
@@ -2932,6 +3008,7 @@ function buildNativeScriptCtx(options) {
2932
3008
  const p = PLUGINS.find((x) => x.id === pid);
2933
3009
  if (p) allPlugins.push(p.package);
2934
3010
  });
3011
+ if (ch && ch.hasZaloPersonal) allPlugins.push('openclaw-zalo-mod');
2935
3012
  if (isMultiBot && state.channel === 'telegram') allPlugins.push(relayPluginSpec);
2936
3013
  const uniquePlugins = [...new Set(allPlugins)];
2937
3014
  const pluginCmd = uniquePlugins.length > 0 ? uniquePlugins.map(function(pkg) { return 'call npm exec -- openclaw plugins install ' + pkg + ' || echo [WARN] Plugin ' + pkg + ' cai dat that bai (co the do rate limit). Ban co the cai thu cong sau.'; }).join('\r\n') : '';
@@ -3379,6 +3456,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
3379
3456
  hasBrowser,
3380
3457
  soulVariant: 'wizard',
3381
3458
  memoryVariant: 'wizard',
3459
+ hasZaloMod: state.channel === 'zalo-personal',
3382
3460
  });
3383
3461
  }
3384
3462
 
@@ -3553,11 +3631,18 @@ function generateZaloLoginSh(opts) {
3553
3631
  `if [ -f "${credPath}" ]; then`,
3554
3632
  ` echo "[OK] Session Zalo da ton tai. Bo qua buoc dang nhap."`,
3555
3633
  `else`,
3556
- ` echo "[Zalo] Chua co session. Chay lenh sau trong terminal khac:"`,
3557
- ` echo " ${loginCmd}"`,
3634
+ ` echo "[Zalo] Chua co session. Bat dau dang nhap..."`,
3635
+ ` ${loginCmd}`,
3636
+ ` ZALO_QR="/tmp/openclaw/openclaw-zalouser-qr${useInstance ? '-default' : ''}.png"`,
3637
+ ` echo ""`,
3638
+ ` echo "=== HUONG DAN DANG NHAP ZALO ==="`,
3639
+ ` echo " 1. Tim file QR tai: $ZALO_QR"`,
3640
+ ` echo " 2. Copy file QR ra may: scp user@host:$ZALO_QR ./zalo-qr.png"`,
3641
+ ` echo " Hoac mo truc tiep: xdg-open $ZALO_QR 2>/dev/null || open $ZALO_QR 2>/dev/null || echo 'Mo file QR thu cong'"`,
3642
+ ` echo " 3. Mo app Zalo > Quet QR > quet ma trong file QR"`,
3643
+ ` echo " 4. Doi thay Login successful trong terminal"`,
3644
+ ` echo "================================"`,
3558
3645
  ` echo ""`,
3559
- ` echo " QR code se xuat hien trong terminal do."`,
3560
- ` echo " Sau khi quet QR thanh cong, nhay Enter de tiep tuc."`,
3561
3646
  ` read -p "Nhan Enter sau khi dang nhap Zalo thanh cong... "`,
3562
3647
  `fi`,
3563
3648
  ];
@@ -3710,6 +3795,34 @@ New-Item -ItemType Directory -Force -Path "$projectDir" | Out-Null
3710
3795
  ps += `Write-Host "[3/4] ${isVi ? 'Build Docker image...' : 'Building Docker image...'}" -ForegroundColor Yellow\n`;
3711
3796
  ps += `Set-Location "$projectDir\\docker\\openclaw"\n$cacheBust = (Get-Date -Format 'yyyyMMddHHmmss')\n& docker compose build --build-arg CACHE_BUST=$cacheBust\n`;
3712
3797
  ps += `Write-Host "[4/4] ${isVi ? 'Khoi dong bot...' : 'Starting bot...'}" -ForegroundColor Yellow\n& docker compose up -d\n`;
3798
+
3799
+ if (state.channel === 'zalo-personal') {
3800
+ const containerName = 'openclaw-bot';
3801
+ const qrPath = '/tmp/openclaw/openclaw-zalouser-qr-default.png';
3802
+ ps += `\nWrite-Host "" -ForegroundColor White\n`;
3803
+ ps += `Write-Host "${isVi ? '=== DANG NHAP ZALO ===' : '=== ZALO LOGIN ==='}" -ForegroundColor Cyan\n`;
3804
+ ps += `Write-Host "${isVi ? 'Doi gateway khoi dong xong (30 giay)...' : 'Waiting for gateway to start (30s)...'}" -ForegroundColor Yellow\n`;
3805
+ ps += `Start-Sleep -Seconds 30\n`;
3806
+ ps += `Write-Host "" -ForegroundColor White\n`;
3807
+ ps += `Write-Host "${isVi ? 'Huong dan dang nhap Zalo:' : 'Zalo login instructions:'}" -ForegroundColor White\n`;
3808
+ ps += `Write-Host " ${isVi ? '1. cd docker\\\\openclaw' : '1. cd docker\\\\openclaw'}" -ForegroundColor White\n`;
3809
+ ps += `Write-Host " ${isVi ? `2. docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose` : `2. docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose`}" -ForegroundColor White\n`;
3810
+ ps += `Write-Host " ${isVi ? `3. Mo Docker Desktop > container ${containerName} > tab Files > tim file: ${qrPath}` : `3. Open Docker Desktop > container ${containerName} > Files tab > find: ${qrPath}`}" -ForegroundColor White\n`;
3811
+ ps += `Write-Host " ${isVi ? ` Hoac chay: docker cp ${containerName}:${qrPath} ./zalo-qr.png` : ` Or run: docker cp ${containerName}:${qrPath} ./zalo-qr.png`}" -ForegroundColor White\n`;
3812
+ ps += `Write-Host " ${isVi ? '4. Mo app Zalo > Quet QR > quet ma trong file QR' : '4. Open Zalo app > Scan QR > scan the QR image'}" -ForegroundColor White\n`;
3813
+ ps += `Write-Host " ${isVi ? '5. Doi thay chu Login successful trong terminal' : '5. Wait for Login successful in terminal'}" -ForegroundColor White\n`;
3814
+ ps += `Write-Host " ${isVi ? '6. Restart container: docker compose restart' : '6. Restart container: docker compose restart'}" -ForegroundColor White\n`;
3815
+ ps += `Write-Host "" -ForegroundColor White\n`;
3816
+ ps += `Write-Host "${isVi ? 'Dung gateway de login...' : 'Stopping gateway for login...'}" -ForegroundColor Yellow\\n`;
3817
+ ps += `& docker exec ${containerName} openclaw gateway stop 2>$null\\n`;
3818
+ ps += `Start-Sleep -Seconds 3\\n`;
3819
+ ps += `Write-Host "${isVi ? 'Dang chay lenh login...' : 'Running login command...'}" -ForegroundColor Yellow\n`;
3820
+ ps += `& docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose\n`;
3821
+ ps += `Write-Host "" -ForegroundColor White\n`;
3822
+ ps += `Write-Host "${isVi ? 'Restart container de ap dung...' : 'Restarting container to apply...'}" -ForegroundColor Green\n`;
3823
+ ps += `& docker compose restart\n`;
3824
+ }
3825
+
3713
3826
  ps += `} catch { Write-Host $_.Exception.Message -ForegroundColor Red }\nRead-Host "${isVi ? 'Nhan Enter de thoat' : 'Press Enter to exit'}"\n`;
3714
3827
  return `@echo off
3715
3828
  chcp 65001>nul
@@ -3747,6 +3860,16 @@ echo ""
3747
3860
  script += `cat > "${path}" << 'CLAWEOF'\n${String(content)}${String(content).endsWith('\n') ? '' : '\n'}CLAWEOF\n\n`;
3748
3861
  });
3749
3862
  script += `echo "${isVi ? 'Files created' : 'Files created'}"\ncd "docker/openclaw"\ndocker compose build --build-arg CACHE_BUST=$(date +%s)\ndocker compose up --detach\n`;
3863
+
3864
+ if (state.channel === 'zalo-personal') {
3865
+ const containerName = 'openclaw-bot';
3866
+ const qrPath = '/tmp/openclaw/openclaw-zalouser-qr-default.png';
3867
+ script += `\necho ""\necho "${isVi ? '=== DANG NHAP ZALO ===' : '=== ZALO LOGIN ==='}"\necho "${isVi ? 'Doi container khoi dong 10 giay...' : 'Waiting 10s for container to start...'}"\nsleep 10\n`;
3868
+ script += `echo "${isVi ? 'Huong dan dang nhap Zalo:' : 'Zalo login instructions:'}"\necho " ${isVi ? '1. cd docker/openclaw' : '1. cd docker/openclaw'}"\necho " 2. docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose"\necho " ${isVi ? `3. Tim file QR trong container: ${qrPath}` : `3. Find QR image in container: ${qrPath}`}"\necho " ${isVi ? ` Hoac chay: docker cp ${containerName}:${qrPath} ./zalo-qr.png` : ` Or run: docker cp ${containerName}:${qrPath} ./zalo-qr.png`}"\necho " ${isVi ? '4. Mo app Zalo > Quet QR > quet ma' : '4. Open Zalo app > Scan QR > scan'}"\necho " ${isVi ? '5. Doi thay Login successful' : '5. Wait for Login successful'}"\necho " ${isVi ? '6. Restart: docker compose restart' : '6. Restart: docker compose restart'}"\necho ""\n`;
3869
+ script += `docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose\n`;
3870
+ script += `echo "${isVi ? 'Restart container...' : 'Restarting container...'}"\ndocker compose restart\n`;
3871
+ }
3872
+
3750
3873
  return script;
3751
3874
  }
3752
3875
 
@@ -3935,18 +4058,7 @@ function generateWinBat(ctx) {
3935
4058
 
3936
4059
  if (state.channel === 'zalo-personal') {
3937
4060
  lines.push('echo [5/6] Dang nhap Zalo Personal...');
3938
- lines.push('echo.');
3939
- lines.push('echo === HUONG DAN DANG NHAP ZALO ===');
3940
- lines.push('echo Cua so Zalo Login se mo. Hay:');
3941
- lines.push('echo 1. Doi QR hien ra trong cua so Zalo Login');
3942
- lines.push('echo 2. Mo app Zalo, chon Quet QR va quet ma');
3943
- lines.push('echo 3. Doi thay chu "Login successful" trong cua so do');
3944
- lines.push('echo 4. Dong cua so Zalo Login');
3945
- lines.push('echo ================================');
3946
- lines.push('echo.');
3947
- lines.push('start "Zalo Login" cmd /k "cd /d \"%PROJECT_DIR%\" && set OPENCLAW_HOME=%OPENCLAW_HOME% && set OPENCLAW_STATE_DIR=%OPENCLAW_HOME% && openclaw channels login --channel zalouser --verbose"');
3948
- lines.push('echo Nhan phim bat ky sau khi dong cua so Zalo Login...');
3949
- lines.push('pause >nul');
4061
+ lines.push(...generateZaloLoginBat({ homeVar: '%OPENCLAW_HOME%', projectDirVar: '%PROJECT_DIR%', label: 'win' }));
3950
4062
  lines.push('call openclaw gateway stop 2>nul');
3951
4063
  lines.push('timeout /t 2 /nobreak >nul');
3952
4064
  lines.push('echo [6/6] Khoi dong bot...');
@@ -4008,6 +4120,28 @@ function generateMacOsSh(ctx) {
4008
4120
  sh.push('if docker compose version > /dev/null 2>&1; then COMPOSE="docker compose"; else COMPOSE="docker-compose"; fi');
4009
4121
  sh.push('cd docker/openclaw');
4010
4122
  sh.push('$COMPOSE up --detach --build');
4123
+ if (state.channel === 'zalo-personal') {
4124
+ const containerName = 'openclaw-bot';
4125
+ const qrPath = '/tmp/openclaw/openclaw-zalouser-qr-default.png';
4126
+ sh.push('echo ""');
4127
+ sh.push('echo "=== DANG NHAP ZALO ==="');
4128
+ sh.push('echo "Doi container khoi dong 10 giay..."');
4129
+ sh.push('sleep 10');
4130
+ sh.push('echo "Dung gateway de login..."');
4131
+ sh.push(`docker exec ${containerName} openclaw gateway stop 2>/dev/null || true`);
4132
+ sh.push('sleep 3');
4133
+ sh.push('echo "Huong dan dang nhap Zalo:"');
4134
+ sh.push(`echo " 1. cd docker/openclaw"`);
4135
+ sh.push(`echo " 2. docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose"`);
4136
+ sh.push(`echo " 3. Tim file QR trong container: ${qrPath}"`);
4137
+ sh.push(`echo " Hoac chay: docker cp ${containerName}:${qrPath} ./zalo-qr.png"`);
4138
+ sh.push('echo " 4. Mo app Zalo > Quet QR > quet ma"');
4139
+ sh.push('echo " 5. Doi thay Login successful"');
4140
+ sh.push('echo " 6. Restart: $COMPOSE restart"');
4141
+ sh.push('echo ""');
4142
+ sh.push(`docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose || true`);
4143
+ sh.push('$COMPOSE restart');
4144
+ }
4011
4145
  sh.push('echo "\u2705 Bot dang chay via Docker. Xem log: docker logs -f openclaw-bot"');
4012
4146
  scriptContent = sh.filter(Boolean).join('\n');
4013
4147
  } else {
@@ -4041,13 +4175,19 @@ function generateMacOsSh(ctx) {
4041
4175
  appendShWriteCommands(sh, sharedNativeFileMap());
4042
4176
  const _uninstallMacMulti = generateUninstallScript();
4043
4177
  if (_uninstallMacMulti) appendShWriteCommands(sh, { [_uninstallMacMulti.name]: _uninstallMacMulti.content });
4044
- sh.push('echo "Starting shared multi-bot gateway..."');
4045
- sh.push('openclaw gateway run');
4178
+ sh.push('echo "Starting shared multi-bot gateway..."');
4179
+ if (state.channel === 'zalo-personal') {
4180
+ sh.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
4181
+ }
4182
+ sh.push('openclaw gateway run');
4046
4183
  } else {
4047
4184
  appendShWriteCommands(sh, botFiles(0));
4048
4185
  const _uninstallMac = generateUninstallScript();
4049
4186
  if (_uninstallMac) appendShWriteCommands(sh, { [_uninstallMac.name]: _uninstallMac.content });
4050
- sh.push('openclaw gateway run');
4187
+ if (state.channel === 'zalo-personal') {
4188
+ sh.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
4189
+ }
4190
+ sh.push('openclaw gateway run');
4051
4191
  }
4052
4192
  scriptContent = sh.filter(Boolean).join('\n');
4053
4193
  }
@@ -4149,6 +4289,11 @@ GWEOF`);
4149
4289
  vps.push(`pm2 start "$PROJECT_DIR/.9router/9router-smart-route-sync.js" --name ${appName}-9router-sync --interpreter "$NODE_BIN" --env DATA_DIR="$DATA_DIR"`);
4150
4290
  }
4151
4291
 
4292
+ // ── Zalo Personal Login (before gateway) ──────────────────────────────
4293
+ if (state.channel === 'zalo-personal') {
4294
+ vps.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
4295
+ }
4296
+
4152
4297
  // Gateway: start the bash wrapper script (has all env setup baked in)
4153
4298
  vps.push(`pm2 start "$PROJECT_DIR/.openclaw/start-gateway.sh" --name ${appName} --interpreter bash`);
4154
4299
  vps.push('pm2 save && pm2 startup');
@@ -4220,11 +4365,17 @@ function generateLinuxSh(ctx) {
4220
4365
  const _uninstallLnxMulti = generateUninstallScript();
4221
4366
  if (_uninstallLnxMulti) appendShWriteCommands(lnx, { [_uninstallLnxMulti.name]: _uninstallLnxMulti.content });
4222
4367
  lnx.push('echo "Starting shared multi-bot gateway..."');
4368
+ if (state.channel === 'zalo-personal') {
4369
+ lnx.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
4370
+ }
4223
4371
  lnx.push('openclaw gateway run');
4224
4372
  } else {
4225
4373
  appendShWriteCommands(lnx, botFiles(0));
4226
4374
  const _uninstallLnx = generateUninstallScript();
4227
4375
  if (_uninstallLnx) appendShWriteCommands(lnx, { [_uninstallLnx.name]: _uninstallLnx.content });
4376
+ if (state.channel === 'zalo-personal') {
4377
+ lnx.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
4378
+ }
4228
4379
  lnx.push('openclaw gateway run');
4229
4380
  }
4230
4381
  scriptContent = lnx.filter(Boolean).join('\n');
@@ -5749,6 +5900,7 @@ model:
5749
5900
  // 3. Dockerfile + docker-compose.yml
5750
5901
  const allPlugins = [];
5751
5902
  if (ch.pluginInstall) allPlugins.push(ch.pluginInstall);
5903
+ if (ch.hasZaloPersonal) allPlugins.push('openclaw-zalo-mod');
5752
5904
  state.config.plugins.forEach((pid) => {
5753
5905
  const plug = PLUGINS.find((p) => p.id === pid);
5754
5906
  if (plug) allPlugins.push(plug.package);
@@ -5762,10 +5914,15 @@ model:
5762
5914
  });
5763
5915
  const dockerGen = globalThis.__openclawDockerGen;
5764
5916
  const relayPluginInstallCmd = isMultiBot ? buildRelayPluginInstallCommand('openclaw') : '';
5917
+ const pluginRuntimeSpecs = allPlugins.filter((p) => p && p !== '@openclaw/zalouser');
5918
+ const pluginIdForSpec = (spec) => {
5919
+ if (String(spec).includes('zalo-mod')) return 'zalo-mod';
5920
+ return String(spec).replace(/^@openclaw\//, '').replace(/^openclaw-/, '');
5921
+ };
5765
5922
  const pluginInstallCmd = [
5766
- ...allPlugins.map((p) => `openclaw plugins install ${p} 2>/dev/null || true`),
5923
+ ...pluginRuntimeSpecs.map((p) => `ensure_plugin ${pluginIdForSpec(p)} ${p}`),
5767
5924
  relayPluginInstallCmd,
5768
- ].filter(Boolean).join(' && ') || '';
5925
+ ].filter(Boolean).join('\n') || '';
5769
5926
  const dockerArtifacts = dockerGen.buildDockerArtifacts({
5770
5927
  openClawNpmSpec: common.OPENCLAW_NPM_SPEC,
5771
5928
  openClawRuntimePackages,
@@ -5776,6 +5933,7 @@ model:
5776
5933
  selectedModel: state.config.model || 'ollama/gemma4:e2b',
5777
5934
  agentId: 'bot',
5778
5935
  allSkills,
5936
+ dockerfilePlugins: [],
5779
5937
  dockerfileSkillInstallMode: 'build',
5780
5938
  runtimeCommandParts: [
5781
5939
  pluginInstallCmd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.7.1",
3
+ "version": "5.7.4",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {