openclaw-zulip
v0.3.0
Published
OpenClaw channel plugin for Zulip — clean-room implementation with ACP topic bindings
Maintainers
Readme
openclaw-zulip
A clean-room OpenClaw channel plugin for Zulip, built from scratch with the OpenClaw Plugin SDK.
Features
- Full Zulip messaging — Streams, topics, DMs, reactions, media, edits, unsend
- ACP topic bindings — Bind ACP agent sessions to specific Zulip topics
- Dual account modes — Run as a bot or impersonate a user account
- Stream-level controls — Per-stream config (require mention, enable/disable)
- Security — DM policy enforcement, allow-from lists
- Actions — Channel list, channel info, member info queries
Installation
openclaw-zulip 0.3.x requires OpenClaw 2026.9.1 or later in the 2026 release line. Install openclaw-zulip 0.2.6 when using an older OpenClaw host.
ACP topic bindings still use the public conversation-runtime SDK export.
Although deprecated, its suggested thread-binding replacements are private
host facades, not supported third-party SDK exports. Removing this last import
requires a public binding registration API from OpenClaw; progress is tracked
in #69.
openclaw plugins install openclaw-zulipConfiguration
Add a channels.zulip section to your OpenClaw config:
channels:
zulip:
serverUrl: https://your-org.zulipchat.com
email: [email protected]
apiKey: your-bot-api-key
mode: bot # or "user"
# Optional: per-stream overrides
streams:
general:
requireMention: true # only respond when mentioned
private-ops:
enabled: false # ignore inbound messages entirely
# Optional: multi-account setup
accounts:
work-bot:
serverUrl: https://work.zulipchat.com
email: [email protected]
apiKey: ...Account modes
| Mode | Description |
|------|-------------|
| bot | Connects as a Zulip bot (default). Messages appear from the bot identity. |
| user | Connects as a regular Zulip user. Messages appear from that user's identity. |
Configuration fields
| Field | Required | Description |
|-------|----------|-------------|
| serverUrl | Yes | Zulip server URL |
| email | Yes | Bot or user email address |
| apiKey | Yes | Zulip API key |
| mode | No | bot (default) or user |
| name | No | Display label for the account |
| enabled | No | Set false to keep the account configured but inactive |
| streams | No | Per-stream config overrides — see Per-stream config |
| dmPolicy | No | DM handling policy — pairing (default), allowlist, open, disabled |
| allowFrom | No | Allowed sender user IDs or emails (used by dmPolicy: allowlist) |
| replyToMode | No | Reply targeting — all (default), first, off |
| accounts | No | Named sub-accounts, each taking the fields above |
| defaultAccount | No | Which named account to use when none is specified |
Use the flat dmPolicy / allowFrom fields shown above. A nested dm block (dm.policy,
dm.allowFrom) is accepted by config validation for parity with other OpenClaw channels, but this
plugin does not read it — values set there have no effect. See issue #44.
Per-stream config
Each key under streams is a stream name, and takes:
| Field | Description |
|-------|-------------|
| enabled | Set false to ignore inbound messages in this stream entirely |
| requireMention | Set true to only respond in this stream when the account is mentioned |
streams:
Homelab:
requireMention: true
Audit Log:
enabled: falseenabled: false makes the stream passive: inbound messages are dropped before any session,
typing indicator, reaction, or agent dispatch happens, so a disabled stream costs no tokens. The
account stays subscribed and fully capable in the other direction — it can still send messages to
the stream and read or search it through agent tools. Use this for streams an account needs to
watch or write to, but shouldn't react to on its own.
The policy is per account: several accounts can subscribe to the same stream and each choose passive or active independently.
Only an explicit enabled: false disables a stream. Streams that are unconfigured, or configured
without an enabled value, are processed as usual.
Stream names are matched ignoring case and surrounding whitespace. A key that is entirely numeric is matched against the stream ID instead, which survives a stream being renamed:
streams:
"42":
enabled: falseNumeric keys are ID selectors only — they are never matched against a stream's name, so a key of
"42" will not pick up a stream that happens to be named 42. A stream whose name is entirely
digits can therefore only be configured by its ID.
ACP topic bindings
A binding ties an ACP agent session to a specific Zulip conversation, so messages arriving there are routed to that session instead of starting a new one. Bindings are matched by pattern:
| Pattern | Matches |
|---------|---------|
| <stream_id>/<topic> | One exact topic in one stream |
| <stream_id>/* | Any topic in that stream |
| */<topic> | That topic name in any stream |
| <stream_id> | The stream as a whole, or a DM conversation |
More specific patterns win: exact stream + exact topic outranks a wildcard on either side.
Bindings are created at runtime through OpenClaw's binding commands — there is no binding section in the plugin config. They are held in memory and do not survive a gateway restart, so treat them as session-scoped rather than durable configuration.
Capabilities
| Feature | Supported | |---------|-----------| | Direct messages | Yes | | Group conversations (streams) | Yes | | Threads (topics) | Yes | | Reactions | Yes | | Message editing | Yes | | Unsend | Yes | | Reply | Yes | | Media | Yes | | ACP topic bindings | Yes | | Native commands | No | | Polls | No |
Migrating from openclaw-channel-zulip
If you're replacing the old openclaw-channel-zulip plugin, note these config changes:
Config field renames
| Old field | New field |
|-----------|-----------|
| url | serverUrl |
Streams format change
The old plugin used an array of stream names:
streams:
- "AI System"
- "Homelab"The new plugin uses an object with per-stream config:
streams:
AI System:
requireMention: true
Homelab:
enabled: trueTo migrate, convert each stream name to a key with {} (empty config) or add per-stream settings as needed.
Development
git clone https://github.com/whizzlelabs/openclaw-zulip.git
cd openclaw-zulip
npm install
npm run build
npm testSee CONTRIBUTING.md for the full development workflow.
