create-openclaw-bot 5.13.4 → 5.13.6

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
@@ -7,7 +7,7 @@
7
7
  *Run one command → open the dashboard → your bot is live. Windows · macOS · Linux · VPS — Docker-powered, auto-installed for you.*
8
8
 
9
9
  <p align="center">
10
- <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.13.4-0EA5E9?style=for-the-badge" alt="Version 5.13.4" /></a>
10
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.13.6-0EA5E9?style=for-the-badge" alt="Version 5.13.6" /></a>
11
11
  <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>
12
12
  <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>
13
13
  <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>
@@ -37,7 +37,7 @@
37
37
 
38
38
  ---
39
39
 
40
- ## 🆕 What's New in v5.13.4
40
+ ## 🆕 What's New in v5.13.6
41
41
 
42
42
  - ⚙️ **New Settings page**: pick your theme (light/dark toggle), language (VI/EN) and timezone right from the dashboard — available in both the sidebar and the mobile nav bar.
43
43
  - 🕒 **Schedules & cron run in the correct local time**: newly created bots carry an explicit timezone, so reminders/cron no longer drift a day off around midnight (a UTC-vs-local bug that could schedule into the past). Scheduling guidance was hardened end-to-end (local time + timezone, explicit delivery target, raw group id).
package/README.vi.md CHANGED
@@ -7,7 +7,7 @@
7
7
  *Chạy một lệnh → mở dashboard → bot lên sóng. Windows · macOS · Linux · VPS — chạy trên Docker, tự cài giúp bạn.*
8
8
 
9
9
  <p align="center">
10
- <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.13.4-0EA5E9?style=for-the-badge" alt="Version 5.13.4" /></a>
10
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.13.6-0EA5E9?style=for-the-badge" alt="Version 5.13.6" /></a>
11
11
  <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>
12
12
  <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>
13
13
  <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>
@@ -37,7 +37,7 @@
37
37
 
38
38
  ---
39
39
 
40
- ## 🆕 Có gì mới trong v5.13.4
40
+ ## 🆕 Có gì mới trong v5.13.6
41
41
 
42
42
  - ⚙️ **Trang Cài đặt mới**: chọn giao diện (nút gạt Sáng/Tối), ngôn ngữ (VI/EN) và múi giờ ngay trong dashboard — có ở cả sidebar lẫn thanh điều hướng mobile.
43
43
  - 🕒 **Lịch & cron chạy đúng giờ địa phương**: bot tạo mới nhận múi giờ rõ ràng, nên nhắc hẹn/cron không còn lệch 1 ngày quanh nửa đêm (lỗi UTC-vs-local có thể đặt lịch vào quá khứ). Đã siết hướng dẫn lập lịch toàn diện (giờ local + múi giờ, nơi gửi rõ ràng, ID nhóm dạng thô).
@@ -1754,10 +1754,8 @@ async function startZaloConnectLogin(projectDir, accountId = 'default') {
1754
1754
  if (!gatewayReady) {
1755
1755
  const check = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', '[ -d "${OPENCLAW_HOME:-/home/node/project/.openclaw}/extensions/zalo-connect" ] && echo OK || echo MISSING'], { cwd: projectDir, shell: false }).catch(() => ({ stdout: 'ERR' }));
1756
1756
  if (String(check.stdout || '').trim() === 'MISSING') {
1757
- sendLog(`[zalo-connect] Plugin missing — installing pinned ${ZALO_CONNECT_PLUGIN_SPEC}...`);
1758
- const repo = String(ZALO_CONNECT_PLUGIN_SPEC).split('#')[0];
1759
- const ref = String(ZALO_CONNECT_PLUGIN_SPEC).split('#')[1] || ZALO_CONNECT_VERSION;
1760
- const installCmd = `tmp=/tmp/openclaw-plugin-zalo-connect-${ref}; rm -rf "$tmp"; git clone --depth 1 --branch "${ref}" "${repo}" "$tmp" && rm -rf "$tmp/.git" && cd /home/node/project && openclaw plugins install "$tmp" 2>&1`;
1757
+ sendLog(`[zalo-connect] Plugin missing — installing ${ZALO_CONNECT_PLUGIN_SPEC}...`);
1758
+ const installCmd = `cd /home/node/project && openclaw plugins install ${ZALO_CONNECT_PLUGIN_SPEC} --force --acknowledge-clawhub-risk 2>&1`;
1761
1759
  const inst = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', installCmd], { cwd: projectDir, shell: false });
1762
1760
  const instOut = `${inst.stdout}\n${inst.stderr}`;
1763
1761
  for (const line of instOut.split(/\r?\n/).filter(Boolean)) sendLog(`[zalo-connect] ${line}`);
@@ -3073,7 +3071,12 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
3073
3071
 
3074
3072
  const k = `${kind}:${id}`;
3075
3073
 
3076
-
3074
+ // zalo-connect is required by any Zalo bot — refuse to disable it while a Zalo binding exists
3075
+ // (the UI also locks the toggle; this is the backend guard).
3076
+ if (kind === 'plugin' && (id === 'zalo-connect' || id === 'openclaw-zalo-connect') && !enabled) {
3077
+ const hasZaloBot = (cfg.bindings || []).some((b) => b?.match?.channel === 'zalo-connect');
3078
+ if (hasZaloBot) throw httpError(400, 'Không thể tắt Zalo Connect khi vẫn còn bot Zalo.');
3079
+ }
3077
3080
 
3078
3081
  if (kind === 'skill' && id === 'cron') {
3079
3082
  cfg.skills = cfg.skills || { entries: {} };
@@ -3338,6 +3341,39 @@ async function installFeature(projectDir, agentId, kind, id) {
3338
3341
  }
3339
3342
 
3340
3343
  if (kind === 'plugin') {
3344
+ // zalo-connect ships on ClawHub (package `openclaw-zalo-connect`) — install/update via
3345
+ // clawhub:latest like other plugins, so the dashboard "Update" button always fetches the newest
3346
+ // published version (no tag pin to bump each release).
3347
+ if (id === 'zalo-connect' || id === 'openclaw-zalo-connect') {
3348
+ let composeDir = null;
3349
+ if (existsSync(join(projectDir, 'docker-compose.yml'))) composeDir = projectDir;
3350
+ else if (existsSync(join(projectDir, 'docker', 'openclaw', 'docker-compose.yml'))) composeDir = join(projectDir, 'docker', 'openclaw');
3351
+ if (composeDir) {
3352
+ const botContainer = getBotContainerName(projectDir);
3353
+ sendLog(`[zalo-connect] Installing/updating ${ZALO_CONNECT_PLUGIN_SPEC} inside ${botContainer}...`);
3354
+ const cmd = `cd /home/node/project && openclaw plugins install ${ZALO_CONNECT_PLUGIN_SPEC} --force --acknowledge-clawhub-risk 2>&1`;
3355
+ const out = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cmd], { cwd: projectDir, shell: false });
3356
+ if (out) for (const line of `${out.stdout}\n${out.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(`[zalo-connect] ${line}`);
3357
+ const okDir = existsSync(join(projectDir, '.openclaw', 'extensions', 'zalo-connect'));
3358
+ if (out.code !== 0 && !okDir) throw new Error(out.stderr || out.stdout || 'Failed to install zalo-connect.');
3359
+ }
3360
+ const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
3361
+ if (existsSync(cfgPath)) {
3362
+ const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
3363
+ cfg.plugins = cfg.plugins || { entries: {}, allow: [] };
3364
+ cfg.plugins.entries = cfg.plugins.entries || {};
3365
+ cfg.plugins.entries['zalo-connect'] = cfg.plugins.entries['zalo-connect'] || {};
3366
+ cfg.plugins.entries['zalo-connect'].enabled = true;
3367
+ cfg.plugins.allow = cfg.plugins.allow || [];
3368
+ if (!cfg.plugins.allow.includes('zalo-connect')) cfg.plugins.allow.push('zalo-connect');
3369
+ await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
3370
+ }
3371
+ if (composeDir) {
3372
+ sendLog('[zalo-connect] Restarting container to apply...');
3373
+ await run('docker', ['restart', getBotContainerName(projectDir)], { shell: false }).catch(() => {});
3374
+ }
3375
+ return { ok: true, id: 'zalo-connect' };
3376
+ }
3341
3377
  const installSpec = pluginInstallSpec(id);
3342
3378
  const installArgs = ['plugins', 'install', installSpec, '--force'];
3343
3379
  if (installSpec.startsWith('clawhub:')) installArgs.push('--acknowledge-clawhub-risk');
@@ -3640,6 +3676,7 @@ async function getFeatureFlags(projectDir, agentId = '') {
3640
3676
  poster: ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
3641
3677
  fbMessenger: ['openclaw-fb-messenger', 'fb-messenger'],
3642
3678
  tencentMemory: ['memory-tencentdb'],
3679
+ zaloConnect: ['zalo-connect', 'openclaw-zalo-connect'],
3643
3680
  };
3644
3681
  const flags = {
3645
3682
  'skill:browser': browserOn,
@@ -3653,6 +3690,7 @@ async function getFeatureFlags(projectDir, agentId = '') {
3653
3690
  'plugin:openclaw-n8n-facebook-poster': isEnabled(aliases.poster),
3654
3691
  'plugin:openclaw-fb-messenger': isEnabled(aliases.fbMessenger),
3655
3692
  'plugin:memory-tencentdb': isEnabled(aliases.tencentMemory),
3693
+ 'plugin:zalo-connect': isEnabled(aliases.zaloConnect),
3656
3694
  };
3657
3695
  const extensionsDir = join(projectDir || '', '.openclaw', 'extensions');
3658
3696
  const extensionDirExists = (aliases = []) =>
@@ -3671,6 +3709,7 @@ async function getFeatureFlags(projectDir, agentId = '') {
3671
3709
  'plugin:openclaw-fb-messenger': extensionDirExists(aliases.fbMessenger)
3672
3710
  || aliases.fbMessenger.some((a) => installedKeys.has(a) || Array.from(installedSpecs).some((spec) => spec.includes(a))),
3673
3711
  'plugin:memory-tencentdb': isActuallyInstalled(aliases.tencentMemory),
3712
+ 'plugin:zalo-connect': isActuallyInstalled(aliases.zaloConnect),
3674
3713
  };
3675
3714
  const versions = {
3676
3715
  'skill:image-gen': await getInstalledSkillVersion(projectDir, aid, 'infographic-generator', cfg),
@@ -3681,6 +3720,7 @@ async function getFeatureFlags(projectDir, agentId = '') {
3681
3720
  'plugin:openclaw-n8n-facebook-poster': await getInstalledPluginVersion(projectDir, aliases.poster),
3682
3721
  'plugin:openclaw-fb-messenger': await getInstalledPluginVersion(projectDir, aliases.fbMessenger),
3683
3722
  'plugin:memory-tencentdb': await getInstalledPluginVersion(projectDir, aliases.tencentMemory),
3723
+ 'plugin:zalo-connect': await getInstalledPluginVersion(projectDir, aliases.zaloConnect),
3684
3724
  };
3685
3725
  // Docker: fill any plugin version still empty from the container's extensions volume
3686
3726
  // (e.g. bundled/clawhub plugins like zalo-mod that aren't on the host). One docker exec.
@@ -3693,9 +3733,14 @@ async function getFeatureFlags(projectDir, agentId = '') {
3693
3733
  fillVer('plugin:openclaw-n8n-facebook-poster', aliases.poster);
3694
3734
  fillVer('plugin:openclaw-fb-messenger', aliases.fbMessenger);
3695
3735
  fillVer('plugin:memory-tencentdb', aliases.tencentMemory);
3736
+ fillVer('plugin:zalo-connect', aliases.zaloConnect);
3696
3737
  }
3697
3738
  const zaloBackend = fresh.channels?.['zalo-connect']?.enabled ? 'zalo-connect' : '';
3698
- return { flags, installed, versions, zaloBackend };
3739
+ // zalo-connect is REQUIRED by any Zalo bot → lock its toggle (checked, can't disable)
3740
+ // whenever a Zalo binding exists.
3741
+ const hasZaloBot = (fresh.bindings || []).some((b) => b?.match?.channel === 'zalo-connect');
3742
+ const locked = { 'plugin:zalo-connect': hasZaloBot };
3743
+ return { flags, installed, versions, zaloBackend, locked };
3699
3744
  }
3700
3745
 
3701
3746
  async function serveStatic(req, res) {
@@ -238,6 +238,8 @@
238
238
 
239
239
  // ── skills ───────────────────────────────────────────────────────────────
240
240
  const skillEntries = buildSkillsEntries(skills, selectedSkills);
241
+ // NOTE: the "zalo-actions" skill was removed — its guidance now lives in the `zalo-connect`
242
+ // tool description itself (fork ≥ v3.0.2), so no per-bot skill entry is needed anymore.
241
243
  if (Object.keys(skillEntries).length > 0) {
242
244
  cfg.skills = { entries: skillEntries };
243
245
  }
@@ -41,11 +41,13 @@
41
41
  const TELEGRAM_SETUP_GUIDE_FILENAME = 'TELEGRAM-GROUP-SETUP.md';
42
42
 
43
43
  // ── OpenClaw Zalo Connect — single source of truth ──────────────────────────
44
- // Pin the maintained fork tag so setup never falls back to upstream/ClawHub.
44
+ // Installed from ClawHub (owned package `openclaw-zalo-connect`) at LATEST, like zalo-mod, so the
45
+ // dashboard "Update" button + first-boot always fetch the newest published version — no tag pin to
46
+ // bump each release. (Was a pinned git tag; switched once the fork was published to ClawHub.)
45
47
  const ZALO_CHANNEL_ID = 'zalo-connect';
46
48
  const ZALO_PLUGIN_ID = 'zalo-connect';
47
- const ZALO_CONNECT_VERSION = '3.0.1';
48
- const ZALO_CONNECT_PLUGIN_SPEC = 'https://github.com/tuanminhhole/openclaw-zalo-connect.git#v3.0.1';
49
+ const ZALO_CONNECT_VERSION = 'latest';
50
+ const ZALO_CONNECT_PLUGIN_SPEC = 'clawhub:openclaw-zalo-connect';
49
51
 
50
52
  function buildRelayPluginInstallCommand(prefix = 'openclaw') {
51
53
  return `if [ ! -d "$OPENCLAW_STATE_DIR/extensions/${TELEGRAM_RELAY_PLUGIN_ID}" ]; then ${prefix} plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC} 2>/dev/null || true; fi`;
@@ -316,11 +316,11 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
316
316
  runtimeParts.push(`node - <<'NODE'\n${restoreConfigScript}\nNODE`);
317
317
  runtimeParts.push(`node - <<'NODE'\n${securityCompatScript}\nNODE`);
318
318
  if (zaloBackend === 'zalo-connect') {
319
- // Pinned ZaloConnect install (never `latest` — plan §4.1). ensure_plugin skips the
320
- // install when extensions/zalo-connect already exists, so restarts never re-download
321
- // and a reconnect never reinstalls. No dist patching, no mentions.js, no watchdog
322
- // hacks: sticker/mention/reaction are native ZaloConnect actions.
323
- const zaloConnectSpec = common.ZALO_CONNECT_PLUGIN_SPEC || 'https://github.com/tuanminhhole/openclaw-zalo-connect.git#v3.0.1';
319
+ // ZaloConnect install from ClawHub (latest). ensure_plugin skips when extensions/zalo-connect
320
+ // already exists, so restarts never re-download; the "Update" button in the dashboard fetches
321
+ // newer versions. No dist patching, no mentions.js, no watchdog: sticker/mention/reaction are
322
+ // native ZaloConnect actions.
323
+ const zaloConnectSpec = common.ZALO_CONNECT_PLUGIN_SPEC || 'clawhub:openclaw-zalo-connect';
324
324
  runtimeParts.push(`ensure_plugin zalo-connect "${zaloConnectSpec}"`);
325
325
  }
326
326
  runtimeParts.push('openclaw gateway run');
@@ -567,15 +567,26 @@ Truyền tham số \`job\` (object) gồm:
567
567
  - **Một lần:** \`kind: "at"\`, \`at\`: giờ địa phương + \`tz\` (khuyên dùng), ví dụ \`"at": "2026-07-22T00:30:00", "tz": "${tz}"\`. Cũng có thể dùng \`+duration\` (vd \`"+30m"\`). **KHÔNG tự quy đổi & ghi \`...Z\` (UTC)** — dễ sai ngày.
568
568
  - 🕒 **XÁC ĐỊNH ĐÚNG NGÀY:** "Current time" trong ngữ cảnh đã theo giờ địa phương (\`${tz}\`). Cứ dùng NGÀY đang thấy để suy "hôm nay / tối nay / sáng mai". (Nếu vô tình thấy giờ dạng \`...Z\` UTC thì phải đổi sang \`${tz}\` trước khi suy ngày.)
569
569
  - ✅ Nếu tool báo \`schedule.at is in the past\` → tính sai ngày (thường thiếu 1 ngày). Cộng thêm 1 ngày rồi thử lại; đừng báo lỗi khi chưa tự sửa.
570
- - **\`payload\`**:
571
- - \`kind\`: \`"agentTurn"\`.
572
- - \`message\`: **NỘI DUNG Y NGUYÊN bot sẽ nói ra** (câu khẳng định), vd \`"Chúc cả nhà ngủ ngon 🌙"\`. ⚠️ KHÔNG viết kiểu mệnh lệnh ("gửi lời chúc tới group ..."). Khi job chạy, \`delivery.announce\` sẽ TỰ gửi nội dung này tới \`to\`. **TUYỆT ĐỐI KHÔNG để job tự gọi tool gửi tin (message/send) announce đã lo, tự gọi sẽ gây lỗi \`Unknown target\` "Cron failed".**
573
- - **\`delivery\`** — 🚨 **BẮT BUỘC điền ĐỦ cả \`channel\` \`to\`.** Nếu thiếu (hoặc để \`channel: "last"\`), job vẫn tạo được nhưng khi tới giờ sẽ **FAIL lúc giao** với lỗi \`Refusing implicit isolated cron delivery\` tin KHÔNG tới nhóm:
574
- - \`mode\`: \`"announce"\`.
570
+ - **\`payload\`**: \`{ "kind": "agentTurn", "message": "<chỉ dẫn/nội dung>" }\`.
571
+ - \`message\` là chỉ dẫn để agent **SINH RA nội dung sẽ gửi** — có thể là câu cố định (\`"Chúc cả nhà ngủ ngon 🌙"\`) hoặc yêu cầu tạo mới mỗi lần (\`"Viết một lời chúc buổi sáng mới, tích cực, không lặp lại"\`). **Phần trả lời của agent CHÍNH LÀ tin được gửi.**
572
+ - **KHÔNG** viết kiểu mệnh lệnh gửi ("dùng tool ... gửi tới group ...", "gửi tin tới groupId ..."). **KHÔNG** để job tự gọi tool gửi tin (message/send/zalo-connect). Việc gửi là do \`delivery.announce\` lo tự gọi sẽ lỗi \`Unknown target\` / "Cron failed".
573
+ - **\`delivery\`** — 🚨 **BẮT BUỘC** cho tin gửi tới người/nhóm. Isolated job reset routing mỗi lần chạy, nên phải ghi \`channel\` + \`to\`; thiếu lỗi \`Refusing implicit isolated cron delivery\` (không gửi được):
574
+ - \`mode\`: \`"announce"\`. ⛔ **KHÔNG dùng \`"none"\`** — job vẫn chạy "ok" nhưng KHÔNG gửi gì (đây là bẫy thường gặp).
575
575
  - \`channel\`: \`"${zaloDeliveryChannel}"\` (KHÔNG để \`"last"\`).
576
- - \`to\`: nơi nhận = **ID THÔ (threadId), KHÔNG thêm tiền tố gì**. Group Zalo → dùng thẳng groupId (vd \`"1925989252066183028"\`). DM cá nhân dùng userId. ⚠️ **TUYỆT ĐỐI KHÔNG thêm \`g:\`** nếu thêm, zalo-connect báo \`Unknown target "g:..."\` và tin KHÔNG gửi được.
576
+ - \`to\`: **ID THÔ, KHÔNG tiền tố**. Group Zalo → groupId thẳng (vd \`"1925989252066183028"\`); DM → userId. ⚠️ **KHÔNG thêm \`g:\`** (sẽ lỗi \`Unknown target "g:..."\`).
577
577
 
578
- ### 📦 Ví dụ job ĐẦY ĐỦ (một lần, giờ địa phương) tạo RIÊNG 1 job cho MỖI group:
578
+ ### 📦 Ví dụ A LẶP LẠI (chúc buổi sáng 9:05 mỗi ngày cho 1 nhóm):
579
+ \`\`\`json
580
+ {
581
+ "sessionTarget": "isolated",
582
+ "wakeMode": "now",
583
+ "schedule": { "kind": "cron", "expr": "5 9 * * *", "tz": "${tz}" },
584
+ "payload": { "kind": "agentTurn", "message": "Viết MỘT lời chúc buổi sáng MỚI mỗi ngày, tích cực, ấm áp, lịch sự, không lặp lại. Chỉ trả về đúng nội dung lời chúc." },
585
+ "delivery": { "mode": "announce", "channel": "${zaloDeliveryChannel}", "to": "1228185345777623323" }
586
+ }
587
+ \`\`\`
588
+
589
+ ### 📦 Ví dụ B — MỘT LẦN (giờ địa phương) — mỗi group 1 job riêng:
579
590
  \`\`\`json
580
591
  {
581
592
  "sessionTarget": "isolated",
@@ -597,6 +608,54 @@ Truyền tham số \`job\` (object) gồm:
597
608
  \`openclaw cron run <job_id> --wait\` (hoặc gọi tool \`cron\` với action \`run\` kèm \`id\`).`;
598
609
  }
599
610
 
611
+ // DEPRECATED (unused): guidance moved into the `zalo-connect` tool description (fork ≥ v3.0.2).
612
+ // Kept only for reference; no longer wired into buildWorkspaceFileMap.
613
+ function buildZaloActionsSkillMd(isVi = true, zaloDeliveryChannel = 'zalo-connect') {
614
+ return `---
615
+ name: zalo-actions
616
+ description: Hành động Zalo phong phú qua tool ${zaloDeliveryChannel} — sticker, thả cảm xúc, bình chọn, ghi chú, nhắc hẹn, media, quản trị nhóm.
617
+ ---
618
+
619
+ # 💬 Hành động Zalo (tool: \`${zaloDeliveryChannel}\`)
620
+
621
+ Ngoài gửi text, tool \`${zaloDeliveryChannel}\` có ~149 action. Gọi bằng: tool \`${zaloDeliveryChannel}\` với \`{ "action": "<tên>", ... }\`. Cứ chủ động dùng khi phù hợp (user xin sticker, cần bình chọn, nhắc hẹn, ghim ghi chú...).
622
+
623
+ ## Quy tắc chung
624
+ - **\`threadId\`** = nơi gửi. Trong GROUP hiện tại → dùng **groupId THÔ** (KHÔNG tiền tố \`g:\`) kèm **\`isGroup: true\`**. DM cá nhân → \`threadId\` = userId, \`isGroup: false\`.
625
+ - Làm xong thì trả lời ngắn gọn cho user, KHÔNG dán JSON/kết quả thô.
626
+
627
+ ## 🎨 Sticker — \`send-sticker\`
628
+ - Dễ nhất (theo từ khoá, tool tự tìm & gửi): \`{ "action": "send-sticker", "threadId": "<groupId>", "isGroup": true, "keyword": "chào buổi sáng" }\`.
629
+ - Chỉ định cụ thể: thêm \`"stickerId"\` + \`"stickerCateId"\` (thay cho \`keyword\`).
630
+
631
+ ## 😀 Thả cảm xúc — \`add-reaction\`
632
+ - \`{ "action": "add-reaction", "msgId": "<id tin nhắn>", "icon": "heart" }\` — \`icon\`: heart / like / haha / wow / cry / angry.
633
+
634
+ ## 📊 Bình chọn — \`create-poll\`
635
+ - \`{ "action": "create-poll", "threadId": "<groupId>", "isGroup": true, "title": "Câu hỏi?", "options": ["A","B"], "allowMultiChoices": false }\`.
636
+
637
+ ## 📌 Ghi chú ghim — \`create-note\`
638
+ - \`{ "action": "create-note", "threadId": "<groupId>", "isGroup": true, "title": "Nội dung ghi chú" }\`.
639
+
640
+ ## ⏰ Nhắc hẹn nhóm — \`create-reminder\`
641
+ - \`{ "action": "create-reminder", "threadId": "<groupId>", "isGroup": true, "title": "...", "startTime": <epoch ms>, "repeat": 0 }\` (repeat: 0 không lặp / 1 ngày / 2 tuần / 3 tháng). Lịch định kỳ kiểu cron → dùng skill \`cronjob\`.
642
+
643
+ ## 🖼️ Media
644
+ - Ảnh: \`send-image\` \`{threadId,isGroup,url}\` · Video: \`send-video\` \`{threadId,isGroup,url,thumbnailUrl?}\` · Voice: \`send-voice\` \`{threadId,isGroup,voiceUrl}\` · File: \`send-file\` \`{threadId,isGroup,filePath}\` · Link preview: \`send-link\` \`{threadId,isGroup,url}\`.
645
+
646
+ ## ↪️ Chuyển tiếp / thu hồi / xoá
647
+ - \`forward-message\` \`{msgId, threadIds:["<đích>"]}\` · \`delete-message\` \`{msgId, threadId, onlyMe?}\`.
648
+ - **Thu hồi tin của CHÍNH bot** (\`undo-message\`): khi user bảo "thu hồi/gỡ tin vừa nãy", gọi \`undo-message\` \`{threadId:"<groupId/userId hiện tại>"}\` — **KHÔNG cần msgId**, hệ thống tự lấy tin gần nhất bot đã gửi trong luồng đó. ⏳ Chỉ thu hồi được tin bot gửi **trong 5 phút** gần nhất; tin cũ hơn (hoặc tin của người khác) thì báo lại là không gỡ được. Muốn nhắm tin cụ thể thì truyền thêm \`{msgId}\`.
649
+
650
+ ## 🛠️ Quản trị nhóm (bot phải là admin)
651
+ - \`add-group-admin\` / \`remove-group-admin\` \`{groupId,userId}\` · \`rename-group\` \`{groupId,groupName}\` · \`change-group-owner\` \`{groupId,userId}\` · \`invite-to-groups\` \`{userId, groupIds:[...]}\` · \`update-group-settings\` \`{groupId, groupSettings:{...}}\` · link nhóm: \`enable-group-link\`/\`disable-group-link\`/\`get-group-link\` \`{groupId}\`.
652
+
653
+ ## 📎 Hội thoại
654
+ - Ghim: \`pin-conversation\` \`{threadId}\` · Tắt báo: \`mute-conversation\` \`{threadId, duration:-1}\` (giây, -1 = mãi) · Đang gõ "…": \`send-typing\` \`{threadId,isGroup}\`.
655
+
656
+ > Còn nhiều action khác (tìm bạn, QR, catalog/sản phẩm, auto-reply, quick message, poll nâng cao, mã hoá tin tự huỷ...). Xem mô tả từng \`action\` + tham số ngay trong schema của tool \`${zaloDeliveryChannel}\`.`;
657
+ }
658
+
600
659
  function buildInfographicGeneratorSkillMd(botName = 'Williams') {
601
660
  return '';
602
661
  }
@@ -1345,6 +1404,10 @@ Add whatever helps you do your job. This is your cheat sheet.
1345
1404
  files['skills/cronjob/SKILL.md'] = buildCronjobSkillMd(isVi, 'zalo-connect', userTimezone);
1346
1405
  }
1347
1406
 
1407
+ // NOTE: the old `zalo-actions` agent skill was removed — its guidance (Zalo conventions +
1408
+ // action recipes) now lives directly in the `zalo-connect` tool description (fork ≥ v3.0.2),
1409
+ // so every zalo-connect bot gets it automatically without a generated per-bot skill.
1410
+
1348
1411
  if (hasImageGen) {
1349
1412
  files['skills/infographic-generator/SKILL.md'] = buildInfographicGeneratorSkillMd(botName);
1350
1413
  files['skills/infographic-generator/image-generator.js'] = buildInfographicGeneratorJs();
package/dist/web/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const $ = (sel) => document.querySelector(sel);
2
- const state = { tab: 'dashboard', system: null, install: null, files: [], catalog: { skills: [], plugins: [] }, logs: [], zaloLoginOpen: false, zaloLoginLines: [], zaloQrDataUrl: '', lang: localStorage.getItem('openclaw-lang') || 'vi', theme: localStorage.getItem('openclaw-theme') || 'dark', tz: localStorage.getItem('openclaw-tz') || 'Asia/Ho_Chi_Minh', navCollapsed: localStorage.getItem('openclaw-nav')==='1', os: null, mode: null, donateOpen: false, botModalOpen: false, botEditId: '', installModalOpen: false, fbPluginModalOpen: false, installTab: 'docker', installDraft: null, pathModal: null, confirmModal: null, botChannel: 'telegram', botPane: 'list', activeBotId: '', selectedFile: '', botMessage: '', projectConnectMessage: '', pendingProjectDir: '', selectedProjectDir: '', featureFlags: {}, featureInstalled: {}, featureLoading: {}, zaloBackend: '', zaloHealth: null, openDirs: {} };
2
+ const state = { tab: 'dashboard', system: null, install: null, files: [], catalog: { skills: [], plugins: [] }, logs: [], zaloLoginOpen: false, zaloLoginLines: [], zaloQrDataUrl: '', lang: localStorage.getItem('openclaw-lang') || 'vi', theme: localStorage.getItem('openclaw-theme') || 'dark', tz: localStorage.getItem('openclaw-tz') || 'Asia/Ho_Chi_Minh', navCollapsed: localStorage.getItem('openclaw-nav')==='1', os: null, mode: null, donateOpen: false, botModalOpen: false, botEditId: '', installModalOpen: false, fbPluginModalOpen: false, installTab: 'docker', installDraft: null, pathModal: null, confirmModal: null, botChannel: 'telegram', botPane: 'list', activeBotId: '', selectedFile: '', botMessage: '', projectConnectMessage: '', pendingProjectDir: '', selectedProjectDir: '', featureFlags: {}, featureInstalled: {}, featureLoading: {}, featureLocked: {}, zaloBackend: '', zaloHealth: null, openDirs: {} };
3
3
  const SVG_CDN = 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons';
4
4
  const OS_OPTIONS = [
5
5
  { id: 'win', title: 'Windows', subtitle: 'Auto-detected desktop', icon: `${SVG_CDN}/windows/default.svg`, badge: 'Desktop' },
@@ -1064,6 +1064,7 @@ function botSkillsPanel() {
1064
1064
  { id: 'learning-memory', title: 'Siêu Trí Nhớ Dài Hạn (learning-memory)', desc: 'Tự động ghi nhớ bài học vào MEMORY.md, tự đóng gói và tiến hóa kỹ năng mới vào skills/' },
1065
1065
  ];
1066
1066
  const plugins = [
1067
+ { id: 'zalo-connect', title: 'OpenClaw Zalo Connect', desc: t('Kênh Zalo cá nhân (zca-js) — BẮT BUỘC cho bot Zalo. Tự cài khi tạo bot Zalo đầu tiên; bấm Cập nhật để lên bản mới nhất.', 'Personal Zalo channel (zca-js) — REQUIRED for Zalo bots. Auto-installed with your first Zalo bot; click Update for the latest version.'), channels: ['zalo-personal'] },
1067
1068
  { id: 'memory-tencentdb', title: 'TencentDB Agent Memory', desc: 'Bộ nhớ phân tầng L0–L3 + nén ngữ cảnh: nhớ tốt session dài, tiết kiệm ~61% token (local SQLite, không cần API)' },
1068
1069
  { id: 'openclaw-browser-automation', title: 'openclaw-browser-automation', desc: 'Smart Search + Browser (headless & Chrome thật)' },
1069
1070
  { id: 'openclaw-zalo-mod', title: 'openclaw-zalo-mod', desc: 'Zalo group helpers', channels: ['zalo-personal'], openWebPort: 18790, openWebPath: '/dashboard' },
@@ -1083,8 +1084,9 @@ function botSkillsPanel() {
1083
1084
  const row = (item, group) => {
1084
1085
  const key = `${group}:${item.id}`;
1085
1086
  const loading = !!state.featureLoading[key];
1087
+ const locked = !!state.featureLocked?.[key];
1086
1088
  const requiresInstall = group === 'plugin' || (group === 'skill' && (item.id === 'image-gen' || item.id === 'learning-memory'));
1087
- const isInstalled = !requiresInstall || !!state.featureInstalled?.[key];
1089
+ const isInstalled = !requiresInstall || !!state.featureInstalled?.[key] || locked;
1088
1090
 
1089
1091
  const version = requiresInstall && isInstalled ? (state.featureVersions?.[key] || '') : '';
1090
1092
  const versionBadge = version ? `<span class="plugin-version-badge" style="display:inline-block; font-size: 11px; background: rgba(66, 133, 244, 0.15); color: #4285F4; padding: 2px 6px; border-radius: 4px; font-weight: 600; border: 1px solid rgba(66,133,244,0.25); flex:0 0 auto;">v${escapeHtml(version)}</span>` : '';
@@ -1094,7 +1096,7 @@ function botSkillsPanel() {
1094
1096
  let toggleHtml = '';
1095
1097
  const secs = [];
1096
1098
  if (isInstalled) {
1097
- toggleHtml = `<label class="feature-switch"><input type="checkbox" data-feature-toggle="${key}" ${flags[key] ? 'checked' : ''} ${loading ? 'disabled' : ''}/><span></span></label>`;
1099
+ toggleHtml = `<label class="feature-switch" title="${locked ? t('Bắt buộc cho bot Zalo — không thể tắt','Required for Zalo bots — cannot be disabled') : ''}"${locked ? ' style="opacity:.4;pointer-events:none;cursor:not-allowed;"' : ''}><input type="checkbox" data-feature-toggle="${key}" ${(flags[key] || locked) ? 'checked' : ''} ${(loading || locked) ? 'disabled' : ''}/><span></span></label>`;
1098
1100
  if (item.openWebPort) {
1099
1101
  secs.push(`<a class="secondary icon-btn2" href="${sameHostUrl('', item.openWebPort)}${item.openWebPath || ''}" target="_blank" rel="noopener" title="${t('Mở dashboard của plugin','Open the plugin dashboard')}" style="padding: 4px 8px; font-size: 11px; height: 28px; border-width: 1px;">${actionIcon('link')}<span>${t('Mở web','Open')}</span></a>`);
1100
1102
  }
@@ -1607,6 +1609,7 @@ async function loadFeatureFlags(silent=false){
1607
1609
  state.featureFlags = data.flags || {};
1608
1610
  state.featureInstalled = data.installed || {};
1609
1611
  state.featureVersions = data.versions || {};
1612
+ state.featureLocked = data.locked || {};
1610
1613
  state.zaloBackend = data.zaloBackend || '';
1611
1614
  if (state.zaloBackend === 'zalo-connect') await loadZaloHealth(true).catch(() => {});
1612
1615
  } catch (_) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.13.4",
3
+ "version": "5.13.6",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {