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.
@@ -295,11 +295,11 @@
295
295
  },
296
296
  };
297
297
 
298
- const allow = [];
298
+ const allow = ['memory-core'];
299
299
 
300
300
  // zalo-mod plugin for Zalo Personal
301
301
  if (isZaloPersonal(channelKey)) {
302
- allow.push('zalo-mod');
302
+ allow.push('zalo-mod', 'zalouser');
303
303
  entries['zalo-mod'] = {
304
304
  enabled: true,
305
305
  config: {
@@ -311,12 +311,11 @@
311
311
  spamWindowSeconds: 300,
312
312
  },
313
313
  };
314
+ entries.zalouser = { enabled: true };
314
315
  }
315
316
 
316
317
  const plugins = { entries };
317
- if (allow.length > 0) {
318
- plugins.allow = allow;
319
- }
318
+ plugins.allow = allow;
320
319
 
321
320
  return { plugins };
322
321
  }
@@ -152,6 +152,7 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
152
152
  selectedModel,
153
153
  agentId,
154
154
  allSkills = [],
155
+ dockerfilePlugins = [],
155
156
  dockerfileSkillInstallMode = 'none',
156
157
  runtimeCommandParts = [],
157
158
  volumeMount = '../..:/root/project',
@@ -185,16 +186,59 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
185
186
  const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
186
187
  ? `\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`
187
188
  : '';
189
+ const pluginLines = dockerfilePlugins.length > 0
190
+ ? `\n# Install plugins (ClawHub)\n${dockerfilePlugins.map((p) => `RUN openclaw plugins install ${p} || echo "Warning: Failed to install plugin ${p}"`).join('\n')}\n`
191
+ : '';
188
192
  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);}"`;
189
193
 
190
- // Dynamic runtime configuration injection for container internal IPs
191
- 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));}`;
192
- const setupInternalIpB64 = encodeBase64Utf8(setupInternalIpScript);
194
+ // Dynamic runtime configuration: backup config before any first-run install, restore after.
195
+ // Missing plugin install may touch openclaw.json, so preserve critical fields.
196
+ 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);}`;
197
+ const backupConfigB64 = encodeBase64Utf8(backupConfigScript);
198
+
199
+ 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);}`;
200
+ const restoreConfigB64 = encodeBase64Utf8(restoreConfigScript);
193
201
 
194
- const runtimeParts = runtimeCommandParts.filter(Boolean);
195
- runtimeParts.unshift('export OPENCLAW_HOME="$PWD/.openclaw"');
196
- runtimeParts.unshift('export OPENCLAW_STATE_DIR="$PWD/.openclaw"');
197
- runtimeParts.unshift(`node -e 'eval(Buffer.from("${setupInternalIpB64}","base64").toString())'`);
202
+ const runtimeParts = runtimeCommandParts.filter(Boolean);
203
+ const runtimePrelude = [
204
+ 'export OPENCLAW_HOME="${OPENCLAW_HOME:-$PWD/.openclaw}"',
205
+ 'export OPENCLAW_STATE_DIR="${OPENCLAW_STATE_DIR:-$OPENCLAW_HOME}"',
206
+ 'mkdir -p "$OPENCLAW_HOME" "$OPENCLAW_STATE_DIR"',
207
+ 'if [ "$OPENCLAW_STATE_DIR" != "$OPENCLAW_HOME" ]; then',
208
+ ' for path in "$OPENCLAW_HOME"/*; do',
209
+ ' [ -e "$path" ] || continue',
210
+ ' name="$(basename "$path")"',
211
+ ' [ "$name" = "plugin-runtime-deps" ] && continue',
212
+ ' [ "$name" = "logs" ] && continue',
213
+ ' [ -e "$OPENCLAW_STATE_DIR/$name" ] || ln -s "$path" "$OPENCLAW_STATE_DIR/$name"',
214
+ ' done',
215
+ 'fi',
216
+ 'ensure_plugin() {',
217
+ ' id="$1"',
218
+ ' spec="$2"',
219
+ ' if [ -d "$OPENCLAW_HOME/extensions/$id" ]; then',
220
+ ' echo "[entrypoint] plugin $id already installed"',
221
+ ' return 0',
222
+ ' fi',
223
+ ' echo "[entrypoint] plugin $id missing; installing $spec"',
224
+ ' openclaw plugins install "$spec" 2>/dev/null || echo "[entrypoint] warning: failed to install plugin $spec"',
225
+ '}',
226
+ 'ensure_skill() {',
227
+ ' id="$1"',
228
+ ' if find "$OPENCLAW_HOME" -maxdepth 4 -type d -path "*/skills/$id" -print -quit 2>/dev/null | grep -q .; then',
229
+ ' echo "[entrypoint] skill $id already installed"',
230
+ ' return 0',
231
+ ' fi',
232
+ ' echo "[entrypoint] skill $id missing; installing"',
233
+ ' openclaw skills install "$id" 2>/dev/null || echo "[entrypoint] warning: failed to install skill $id"',
234
+ '}',
235
+ 'echo "[entrypoint] ensuring runtime assets, then starting gateway"',
236
+ ];
237
+ runtimeParts.unshift(...runtimePrelude);
238
+ // Backup config BEFORE plugin installs (runtimeCommandParts may contain plugin install commands)
239
+ runtimeParts.unshift(`node -e 'eval(Buffer.from("${backupConfigB64}","base64").toString())'`);
240
+ // Restore config AFTER plugin installs (which may clobber openclaw.json)
241
+ runtimeParts.push(`node -e 'eval(Buffer.from("${restoreConfigB64}","base64").toString())'`);
198
242
  if (hasBrowser) {
199
243
  runtimeParts.push('socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &');
200
244
  runtimeParts.push('Xvfb :99 -screen 0 1280x720x24 > /dev/null 2>&1 & DISPLAY=:99 openclaw gateway run');
@@ -209,7 +253,7 @@ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /v
209
253
  ${browserInstallLines}
210
254
  ARG OPENCLAW_VER="${openClawNpmSpec}"
211
255
  ARG CACHE_BUST=""
212
- RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}
256
+ RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}${pluginLines}
213
257
  ${patchLine}
214
258
  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
215
259
  WORKDIR /root/project
@@ -225,7 +269,7 @@ CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
225
269
  const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64);
226
270
  const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
227
271
 
228
- const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/root/project/.openclaw\n';
272
+ const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/var/lib/openclaw-state\n';
229
273
 
230
274
  let compose;
231
275
  if (isMultiBot) {
@@ -418,7 +462,19 @@ ${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''}
418
462
  - "18791:18791"`;
419
463
  }
420
464
 
421
- return {
465
+ compose = compose.replaceAll(
466
+ ` - ${volumeMount}`,
467
+ ` - ${volumeMount}\n - openclaw-state:/var/lib/openclaw-state`
468
+ );
469
+ if (compose.includes('\nvolumes:\n')) {
470
+ if (!compose.includes(' openclaw-state:')) {
471
+ compose = compose.replace('\nvolumes:\n', '\nvolumes:\n openclaw-state:\n');
472
+ }
473
+ } else {
474
+ compose += '\n\nvolumes:\n openclaw-state:';
475
+ }
476
+
477
+ return {
422
478
  dockerfile,
423
479
  compose,
424
480
  syncScript,
@@ -16,21 +16,34 @@ const workspaceRoot = /** @type {OpenClawWorkspaceRoot} */ (
16
16
  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." : ''}`;
17
17
  }
18
18
 
19
+ function buildZaloSoulSection(isVi, botName) {
20
+ const name = botName || 'Bot';
21
+ if (isVi) {
22
+ 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.`;
23
+ }
24
+ 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.`;
25
+ }
26
+
19
27
  function buildSoulDoc(options = {}) {
20
- const { isVi = true, persona = '', variant = 'wizard' } = options;
28
+ const { isVi = true, persona = '', variant = 'wizard', hasZaloMod = false, botName = 'Bot' } = options;
29
+ let doc;
21
30
  if (variant === 'cli-simple') {
22
- return isVi
31
+ doc = isVi
23
32
  ? `# 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`
24
33
  : `# Soul\n\n${persona || 'Friendly, clear, and outcome-focused.'}\n`;
25
- }
26
- if (variant === 'cli-rich') {
27
- return isVi
34
+ } else if (variant === 'cli-rich') {
35
+ doc = isVi
28
36
  ? `# 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}` : ''}`
29
37
  : `# 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}` : ''}`;
38
+ } else {
39
+ doc = isVi
40
+ ? `# 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}` : ''}`
41
+ : `# 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}` : ''}`;
30
42
  }
31
- return isVi
32
- ? `# 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}` : ''}`
33
- : `# 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}` : ''}`;
43
+ if (hasZaloMod) {
44
+ doc += buildZaloSoulSection(isVi, botName);
45
+ }
46
+ return doc;
34
47
  }
35
48
 
36
49
  function buildTeamDoc(options = {}) {
@@ -305,6 +318,7 @@ const CDP_URL = 'http://127.0.0.1:9222';
305
318
  agentWorkspaceDir = 'workspace',
306
319
  hasBrowser = false,
307
320
  hasScheduler = false,
321
+ hasZaloMod = false,
308
322
  browserDocVariant = '',
309
323
  } = options;
310
324
 
@@ -332,19 +346,25 @@ const CDP_URL = 'http://127.0.0.1:9222';
332
346
  : `\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.`)
333
347
  : '';
334
348
 
349
+ const zaloModSection = hasZaloMod
350
+ ? (isVi
351
+ ? `\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`
352
+ : `\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`)
353
+ : '';
354
+
335
355
  const dmOverride = isVi
336
356
  ? `\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.`
337
357
  : `\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.`;
338
358
 
339
359
  if (variant === 'relay') {
340
360
  return isVi
341
- ? `# 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`
342
- : `# 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`;
361
+ ? `# 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`
362
+ : `# 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`;
343
363
  }
344
364
 
345
365
  return isVi
346
- ? `# 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`
347
- : `# 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`;
366
+ ? `# 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`
367
+ : `# 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`;
348
368
  }
349
369
  function buildTeamsDoc(options = {}) {
350
370
  const {
@@ -418,20 +438,21 @@ const CDP_URL = 'http://127.0.0.1:9222';
418
438
  teamRosterFormatted = '',
419
439
  emoji = '',
420
440
  hasScheduler = false,
441
+ hasZaloMod = false,
421
442
  } = opts;
422
443
 
423
444
  const isMultiBot = variant === 'relay';
424
445
 
425
446
  const files = {
426
447
  'IDENTITY.md': buildIdentityDoc({ isVi, name: botName, desc: botDesc, emoji }),
427
- 'SOUL.md': buildSoulDoc({ isVi, persona, variant: soulVariant }),
448
+ 'SOUL.md': buildSoulDoc({ isVi, persona, variant: soulVariant, hasZaloMod, botName }),
428
449
  'AGENTS.md': buildAgentsDoc({
429
450
  isVi, botName, botDesc, ownAliases, otherAgents, workspacePath,
430
451
  variant, includeSecurity: true, replyToDirectMessages: true,
431
452
  }),
432
453
  'USER.md': buildUserDoc({ isVi, userInfo, variant: userVariant || (isMultiBot ? 'cli-multi' : 'wizard') }),
433
454
  'TOOLS.md': buildToolsDoc({
434
- isVi, skillListStr, workspacePath, variant, agentWorkspaceDir, hasBrowser, hasScheduler, browserDocVariant,
455
+ isVi, skillListStr, workspacePath, variant, agentWorkspaceDir, hasBrowser, hasScheduler, hasZaloMod, browserDocVariant,
435
456
  }),
436
457
  'MEMORY.md': buildMemoryDoc({ isVi, variant: memoryVariant }),
437
458
  'HEARTBEAT.md': buildHeartbeatDoc({ isVi }),