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.
8.2 KiB
summary, read_when, title
| summary | read_when | title | |
|---|---|---|---|
| Zalo bot support status, capabilities, and configuration |
|
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)
- 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
- From a source checkout:
- Set the token:
- Env:
ZALO_BOT_TOKEN=... - Or config:
channels.zalo.botToken: "...".
- Env:
- Restart the gateway (or finish onboarding).
- 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)
- Go to https://bot.zaloplatforms.com and sign in.
- Create a new bot and configure its settings.
- 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.
- Restart the gateway. Zalo starts when a token is resolved (env or config).
- 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 zalohanzo-bot pairing approve zalo <CODE>
- Pairing is the default token exchange. Details: Pairing
channels.zalo.allowFromaccepts numeric user IDs (no username lookup available).
Access control (Groups)
channels.zalo.groupPolicycontrols group inbound handling:open | allowlist | disabled.- Default behavior is fail-closed:
allowlist. channels.zalo.groupAllowFromrestricts which sender IDs can trigger the bot in groups.- If
groupAllowFromis unset, Zalo falls back toallowFromfor sender checks. groupPolicy: "disabled"blocks all group messages.groupPolicy: "open"allows any group member (mention-gated).- Runtime note: if
channels.zalois missing entirely, runtime still falls back togroupPolicy="allowlist"for safety.
Long-polling vs webhook
- Default: long-polling (no public URL required).
- Webhook mode: set
channels.zalo.webhookUrlandchannels.zalo.webhookSecret.- The webhook secret must be 8-256 characters.
- Webhook URL must use HTTPS.
- Zalo sends events with
X-Bot-Api-Secret-Tokenheader 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+jsonmedia 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).openrequires"*". 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 toallowFromwhen 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.