Channels
Claw Matrix
OpenClaw Matrix channel plugin with E2E encryption
Install
openclaw plugins install https://gitlab.com/nicebit/claw-matrix.git`
Configuration Example
[global]
server_name = "your-domain.com"
database_path = "/data/db"
port = [8448]
address = "0.0.0.0"
allow_registration = false
allow_encryption = true
allow_federation = true
trusted_servers = ["matrix.org"]
log = "info"
README
# claw-matrix
OpenClaw channel plugin for Matrix with E2E encryption via `@matrix-org/matrix-sdk-crypto-nodejs`.
Tested and known to work well with [Tuwunel](https://github.com/matrix-construct/tuwunel) homeservers.
## Installation Prompt
Use this prompt with OpenClaw to install claw-matrix. The agent will walk you through setup interactively.
````
You are an installation assistant for claw-matrix, the OpenClaw Matrix channel plugin.
Present the user with 3 deployment options and ask which they'd like to set up:
### Option 1: claw-matrix only
Connect to an existing Matrix homeserver. Best if the user already runs Synapse, Dendrite, Tuwunel, or uses a hosted provider like matrix.org.
Requirements:
- An existing Matrix homeserver with a registered bot account
- The bot account's access token
- Homeserver URL (must be HTTPS)
Steps:
1. Install the claw-matrix plugin:
`openclaw plugins install https://gitlab.com/nicebit/claw-matrix.git`
2. Verify the plugin loaded:
`openclaw plugins list`
If there are load errors, run `openclaw plugins doctor` to diagnose.
3. Add the Matrix channel with an account. Ask the user for their homeserver URL, bot user ID, and access token, then run:
`openclaw channels add --channel matrix --account default --name "Matrix Bot"`
4. Configure the account credentials via the config CLI:
```
openclaw config set channels.matrix.accounts.default.enabled true
openclaw config set channels.matrix.accounts.default.homeserver "https://your-homeserver.example.com"
openclaw config set channels.matrix.accounts.default.userId "@bot:your-homeserver.example.com"
openclaw config set channels.matrix.accounts.default.accessToken "syt_..."
openclaw config set channels.matrix.accounts.default.encryption true
openclaw config set channels.matrix.accounts.default.deviceName "OpenClaw"
openclaw config set channels.matrix.accounts.default.dm.policy "allowlist"
openclaw config set channels.matrix.accounts.default.dm.allowFrom '["@youruser:example.com"]'
openclaw config set channels.matrix.accounts.default.groupPolicy "disabled"
```
5. Restart the gateway: `openclaw gateway restart`
6. Verify: `openclaw channels status` and `openclaw channels logs` β look for "Matrix monitor started" and successful /sync
### Option 2: Tuwunel + claw-matrix
Self-host a lightweight, high-performance Matrix homeserver using Tuwunel (Rust-based, successor to conduwuit) alongside claw-matrix. Best for users who want full control over their Matrix infrastructure without the resource overhead of Synapse.
Requirements:
- A server with a domain name and valid TLS (or a reverse proxy)
- Podman or Docker for running Tuwunel
- DNS records pointing to the server
Steps:
1. Pull the Tuwunel container image:
`podman pull ghcr.io/matrix-construct/tuwunel:main`
2. Create data directory:
`mkdir -p ~/.local/share/tuwunel`
3. Generate a Tuwunel config at `~/.local/share/tuwunel/tuwunel.toml`:
```toml
[global]
server_name = "your-domain.com"
database_path = "/data/db"
port = [8448]
address = "0.0.0.0"
allow_registration = false
allow_encryption = true
allow_federation = true
trusted_servers = ["matrix.org"]
log = "info"
```
4. Run Tuwunel:
```
podman run -d --name tuwunel \
--network=host --userns=keep-id \
-v ~/.local/share/tuwunel:/data:Z \
ghcr.io/matrix-construct/tuwunel:main
```
5. Register the bot account via the Tuwunel admin API or CLI.
6. Obtain an access token for the bot account.
7. Follow Option 1 steps 1-6 using `https://your-domain.com:8448` as the homeserver URL and the bot's access token.
### Option 3: Tuwunel + Cloudflare + claw-matrix
Full production stack: Tuwunel homeserver proxied through Cloudflare for DDoS protection, TLS termination, and caching β plus claw-matrix for OpenClaw integration. Best for public-facing deployments or federation-heavy setups.
Requirements:
- A Cloudflare account with a domain configured
- `cloudflared` (Cloudflare Tunnel daemon) installed
- Podman or Docker for running Tuwunel
Steps:
1. Set up Tuwunel (follow Option 2, steps 1-4), but bind to localhost only:
- In `tuwunel.toml`, set `address = "127.0.0.1"`
2. Configure Cloudflare DNS:
- Add an A/AAAA record for `matrix.your-domain.com` (proxied, orange cloud)
- Add an SRV record: `_matrix._tcp.your-domain.com β matrix.your-domain.com:443`
3. Create a Cloudflare Tunnel:
```
cloudflared tunnel create tuwunel
cloudflared tunnel route dns tuwunel matrix.your-domain.com
```
4. Configure the tunnel (`~/.cloudflared/config.yml`):
```yaml
tunnel: <tunnel-id>
credentials-file: ~/.cloudflared/<tunnel-id>.json
ingress:
- hostname: matrix.your-domain.com
service: http://localhost:8448
- service: http_status:404
```
5. Start the tunnel:
`cloudflared tunnel run tuwunel`
(Or set up as a systemd service for persistence.)
6. Configure Cloudflare SSL/TLS to "Full (strict)".
7. Set up `.well-known` delegation if `server_name` differs from the matrix subdomain:
- Serve `/.well-known/matrix/server` returning `{"m.server": "matrix.your-domain.com:443"}`
- Serve `/.well-known/matrix/client` returning `{"m.homeserver": {"base_url": "https://matrix.your-domain.com"}}`
8. Register the bot account and obtain an access token (see Option 2, steps 5-6).
9. Follow Option 1 steps 1-6 using `https://matrix.your-domain.com` as the homeserver URL.
---
After presenting the options, guide the user step-by-step through their chosen option. Ask for each required value interactively (domain, tokens, user IDs). Validate inputs where possible (e.g., homeserver URL must start with https://, userId must match @user:domain format). After installation, help verify the setup is working by checking gateway logs.
````
## Plugin Profile
OpenClaw channel plugin for Matrix with E2E encryption via `@matrix-org/matrix-sdk-crypto-nodejs`.
**Location:** `~/.openclaw/extensions/claw-matrix/`
**Status:** Phase 1 complete β text messages, DMs, groups, E2E encryption working. Device cross-signed (2026-02-11).
**Plugin ID:** `claw-matrix` (registers channel ID `matrix`)
**Loaded via:** jiti (JIT TypeScript), NOT compiled β entry point is `index.ts` directly
## Architecture
```
index.ts β register(api): stores PluginRuntime, registers channel
src/runtime.ts β module-level PluginRuntime store (get/set) β NEW, solves dispatch
src/channel.ts β ChannelPlugin contract (all OpenClaw adapters)
src/monitor.ts β sync loop lifecycle + inbound message dispatch
src/config.ts β Zod schema + ResolvedMatrixAccount resolver
src/actions.ts β agent tool actions (send/read/channel-list)
src/types.ts β Matrix event/response TypeScript interfaces
src/client/http.ts β matrixFetch() β authenticated Matrix API client
src/client/sync.ts β runSyncLoop() β long-poll /sync, decrypt, dispatch
src/client/send.ts β sendMatrixMessage() β encrypt + send (markdownβHTML)
src/client/rooms.ts β in-memory room state (encryption, type, names, members)
src/crypto/machine.ts β OlmMachine init/close, crypto store path
src/crypto/outgoing.ts β processOutgoingRequests() β key upload/query/claim/share
```
## Message Flow
### Inbound (Matrix β Agent)
1. `runSyncLoop()` long-polls `/sync` (30s timeout, exponential backoff)
2. To-device events fed to OlmMachine FIRST (key deliveries)
3. UTD queue retried (previously undecryptable events)
4. Timeline events: encrypted β `machine.decryptRoomEvent()` β plaintext
5. `onMessage(event, roomId)` callback fires in `monitor.ts`
6. Monitor checks: skip own messages β access control (allowlist) β empty body
7. `core.channel.routing.resolveAgentRoute()` β determines agent + session key
8. `core.channel.reply.finalizeInboundContext()` β creates FinalizedMsgContext
9. `core.channel.reply.dispatchReplyWithBufferedBlockDispatcher()` β agent turn
10. Agent reply delivered via `deliver` callback β `sendMatrixMessage()`
### Outbound (Agent β Matrix)
1. OpenClaw calls `outbound.sendText({ to: roomId, text })` OR deliver callback
2. `sendMatrixMessage()` formats markdownβHTML via markdown-it + sanitize-html
3. If room encrypted: `ensureRoomKeysShared()` β `machine.encryptRoomEvent()` β PUT encrypted event
4. If plaintext: PUT `m.room.message` directly
## Key Interfaces
### MonitorMatrixOpts (monitor.ts)
```typescript
{ config, account: ResolvedMatrixAccount, accountId, abortSignal, log, getStatus, setStatus }
```
### ResolvedMatrixAccount (config.ts)
```typescript
{ accountId, enabled, homeserver, userId, accessToken, password?, encryption, deviceName,
dm: { policy, allowFrom[] }, groupPolicy, groups: Record<roomId, { allow, requireMention }>,
groupAllowFrom[], chunkMode, textChunkLimit, recoveryKey?, trustMode }
```
### MsgContext fields set by monitor.ts
```typescript
{ Body, RawBody, CommandBody, From: "matrix:${sender}" | "matrix:room:${roomId}",
To: "matrix:${roomId}", SessionKey (from route), AccountId, ChatType: "direct"|"group",
GroupSubject?, SenderName, SenderId, Provider: "matrix", Surface: "matrix",
MessageSid, OriginatingChannel: "matrix", OriginatingTo: roomId, Timestamp,
CommandAuthorized: true }
```
### ChannelPlugin adapters implemented (channel.ts)
- **meta** β id, label, blurb
- **capabilities** β text only, dm+group, blockStreaming; NO reactions/edit/unsend/reply/media/threads
- **config** β listAccountIds, resolveAccount, isEnabled, isConfigured, resolveAllowFrom
- **gateway** β startAccount (launches monitor), stopAccount (abortSignal)
- **outbound** β deliveryMode: "direct", sendText, sendPayload, resolveTarget
- **security** β resolveDmPolicy (returns dm.policy + dm.allowFrom)
- **groups** β resolveRequireMention
- **actions** β send, read, channel-list (via actions.ts)
- **status** β buildAccountSnapshot
- **messaging** β normalizeTarget + targetResol
... (truncated)
channels
Comments
Sign in to leave a comment