Files
zeekay acaeda400a Eradicate OpenClaw — resolve merge conflicts and finish the rebrand
Three coordinated things:

1. Resolve the committed upstream/main merge conflicts. 94 files were
   sitting with raw <<<<<<< HEAD ... >>>>>>> upstream/main blocks. Every
   conflict had HanzoBot/Bot on HEAD and OpenClaw on upstream. Resolved
   by keeping HEAD everywhere — 540 conflict blocks.

2. Rewrite remaining OpenClaw text refs.
     OPENCLAW   → HANZO_BOT
     OpenClaw   → HanzoBot          (PascalCase type identifiers)
     openclaw   → bot               (codebase convention — botToken,
                                    botUsername etc.; avoids invalid
                                    hyphenated JS identifiers)
     openclaw[_]/openclaw[A-Z] → bot[_]/bot[A-Z]   (compound)
     ai.openclaw.x → ai.hanzo.bot.x  (JVM package path)

3. Delete or rename openclaw-named file paths (116 of them). Dead
   duplicates with a Bot-named canonical from the partial migration:
   deleted. Otherwise renamed.
     ai/openclaw/**           → deleted (ai/hanzo/bot/** canonical)
     Sources/OpenClaw*/       → deleted (Sources/Bot* canonical)
     Tests/OpenClawIPCTests/  → deleted (Tests/BotIPCTests canonical)
     OpenClawKit/             → deleted (BotKit canonical)
     openclaw-tools.*.ts      → deleted (bot-tools.*.ts canonical)
     openclaw-root.ts etc.    → deleted (bot-root.ts canonical)
     types.openclaw.ts        → deleted (types.bot.ts canonical)
     extensions/*/openclaw.plugin.json → renamed hanzo-bot.plugin.json
     docs/start/openclaw.md   → renamed hanzo-bot.md (+ zh-CN)
     docs/assets/openclaw-*.png, whatsapp-openclaw*.jpg → deleted
     scripts/.../openclaw-*   → deleted (bot/hanzo-bot parallels)
     openclaw.mjs             → deleted (hanzo-bot.mjs is package.bin)

Then patched 65 broken JS/TS import strings where hanzo-bot had ended
up inside an import path ('./types.hanzo-bot.js' → './types.bot.js'
etc., since the on-disk filename uses the brand-neutral bot- prefix).

Pre-existing lint debt cleaned up to get oxlint --type-aware to 0/0:
removed the dead i18n test that referenced a path no longer existing;
defined the missing DIDConfig and WalletConfig types in types.base.ts
(they were imported but never declared); dropped unused imports;
collapsed redundant type assertion and a tautological meta.bot lookup
that the openclaw→bot rename made redundant.

The a2ui.bundle.js generated artifact is kept at its pre-rebrand
contents — it gets regenerated by 'pnpm canvas:a2ui:bundle' from
sources I cannot rebuild in this commit; the source side is clean.

Verified: 0 occurrences of openclaw (case-insensitive) in tree source
content, 0 file or directory paths with openclaw in the name; oxlint
--type-aware src test reports 0/0.
2026-05-11 17:56:29 -07:00

8.2 KiB

summary, read_when, title
summary read_when title
Zalo bot support status, capabilities, and configuration
Working on Zalo features or webhooks
Zalo

Zalo (Bot API)

Status: experimental. DMs are supported; group handling is available with explicit group policy controls.

Plugin required

Zalo ships as a plugin and is not bundled with the core install.

  • Install via CLI: hanzo-bot plugins install @hanzo/bot-zalo
  • Or select Zalo during onboarding and confirm the install prompt
  • Details: Plugins

Quick setup (beginner)

  1. Install the Zalo plugin:
    • From a source checkout: hanzo-bot plugins install ./extensions/zalo
    • From npm (if published): hanzo-bot plugins install @hanzo/bot-zalo
    • Or pick Zalo in onboarding and confirm the install prompt
  2. Set the token:
    • Env: ZALO_BOT_TOKEN=...
    • Or config: channels.zalo.botToken: "...".
  3. Restart the gateway (or finish onboarding).
  4. DM access is pairing by default; approve the pairing code on first contact.

Minimal config:

{
  channels: {
    zalo: {
      enabled: true,
      botToken: "12345689:abc-xyz",
      dmPolicy: "pairing",
    },
  },
}

What it is

Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations. It is a good fit for support or notifications where you want deterministic routing back to Zalo.

  • A Zalo Bot API channel owned by the Gateway.
  • Deterministic routing: replies go back to Zalo; the model never chooses channels.
  • DMs share the agent's main session.
  • Groups are supported with policy controls (groupPolicy + groupAllowFrom) and default to fail-closed allowlist behavior.

Setup (fast path)

1) Create a bot token (Zalo Bot Platform)

  1. Go to https://bot.zaloplatforms.com and sign in.
  2. Create a new bot and configure its settings.
  3. Copy the bot token (format: 12345689:abc-xyz).

2) Configure the token (env or config)

Example:

{
  channels: {
    zalo: {
      enabled: true,
      botToken: "12345689:abc-xyz",
      dmPolicy: "pairing",
    },
  },
}

Env option: ZALO_BOT_TOKEN=... (works for the default account only).

Multi-account support: use channels.zalo.accounts with per-account tokens and optional name.

  1. Restart the gateway. Zalo starts when a token is resolved (env or config).
  2. DM access defaults to pairing. Approve the code when the bot is first contacted.

How it works (behavior)

  • Inbound messages are normalized into the shared channel envelope with media placeholders.
  • Replies always route back to the same Zalo chat.
  • Long-polling by default; webhook mode available with channels.zalo.webhookUrl.

Limits

  • Outbound text is chunked to 2000 characters (Zalo API limit).
  • Media downloads/uploads are capped by channels.zalo.mediaMaxMb (default 5).
  • Streaming is blocked by default due to the 2000 char limit making streaming less useful.

Access control (DMs)

DM access

  • Default: channels.zalo.dmPolicy = "pairing". Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
  • Approve via:
    • hanzo-bot pairing list zalo
    • hanzo-bot pairing approve zalo <CODE>
  • Pairing is the default token exchange. Details: Pairing
  • channels.zalo.allowFrom accepts numeric user IDs (no username lookup available).

Access control (Groups)

  • channels.zalo.groupPolicy controls group inbound handling: open | allowlist | disabled.
  • Default behavior is fail-closed: allowlist.
  • channels.zalo.groupAllowFrom restricts which sender IDs can trigger the bot in groups.
  • If groupAllowFrom is unset, Zalo falls back to allowFrom for sender checks.
  • groupPolicy: "disabled" blocks all group messages.
  • groupPolicy: "open" allows any group member (mention-gated).
  • Runtime note: if channels.zalo is missing entirely, runtime still falls back to groupPolicy="allowlist" for safety.

Long-polling vs webhook

  • Default: long-polling (no public URL required).
  • Webhook mode: set channels.zalo.webhookUrl and channels.zalo.webhookSecret.
    • The webhook secret must be 8-256 characters.
    • Webhook URL must use HTTPS.
    • Zalo sends events with X-Bot-Api-Secret-Token header for verification.
    • Gateway HTTP handles webhook requests at channels.zalo.webhookPath (defaults to the webhook URL path).
    • Requests must use Content-Type: application/json (or +json media types).
    • Duplicate events (event_name + message_id) are ignored for a short replay window.
    • Burst traffic is rate-limited per path/source and may return HTTP 429.

Note: getUpdates (polling) and webhook are mutually exclusive per Zalo API docs.

Supported message types

  • Text messages: Full support with 2000 character chunking.
  • Image messages: Download and process inbound images; send images via sendPhoto.
  • Stickers: Logged but not fully processed (no agent response).
  • Unsupported types: Logged (e.g., messages from protected users).

Capabilities

Feature Status
Direct messages ✅ Supported
Groups ⚠️ Supported with policy controls (allowlist by default)
Media (images) ✅ Supported
Reactions ❌ Not supported
Threads ❌ Not supported
Polls ❌ Not supported
Native commands ❌ Not supported
Streaming ⚠️ Blocked (2000 char limit)

Delivery targets (CLI/cron)

  • Use a chat id as the target.
  • Example: hanzo-bot message send --channel zalo --target 123456789 --message "hi".

Troubleshooting

Bot doesn't respond:

  • Check that the token is valid: hanzo-bot channels status --probe
  • Verify the sender is approved (pairing or allowFrom)
  • Check gateway logs: hanzo-bot logs --follow

Webhook not receiving events:

  • Ensure webhook URL uses HTTPS
  • Verify secret token is 8-256 characters
  • Confirm the gateway HTTP endpoint is reachable on the configured path
  • Check that getUpdates polling is not running (they're mutually exclusive)

Configuration reference (Zalo)

Full configuration: Configuration

Provider options:

  • channels.zalo.enabled: enable/disable channel startup.
  • channels.zalo.botToken: bot token from Zalo Bot Platform.
  • channels.zalo.tokenFile: read token from file path.
  • channels.zalo.dmPolicy: pairing | allowlist | open | disabled (default: pairing).
  • channels.zalo.allowFrom: DM allowlist (user IDs). open requires "*". The wizard will ask for numeric IDs.
  • channels.zalo.groupPolicy: open | allowlist | disabled (default: allowlist).
  • channels.zalo.groupAllowFrom: group sender allowlist (user IDs). Falls back to allowFrom when unset.
  • channels.zalo.mediaMaxMb: inbound/outbound media cap (MB, default 5).
  • channels.zalo.webhookUrl: enable webhook mode (HTTPS required).
  • channels.zalo.webhookSecret: webhook secret (8-256 chars).
  • channels.zalo.webhookPath: webhook path on the gateway HTTP server.
  • channels.zalo.proxy: proxy URL for API requests.

Multi-account options:

  • channels.zalo.accounts.<id>.botToken: per-account token.
  • channels.zalo.accounts.<id>.tokenFile: per-account token file.
  • channels.zalo.accounts.<id>.name: display name.
  • channels.zalo.accounts.<id>.enabled: enable/disable account.
  • channels.zalo.accounts.<id>.dmPolicy: per-account DM policy.
  • channels.zalo.accounts.<id>.allowFrom: per-account allowlist.
  • channels.zalo.accounts.<id>.groupPolicy: per-account group policy.
  • channels.zalo.accounts.<id>.groupAllowFrom: per-account group sender allowlist.
  • channels.zalo.accounts.<id>.webhookUrl: per-account webhook URL.
  • channels.zalo.accounts.<id>.webhookSecret: per-account webhook secret.
  • channels.zalo.accounts.<id>.webhookPath: per-account webhook path.
  • channels.zalo.accounts.<id>.proxy: per-account proxy URL.