OpenClaw channel plugin for Zalo personal accounts via openzca CLI.
Warning: this is an unofficial personal-account automation integration. Use at your own risk.
- Plugin id:
openzalo - Channel id:
openzalo - Package name:
@tuyenhx/openzalo - Required external binary:
openzca - Optional external binary for
/acpsupport:acpx
This plugin now bundles one optional skill (auto-discovered from ./skills):
openzca: advancedopenzcaCLI workflows, with DB-backed reads for summaries, history, and search.
openzca is installed at workspace/plugin level, not per-sender.
So "owner-only" should be enforced by runtime policy, not by skill installation.
Recommended setup:
- Keep general agents on
tools.profile: "messaging"(noexec). - Grant
execonly to a dedicated admin agent. - In OpenZalo group config, use
allowFrom+skillsfilter to expose advanced skills only in admin-controlled groups. - Use normal OpenZalo
messageactions for routine operations; use the bundledopenzcaskill when you want the full playbook or raw CLI workflows.
- OpenClaw Gateway is installed and running.
openzcais installed and available inPATH(or configurechannels.openzalo.zcaBinary).- If you want OpenZalo ACP-local sessions via
/acp, installacpxtoo. - You can authenticate with your Zalo account on the gateway machine.
Example direct login with openzca:
openzca --profile default auth loginExample acpx install for /acp support:
npm i -g acpxVerify:
which acpx
acpx --helpUse this after @tuyenhx/openzalo is published:
openclaw plugins install @tuyenhx/openzaloTo force ClawHub as the source once the package is listed there:
openclaw plugins install clawhub:@tuyenhx/openzaloFrom the OpenClaw repo root:
openclaw plugins install ./extensions/openzaloOr from this plugin directory:
openclaw plugins install .Restart Gateway after installation.
- OpenClaw plugin installs can resolve from ClawHub or npm.
- Newer
clawhubCLI builds can publish native plugin packages withclawhub package publish. - To distribute this plugin, publish the package itself; users can then install it with
openclaw plugins install @tuyenhx/openzalooropenclaw plugins install clawhub:@tuyenhx/openzalowhen available there. - The bundled
skills/openzcaskill can be published separately with theclawhubCLI if you want it discoverable as a standalone skill too.
- Login account for this channel:
openclaw channels login --channel openzalo
# optional multi-account
openclaw channels login --channel openzalo --account work- Add channel config:
{
channels: {
openzalo: {
enabled: true,
profile: "default",
dmPolicy: "pairing",
groupPolicy: "allowlist",
groupAllowFrom: ["<GROUP_ID>"],
},
},
}Or via CLI:
openclaw channels add --channel openzalo --account default- Send test message:
openclaw message send --channel openzalo --target <userId> --message "Hello from OpenClaw"
openclaw message send --channel openzalo --target group:<groupId> --message "Hello group"
openclaw message send --channel openzalo --target group:<groupId> --message "Hi @Alice Nguyen and @123456789"For group sends, plain @Name and @userId are forwarded to openzca and become native Zalo mentions.
For native mentions, do not guess. Only tag when you already have an exact unique member id or name from context or from the user.
This plugin can bind the current OpenZalo conversation to a local ACPX session without changing OpenClaw core.
Install acpx first:
npm i -g acpxIf the gateway service cannot see your shell PATH, set channels.openzalo.acpx.command to the absolute path from which acpx.
Example config:
{
channels: {
openzalo: {
acpx: {
enabled: true,
command: "/full/path/to/acpx", // or "acpx" if PATH is correct
agent: "claude", // e.g. claude | codex
cwd: "/Users/<you>/.openclaw/workspace",
permissionMode: "approve-all", // approve-all | approve-reads | deny-all
nonInteractivePermissions: "fail", // fail | deny
},
},
},
}Notes:
agentis the ACPX agent id. For Claude Code, useclaude. For Codex, usecodex.cwdis the working directory ACPX will use for that conversation.commandshould be an absolute path if/acp onreportsacpx command not found.
Supported OpenZalo ACP commands:
/acp status
/acp on
/acp on claude cwd=/Users/<you>/.openclaw/workspace
/acp reset
/acp off
Behavior:
/acp onbinds the current conversation to a persistent ACPX session./acp statusshows whether the conversation is bound and reports session status./acp resetrecreates the ACPX session for the current conversation./acp offunbinds the conversation and closes the ACPX session.
{
channels: {
openzalo: {
enabled: true,
profile: "default", // default: account id
zcaBinary: "openzca", // or full path
acpx: {
enabled: true,
command: "/full/path/to/acpx", // or "acpx" if PATH is correct
agent: "claude", // e.g. claude | codex
cwd: "/Users/<you>/.openclaw/workspace",
permissionMode: "approve-all", // approve-all | approve-reads | deny-all
nonInteractivePermissions: "fail", // fail | deny
},
// DM access: pairing | allowlist | open | disabled
dmPolicy: "pairing",
allowFrom: ["<OWNER_USER_ID>"],
// Group access: allowlist | open | disabled
groupPolicy: "allowlist",
groupAllowFrom: ["<GROUP_ID>"],
// Optional per-group overrides
groups: {
"<GROUP_ID>": {
enabled: true,
requireMention: true, // default true
allowFrom: ["<ALLOWED_SENDER_ID>"],
tools: {
allow: ["group:messaging"],
deny: ["group:fs", "group:runtime"],
},
toolsBySender: {
"<OWNER_USER_ID>": { allow: ["group:runtime", "group:fs"] },
},
skills: ["skill-id"],
systemPrompt: "Custom prompt for this group.",
},
},
historyLimit: 12,
dmHistoryLimit: 12, // optional (schema-supported)
textChunkLimit: 1800,
chunkMode: "length", // length | newline
blockStreaming: false,
mediaMaxMb: 25, // optional (schema-supported)
markdown: {}, // optional (schema-supported)
mediaLocalRoots: [
"/Users/<you>/.openclaw/workspace",
"/Users/<you>/.openclaw/media",
],
sendTypingIndicators: true,
threadBindings: {
enabled: true,
spawnSubagentSessions: true,
ttlHours: 24,
},
actions: {
reactions: true,
messages: true, // read/edit/unsend
groups: true, // rename/add/remove/leave
pins: true, // pin/unpin/list-pins
memberInfo: true, // member-info
groupMembers: true, // reserved
},
},
},
}channels.openzalo.accounts.<accountId> overrides top-level fields:
channels:
openzalo:
enabled: true
defaultAccount: default
accounts:
default:
profile: default
acpx:
enabled: true
command: /full/path/to/acpx
agent: claude
cwd: /Users/<you>/.openclaw/workspace
work:
profile: work
enabled: trueProfile resolution is per account. If zcaBinary is not set, plugin uses:
channels.openzalo[.accounts.<id>].zcaBinaryOPENZCA_BINARYenv varopenzca
If acpx is not set, OpenZalo ACP-local uses:
channels.openzalo[.accounts.<id>].acpx.commandOPENZALO_ACPX_COMMANDenv varacpx
- DM target:
<userId> - Group target:
group:<groupId> - Also accepted for groups:
g-<groupId>,g:<groupId> - Also accepted for DM/user targets:
user:<userId>,dm:<userId>,u:<userId>,u-<userId> - Channel prefixes like
openzalo:<target>andozl:<target>are normalized automatically. - Legacy
zlu:<target>remains accepted for backward compatibility.
Use group: for explicit group sends.
- Inbound listener uses
openzca listen --raw --supervisedso OpenZalo owns restart policy and receives lifecycle heartbeats. - Group messages require mention by default (
requireMention: true) unless overridden. - Authorized slash/bang control commands can still be processed in groups when access policy allows.
- Pairing mode sends approval code for unknown DM senders.
- Subagent session binding controls use
channels.openzalo.threadBindings.*(or per-account overrides). - Local media is restricted to allowed roots for safety.
Default safe media roots (under OPENCLAW_STATE_DIR or CLAWDBOT_STATE_DIR, fallback ~/.openclaw):
workspacemediaagentssandboxes
openzca not found: installopenzcaor setchannels.openzalo.zcaBinary.acpx command not found: installacpx(for examplenpm i -g acpx) or setchannels.openzalo.acpx.commandto the absoluteacpxpath.- Auth check fails: run
openclaw channels login --channel openzalo(oropenzca --profile <id> auth login). - Group message dropped: verify
groupPolicy,groupAllowFrom, andgroups.<groupId>allowlist. - Group message dropped with allowlist configured: check
requireMentionand mention detection. - Local media blocked: add absolute paths to
channels.openzalo.mediaLocalRoots.