Skip to content

Latest commit

 

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OpenClaw Zulip Bridge

Original project attribution

This repository was cloned and adapted from the original GitHub project niyazmft/openclaw-zulip-bridge. The current jeffsnell/openclaw-zulip-bridge repository preserves that project history and continues development for Jeff Snell's OpenClaw/Zulip deployment.

Version OpenClaw Node.js pnpm TypeScript License

High-performance OpenClaw channel plugin for Zulip streams and private messages with persistent event queues, traffic policies, and comprehensive observability.

Table of Contents


Quick Start

💡 Easiest path when you already have a trusted OpenClaw agent on the target machine: ask that agent to clone this repository into a local project directory, run pnpm install and pnpm run check, add the local clone to OpenClaw's plugin load paths, configure the Zulip bot credentials, restart the gateway, and perform a real Zulip message/reply test.

For a manual install, run:

# 1. Clone this repository
git clone https://github.com/jeffsnell/openclaw-zulip-bridge.git
cd openclaw-zulip-bridge

# 2. Install dependencies and validate
pnpm install
pnpm run check

# 3. Add the absolute local repository path to plugins.load.paths in openclaw.json
#    and configure channels.zulip with your bot credentials and access policy.

# 4. Validate, restart, and verify
openclaw config validate
openclaw gateway restart
openclaw status --deep

See Installation for the OpenClaw config shape and Zulip channel settings.


Features

  • Persistent Event Polling: Automatically resumes from where it left off using locally-persisted queue metadata.
  • Traffic Policies: Granular control over who can interact with the bot in DMs and Streams.
  • Multiple Accounts: Support for multiple Zulip accounts and realms in a single instance.
  • Mention Gating: Intelligent stream handling with oncall, onmessage, and onchar modes.
  • Durable Deduplication: Built-in persistent deduplication store to prevent duplicate message processing.
  • Media Support: Automatically processes Zulip uploads and inline images.
  • Rich Feedback: Optional reaction-based status indicators for request start, success, and errors.
  • Standardized Observability: Machine-parseable logs for easy monitoring and troubleshooting.

Prerequisites

  • OpenClaw: Version >=2026.6.0
  • Node.js: Latest LTS recommended (Node 22+)
  • Zulip Bot: A registered bot on your Zulip realm

Creating a Zulip Bot

  1. Log into your Zulip server
  2. Go to Settings → Your Bots → Add a new bot
  3. Choose Bot type: "Generic bot"
  4. Give it a name (e.g., "openclaw-bot")
  5. Copy the API key shown — this is your ZULIP_API_KEY
  6. The bot's email is your ZULIP_EMAIL

Installation

This repository can be used as a local OpenClaw plugin by cloning it and adding the clone path to OpenClaw's plugin load paths.

1. Clone The Repository

git clone https://github.com/jeffsnell/openclaw-zulip-bridge.git
cd openclaw-zulip-bridge

Do not clone directly into ~/.openclaw/extensions/zulip. Use a normal project directory and load that path from OpenClaw config.

2. Install Dependencies And Validate

Use pnpm:

pnpm install
pnpm run check

3. Add The Plugin Load Path To OpenClaw

Add the local repository path to your OpenClaw config:

{
  "plugins": {
    "entries": {
      "zulip": {
        "enabled": true
      }
    },
    "load": {
      "paths": [
        "/absolute/path/to/openclaw-zulip-bridge"
      ]
    }
  }
}

4. Configure The Zulip Channel

Add your Zulip bot credentials to OpenClaw's channel config:

{
  "channels": {
    "zulip": {
      "enabled": true,
      "name": "Zulip Bot",
      "url": "https://your-zulip-server.example.com",
      "email": "bot@example.com",
      "apiKey": "your-zulip-bot-api-key",
      "dmPolicy": "allowlist",
      "allowFrom": ["your-email@example.com", 12345],
      "groupPolicy": "allowlist",
      "groupAllowFrom": ["your-email@example.com", 12345],
      "chatmode": "oncall",
      "streams": ["*"],
      "requireMention": true
    }
  }
}

Do not commit real Zulip API keys or OpenClaw config files. Use placeholders in documentation and keep credentials in your local OpenClaw config or environment.

For allowlists, use the Zulip sender email and/or numeric Zulip user id for the human operators who should be allowed to trigger the bot. Avoid allowFrom: ["*"] and groupAllowFrom: ["*"] for normal operation when the OpenClaw agent has elevated/runtime/file tools enabled.

5. Restart OpenClaw And Verify

openclaw config validate
openclaw gateway restart
openclaw status --deep

Expected result:

Zulip    ON      OK     configured

Offline Installation

This plugin has zero production npm dependencies. You can build it on a connected machine, then copy the folder to an offline machine:

# On the connected machine:
git clone https://github.com/jeffsnell/openclaw-zulip-bridge.git /tmp/zulip-bridge
cd /tmp/zulip-bridge
pnpm install
pnpm run check

Then on the offline machine, copy the validated folder to a local project path, add that path to OpenClaw's plugin load paths, configure channels.zulip, restart OpenClaw, and run:

openclaw status --deep

Configuration

Interactive Setup (Recommended)

Run the built-in channel onboarding wizard:

openclaw channels add

Then at the interactive prompts:

  1. Select "Set up a chat channel now?" → Yes
  2. Choose "Zulip (plugin)" from the channel list
  3. Follow the guided prompts to enter your API key, bot email, and site URL
  4. Choose to route messages to an agent (e.g., main)

After setup, if dmPolicy is "pairing", send a DM to your bot and approve yourself:

openclaw pairing approve zulip <PAIRING_CODE_FROM_ZULIP_DM>

Tip: If ZULIP_API_KEY, ZULIP_EMAIL, and ZULIP_URL are set as environment variables, the wizard uses them automatically.

Manual Configuration

For advanced users, add to your openclaw.json:

{
  "plugins": {
    "entries": {
      "zulip": {
        "enabled": true
      }
    },
    "load": {
      "paths": ["/absolute/path/to/openclaw-zulip-bridge"]
    }
  },
  "channels": {
    "zulip": {
      "enabled": true,
      "url": "https://your-zulip-server.example.com",
      "email": "bot@example.com",
      "apiKey": "your-zulip-bot-api-key",
      "dmPolicy": "allowlist",
      "allowFrom": ["your-email@example.com", 12345],
      "groupPolicy": "allowlist",
      "groupAllowFrom": ["your-email@example.com", 12345],
      "chatmode": "oncall",
      "streams": ["*"],
      "requireMention": true
    }
  }
}

Configuration Options

Option Type Default Description
enabled boolean true Enable the Zulip channel
dmPolicy string "pairing" Who can DM the bot: "open" (anyone), "allowlist" (specific users), "pairing" (must pair first), "disabled" (ignore DMs)
streams string[] ["*"] Streams to monitor ("*" = all)
blockStreaming boolean false Enable block streaming for responses
chatmode string "onmessage" Stream trigger mode: "oncall", "onmessage", "onchar"
name string - Optional display name for the account
email string - Bot email address
apiKey string - Bot API key
url / site / realm string - Zulip server URL
allowFrom string[]/number[] - DM allowlist (Zulip sender emails, numeric user ids, or normalized names)
groupAllowFrom string[]/number[] - Group/stream allowlist (Zulip sender emails, numeric user ids, or normalized names)
groupPolicy string "allowlist" Group policy: "open", "allowlist", "disabled"
requireMention boolean true Require @mention in streams
oncharPrefixes string[] [">", "!"] Trigger characters for onchar mode
mediaMaxMb number 5 Maximum media upload size (MB)
textChunkLimit number 4000 Text chunk size limit
chunkMode string "length" Chunking mode: "length", "newline"
reactions object enabled Reaction indicators, e.g. { "enabled": true, "clearOnFinish": true }
streaming boolean true Enable receiving streaming messages
responsePrefix string - Custom response prefix override

Environment Variables

Variable Required Description
ZULIP_API_KEY Yes Bot API key from Zulip
ZULIP_EMAIL Yes Bot email address
ZULIP_URL Yes Zulip server URL (e.g., https://chat.example.com)

Verification

After setup, verify the bridge works:

  1. Validate config:

    openclaw config validate
  2. Check gateway and channel status:

    openclaw status --deep

    Expected channel state:

    Zulip    ON      OK     configured
    
  3. Check plugin status:

    openclaw plugins doctor
  4. Check logs if status is not enough. Useful markers include:

    [zulip] Plugin registration complete.
    [default] starting provider
    zulip queue registered [accountId=default queueId=... lastEventId=...]
    
  5. Test direct message: send a DM from an allowed user to the bot and confirm it replies.

  6. Test stream: mention @bot-name in a monitored stream from an allowed user and confirm it replies.

If dmPolicy or groupPolicy is allowlist, make sure the sender is present in allowFrom or groupAllowFrom.


Architecture

┌─────────────────────────────────────────────────────────────┐
│                        OpenClaw Gateway                      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Zulip Plugin (index.ts)                   │
├─────────────────────────────────────────────────────────────┤
│  ┌──────────────┐    ┌─────────────┐    ┌────────────────┐   │
│  │   monitor   │───▶│   client    │───▶│  Zulip API     │   │
│  │  (polling)  │    │  (requests) │    │  (REST/WebSocket)│   │
│  └──────────────┘    └─────────────┘    └────────────────┘   │
│         │                   │                                  │
│         ▼                   ▼                                  │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │               reply-handler.ts                          │  │
│  │  - Markdown processing                                  │  │
│  │  - Text chunking                                        │  │
│  │  - Typing indicators                                    │  │
│  │  - Media handling                                       │  │
│  └─────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
  • monitor.ts: Event loop that polls Zulip API, maintains event queue with persistence
  • client.ts: HTTP client wrapping Zulip REST API
  • reply-handler.ts: Converts agent responses to Zulip format, handles chunking
  • send.ts: Message delivery with security validation
  • accounts.ts: Multi-account configuration resolution

Troubleshooting

"plugin not found: zulip"

Cause: The plugin was installed but "zulip" is not in plugins.allow.

Fix:

openclaw config set plugins.allow '["zulip","telegram","memory-core","exa","ollama"]'
openclaw gateway restart

"registerFull already called, skipping duplicate monitor start"

Status: Harmless — the host calls registerFull twice during startup. The plugin deduplicates this.

"Health-monitor restarting (reason: stopped)"

Cause: The monitor's statusSink({ running: true }) wasn't called at the start of the monitor loop, so the host thinks the channel is dead.

Fix: Ensure statusSink is called at the top of the monitor function, not conditionally inside event handlers.

"Invalid config: must not have additional properties: streaming"

Cause: The manifest JSON Schema in openclaw.plugin.json was missing the streaming property that the host wizard writes.

Fix: Add "streaming": { "type": "boolean" } to both configSchema and channelConfigs.schema in the manifest.

Local plugin load path does not work

Prefer loading this repository from a normal project directory via plugins.load.paths.

Check:

pwd
pnpm install
pnpm run check
openclaw config validate
openclaw gateway restart
openclaw status --deep

Then verify your OpenClaw config points to the absolute path of this local repository.

"plugin not found: zulip" after installing

  1. Restart the gateway: openclaw gateway restart
  2. Check that the plugin is in the extensions dir: ls ~/.openclaw/extensions/zulip/

"not a valid hook pack"

Ensure you cloned to a neutral directory and ran pnpm install && pnpm run build.

Queue Registration Fails

Verify credentials with openclaw channels add and re-enter them.

No Response in Streams

Ensure the bot is a member of the stream and it's in your streams config.

Logs show "mention required"

Default requires @mentions. Check your chatmode setting.


Known Issues

Performance: First Message After Startup is Slower

Status: Expected behavior

Problem: The first Zulip message after gateway startup takes ~5–8s to get a reply.

Root Cause: Model warmup + cold inference for the first agent run. Subsequent messages reply in ~2–4s.

Mitigation: None needed. This is normal for external model providers.


Legacy Skill Packages

Two old skill packages exist on ClawHub under the same namespace:

  • @niyazmft/zulip-bridge (deprecated, redirects to canonical)
  • @niyazmft/openclaw-zulip-bridge (deprecated, merged into zulip-bridge)

These are skill (text bundle) packages, not code plugins. The active, maintained package is @niyazmft/openclaw-zulip (this repo).

Development

Local Setup

pnpm install
pnpm run check

This runs: bootstrap → typecheck → build → smoke test → unit tests → package check

Project Structure

src/
├── channel.ts            # Plugin entry point & channel config
├── setup-core.ts         # Interactive setup wizard
├── setup-surface.ts      # Setup wizard UI
├── config-schema.ts      # Configuration validation
├── types.ts              # Type definitions
├── zulip/
│   ├── auth.ts           # Authentication utilities
│   ├── bootstrap.ts      # Monitor initialization
│   ├── client.ts         # Zulip API client
│   ├── dedupe-store.ts   # Deduplication store
│   ├── media-utils.ts    # Media processing
│   ├── monitor-helpers.ts # Logging helpers
│   ├── monitor.ts       # Event polling & queue management
│   ├── policy.ts         # DM/group policy logic
│   ├── polling.ts       # Event polling
│   ├── probe.ts          # Connection probing
│   ├── queue-manager.ts  # Queue persistence
│   ├── reactions.ts      # Reaction handling
│   ├── reply-handler.ts  # Response processing
│   ├── send.ts           # Message sending with security
│   ├── text-utils.ts     # Text processing
│   ├── uploads.ts        # Upload handling
│   └── accounts.ts       # Multi-account config resolution

Contributing

Contributions are welcome! Please read our contribution guidelines before submitting PRs.

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Run pnpm run check to validate
  5. Submit a pull request

Running Tests

# Run all tests
pnpm test

# Run specific test file
pnpm test -- test/policy.test.ts

Security

For security vulnerabilities, please do not open a public issue. Contact the maintainer directly through GitHub or email.


License

MIT License - see LICENSE file for details.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages