# ZTM Chat Channel Plugin for OpenClaw
[](https://openclaw.ai)
[](https://github.com/flomesh-io/ztm)
[](https://npmjs.com/package/@flomesh/ztm-chat)
[](https://github.com/clawparty-ai/openclaw-channel-plugin-ztm/actions)
[](https://codecov.io/gh/clawparty-ai/openclaw-channel-plugin-ztm)
[](https://www.typescriptlang.org/)
[](https://vitest.dev)
[](https://github.com/clawparty-ai/openclaw-channel-plugin-ztm/releases/latest)
[](LICENSE)
This plugin integrates OpenClaw with ZTM (Zero Trust Mesh) Chat, enabling decentralized P2P messaging through the ZTM network.
## Architecture
```mermaid
flowchart TB
subgraph ZTM["ZTM Network"]
User["ZTM User"]
Agent["ZTM Agent"]
end
subgraph OpenClaw["OpenClaw Gateway"]
Plugin["ztm-chat Plugin"]
Dispatcher["Message Dispatcher"]
AgentLLM["AI Agent"]
end
subgraph Storage["Local Storage"]
State["Message State"]
Pairing["Pairing Store"]
end
User -->|"DM/Group Message"| Agent
Agent -->|"Chat App API"| Plugin
Plugin -->|"Route"| Dispatcher
Dispatcher -->|"Check Policy"| AgentLLM
AgentLLM -->|"Response"| Plugin
Plugin -->|"Chat App API"| Agent
Agent -->|"Deliver"| User
Plugin --> State
Plugin --> Pairing
```
**Data Flow:**
1. User sends message to ZTM Agent
2. Plugin polls Chat App API for new messages
3. Dispatcher checks DM/Group policy
4. If allowed, route to AI Agent
5. AI Agent generates response
6. Plugin sends response via Chat App API
7. ZTM Agent delivers to user
## Features
- **Peer-to-Peer Messaging**: Send and receive messages with other ZTM users
- **Remote Connection**: Connect to ZTM Agent from anywhere via HTTP API
- **Secure**: Supports mTLS authentication with ZTM certificates
- **Decentralized**: Messages flow through the ZTM P2P network
- **Multi-Account**: Support for multiple ZTM bot accounts with isolated state
- **User Discovery**: Browse and discover other users in your ZTM mesh
- **Real-Time Updates**: Watch mechanism for message monitoring (1-second polling interval)
- **Message Deduplication**: Prevents duplicate message processing
- **Structured Logging**: Context-aware logger with sensitive data filtering
- **Interactive Wizard**: CLI-guided configuration setup
- **Group Chat Support**: Multi-user group conversations with permission control
- **Fine-Grained Access Control**: Per-group policies, mention gating, and tool restrictions
## Installation
### 1. Install ZTM CLI
Download ZTM from GitHub releases and install to `/usr/local/bin`:
```bash
# Download (example: v2.0.0 for Linux x86_64)
curl -L "https://github.com/flomesh-io/ztm/releases/download/v2.0.0/ztm-aio-v2.0.0-generic_linux-x86_64.tar.gz" -o /tmp/ztm.tar.gz
# Extract
tar -xzf /tmp/ztm.tar.gz -C /tmp
# Install to /usr/local/bin (requires sudo)
sudo mv /tmp/bin/ztm /usr/local/bin/ztm
# Cleanup
rm /tmp/ztm.tar.gz
# Verify
ztm version
```
### 2. Start ZTM Agent
```bash
ztm start agent
```
The agent will start listening on `http://localhost:7777` by default.
### 3. Install Plugin
#### Option A: Install from npm (recommended for users)
```bash
openclaw plugins install @flomesh/ztm-chat
```
#### Option B: Local development installation
```bash
# Install dependencies first
npm install
# Install from local path
openclaw plugins install -l .
# Or link for development
npm link
openclaw plugins install -l .
```
After installation, the plugin will be available as `ztm-chat` (or `ztm` as alias).
### 4. Run Configuration Wizard
```bash
openclaw onboard
```
Select **"ZTM Chat (P2P)"** from the channel list.
The wizard will guide you through:
1. **ZTM Agent URL** (default: `http://localhost:7777`)
2. **Permit Source Selection**
- Permit Source: `server` (from permit server) or `file` (from local file)
- Permit Server URL (when server): `https://clawparty.flomesh.io:7779/permit`
- Permit File Path (when file): path to local permit.json
3. **Bot Username** (default: `openclaw-bot`)
4. **Security Settings**
- DM Policy: `pairing` (recommended), `allow`, or `deny`
- Allow From: Whitelist of usernames (or `*` for all)
5. **Group Chat Settings** (if enabled)
- Enable Groups: Yes/No
- Group Policy: `allowlist`, `open`, or `disabled`
- Require Mention: Yes/No (default: Yes)
6. **Summary & Save**
### 5. Restart OpenClaw
```bash
openclaw gateway restart
```
## Group Chat
### Overview
ZTM Chat supports group conversations with fine-grained permission control. When `enableGroups` is enabled, the bot can:
- Receive and process messages from group chats
- Reply to group messages with @mention support
- Apply per-group access policies
- Restrict available tools based on group membership
### How It Works
```mermaid
flowchart TB
subgraph Remote["Remote User Environment"]
User1["Member 1"]
User2["Member 2"]
User3["Member 3"]
RemotePlugin["ZTM Chat Plugin
(HTTP API Client)"]
RemoteAgent["ZTM Agent
(Remote)"]
RemoteFiles["Message Files
(Remote)"]
end
subgraph P2P["ZTM P2P Network"]
Hub["Mesh Hub
(Coordinates Sync)"]
end
subgraph Local["Local Bot Environment"]
LocalAgent["ZTM Agent
(Local)"]
LocalFiles["Message Files
(Local)"]
LocalPlugin["ZTM Chat Plugin
(HTTP API Client)"]
subgraph PluginFlow["Bot Message Processing"]
Watch["Watch Loop
(1s interval)"]
Classify["Classify Changes
(peer/group)"]
Fetch["Fetch Messages
(HTTP API)"]
Policy["Policy Check"]
Dispatch["Dispatch Callbacks"]
AI["AI Agent"]
Send["Send Response
(HTTP API)"]
end
end
User1 -->|"@mention"| RemotePlugin
User2 -->|"@mention"| RemotePlugin
User3 -->|"@mention"| RemotePlugin
RemotePlugin -->|"HTTP Request"| RemoteAgent
RemoteAgent -->|"HTTP Response"| RemotePlugin
RemoteAgent -->|"Read/Write"| RemoteFiles
RemoteFiles -->|"P2P Sync"| Hub
Hub -->|"P2P Sync"| LocalFiles
LocalAgent -->|"Read/Write"| LocalFiles
LocalPlugin -->|"HTTP Request"| LocalAgent
LocalAgent -->|"HTTP Response"| LocalPlugin
LocalPlugin -->|"1. watchChanges"| Watch
Watch -->|"2. Changed Items"| Classify
Classify -->|"3. Peer/Group"| Fetch
Fetch -->|"4. getMessages"| LocalPlugin
LocalPlugin -->|"Messages"| Fetch
Fetch -->|"5. Messages"| Policy
Policy -->|"Allow"| Dispatch
Policy -->|"Deny"| Ignore[Ignore Message]
Dispatch -->|"6. Notify Callbacks"| AI
AI -->|"7. Response"| Send
Send -->|"8. sendGroupMessage"| LocalPlugin
LocalPlugin -->|"9. HTTP POST"| LocalAgent
LocalAgent -->|"Write to Files"| LocalFiles
LocalFiles -->|"P2P Sync"| Hub
Hub -->|"P2P Sync"| RemoteFiles
RemoteAgent -->|"Write to Files"| RemoteFiles
RemotePlugin -->|"Display"| User1
RemotePlugin -->|"Display"| User2
RemotePlugin -->|"Display"| User3
```
### Mention Gating
When `requireMention` is enabled (default), the bot will only process messages that @mention the bot username:
```
# Bot username: my-bot
# These messages will be processed:
@my-bot can you help me?
Hey @my-bot what's up?
# These messages will be ignored:
hello everyone!
good morning
```
**Note:** `requireMention` applies to ALL users, including the group creator. This ensures even the group owner must explicitly mention the bot to trigger a response.
### Per-Group Configuration
You can configure different policies for different groups:
```yaml
channels:
ztm-chat:
accounts:
my-bot:
enableGroups: true
groupPolicy: allowlist # Default for unknown groups
requireMention: true # Global default (can be overridden per group)
groupPermissions:
alice/team:
groupPolicy: open
requireMention: false
bob/project-x:
groupPolicy: allowlist
requireMention: true
allowFrom: [bob, charlie, david]
private/secret-group:
groupPolicy: disabled
```
### Tool Restrictions
Control which tools are available in each group:
```yaml
channels:
ztm-chat:
accounts:
my-bot:
groupPermissions:
alice/team:
groupPolicy: open
requireMention: false
tools:
allow:
- group:messaging
- group:sessions
- group:runtime
toolsBySender:
admin:
alsoAllow:
- exec
- fs
```
#### Tool Policy Options
| Option | Description |
|--------|-------------|
| `tools.allow` | Only allow these tools (deny all others) |
| `tools.deny` | Deny these tools (allow all others) |
| `toolsBySender.{user}.alsoAllow` | Additional tools for specific users |
| `toolsBySender.{user}.deny` | Deny tools for specific users |
#### Default Tools
By default, groups only have access to:
- `group:messaging` - Send/receive messages
- `group:sessions` - Session management
### Creator Privileges
Group creators have special privileges that allow them to bypass certain policy checks:
| Check | Creator Bypass? |
|-------|---------------|
| `groupPolicy` (disabled/allowlist/open) | ✅ Yes |
| `allowFrom` whitelist | ✅ Yes |
| `requireMention` | ❌ No (still required) |
This ensures the bot owner can always interact with their own groups while still requiring explicit @mentions to trigger responses.
## Usage
### Sending a Message
From any ZTM user, send a message to your bot:
```
Hello! Can you help me with something?
```
The bot will respond through OpenClaw's AI agent.
### Pairing Mode
By default, the bot uses **pairing mode** (`dmPolicy: "pairing"`):
1. **New users** must be approved before they can send messages
2. When an unapproved user sends a message, the bot sends them a pairing code
3. Approve users using the CLI with their pairing code
#### List Pending Requests
```bash
openclaw pairing list ztm-chat
```
#### Approve a Pairing Request
```bash
openclaw pairing approve ztm-chat
```
#### Pairing Mode Policies
| Policy | Behavior |
|--------|----------|
| `allow` | Accept messages from all users (no approval needed) |
| `deny` | Reject messages from all users (except allowFrom list) |
| `pairing` | Require explicit approval for new users (recommended) |
## CLI Commands
### Onboarding Commands
```bash
# Interactive setup wizard (recommended)
openclaw onboard
# Select "ZTM Chat" from the channel list
# Follow the 6-step configuration wizard
# Manage existing configuration
openclaw onboard
# Select "ZTM Chat" -> "Manage" -> Choose option:
# - Test Connection: Verify ZTM Agent connectivity
# - Update Configuration: Re-run the wizard
# - Remove: Display removal instructions
```
### Channel Commands
```bash
# Check channel status
openclaw channels status ztm-chat
# View configuration
openclaw channels describe ztm-chat
# Probe connection
openclaw channels status ztm-chat --probe
# Enable/disable channel
openclaw channels disable ztm-chat
openclaw channels enable ztm-chat
# List connected peers
openclaw channels directory ztm-chat peers
# List groups (if enabled)
openclaw channels directory ztm-chat groups
```
### Pairing Commands
```bash
# List pending pairing requests
openclaw pairing list ztm-chat
# Approve a pairing request
openclaw pairing approve ztm-chat
```
## Configuration
### Configuration File
Configuration is stored in `openclaw.yaml` under `channels.ztm-chat`:
#### Mode 1: Server (from permit server)
```yaml
channels:
ztm-chat:
enabled: true
accounts:
my-bot:
agentUrl: "http://localhost:7777"
permitSource: "server"
permitUrl: "https://clawparty.flomesh.io:7779/permit"
meshName: "production-mesh"
username: "my-bot"
enableGroups: true
dmPolicy: "pairing"
allowFrom:
- alice
- trusted-team
groupPolicy: "allowlist"
requireMention: true
groupPermissions:
alice/team:
creator: "alice"
group: "team"
groupPolicy: "open"
requireMention: false
allowFrom: []
tools:
```
#### Mode 2: File (from local permit.json)
```yaml
channels:
ztm-chat:
enabled: true
accounts:
my-bot:
agentUrl: "http://localhost:7777"
permitSource: "file"
permitFilePath: "/path/to/permit.json"
meshName: "production-mesh"
username: "my-bot"
enableGroups: true
dmPolicy: "pairing"
allowFrom:
- alice
- trusted-team
groupPolicy: "allowlist"
requireMention: true
groupPermissions:
alice/team:
creator: "alice"
group: "team"
groupPolicy: "open"
requireMention: false
allowFrom: []
tools:
allow:
- group:messaging
- group:sessions
- group:runtime
toolsBySender:
admin:
alsoAllow:
- exec
```
### Configuration Options
**Required:**
| Option | Type | Description |
|--------|------|-------------|
| `agentUrl` | string | ZTM Agent API URL |
| `permitSource` | string | Permit source: `"server"` (from permit server) or `"file"` (from local file) |
| `meshName` | string | Name of your ZTM mesh |
| `username` | string | Bot's ZTM username |
**Required (when permitSource is "server"):**
| Option | Type | Description |
|--------|------|-------------|
| `permitUrl` | string | Permit Server URL |
**Optional (when permitSource is "file"):**
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `permitFilePath` | string | - | Path to local permit.json file |
**Optional - Basic:**
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | boolean | `true` | Enable/disable account |
| `enableGroups` | boolean | `false` | Enable group chat support |
| `dmPolicy` | string | `"pairing"` | DM policy: `allow`, `deny`, `pairing` |
| `allowFrom` | string[] | `[]` | List of approved usernames |
| `apiTimeout` | number | `30000` | API timeout in milliseconds (1000-300000) |
**Optional - Group:**
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `groupPolicy` | string | `"allowlist"` | Default group policy: `open`, `allowlist`, `disabled` |
| `requireMention` | boolean | `true` | Require @mention for group messages (global default) |
| `groupPermissions` | object | `{}` | Per-group permission overrides |
### Group Permission Options
| Option | Type | Description |
|--------|------|-------------|
| `groupPolicy` | string | Policy for this group: `open`, `allowlist`, `disabled` |
| `requireMention` | boolean | Require @mention to process message (default: `true`) |
| `allowFrom` | string[] | Whitelist of allowed senders |
| `tools.allow` | string[] | Only allow these tools |
| `tools.deny` | string[] | Deny these tools |
| `toolsBySender` | object | Sender-specific tool overrides |
### Environment Variables
| Variable | Description |
|----------|-------------|
| `ZTM_CHAT_LOG_LEVEL` | Logging level: `debug`, `info`, `warn`, `error` |
## Message Processing Pipeline (ADR-010 Compliant)
This plugin follows **ADR-010: Multi-Layer Message Processing Pipeline** for consistent message handling:
### Architecture Layers
| Layer | Module | Responsibility |
|-------|--------|----------------|
| **Layer 1** | `watcher.ts` | Chat Processing (orchestration) |
| **Layer 2** | `processor.ts` | Message Processing (validation, watermark, normalization) |
| **Layer 3** | `policy-checker.ts` | **Policy Enforcement** (unified DM/Group checking) |
| **Layer 4** | `message-dispatcher.ts` | Dispatch (callback execution) |
| **Layer 5** | `dispatcher.ts` | Persistence (watermark updates) |
### Key Design Principle
**Unified Policy Checking (ADR-010 Layer 3):**
- **DM messages**: Check DM policy (pairing/allowlist/deny modes)
- **Group messages**: Check **ONLY** Group policy (DM policy is **NOT** applied)
- **Timing**: Policy checks happen **before** watermark updates
This fixes the critical bug where `dmPolicy:deny` was incorrectly rejecting group messages.
### DM Message Flow
```
1. Message arrives from ZTM
2. processPeerMessage() validates self-message check
3. checkMessagePolicy() applies DM policy (Layer 3)
4. processIncomingMessage() normalizes (Layer 2, skipPolicyCheck=true)
5. notifyMessageCallbacks() updates watermark (Layer 5)
6. handleInboundMessage() dispatches to AI agent (Layer 4)
```
### Group Message Flow
```
1. Message arrives from ZTM
2. processGroupMessage() validates self-message check
3. checkMessagePolicy() applies Group policy (Layer 3) - NOT DM policy
4. processIncomingMessage() normalizes (Layer 2, skipPolicyCheck=true)
5. notifyMessageCallbacks() updates watermark (Layer 5)
6. handleInboundMessage() dispatches to AI agent (Layer 4)
```
## Message Flow
### Direct Message (DM)
```mermaid
sequenceDiagram
participant U as ZTM User
participant A as ZTM Agent
participant P as Plugin
participant B as AI Agent
U->>A: 1. Send DM to bot
A->>A: 2. Store message
P->>A: 3. Poll /api/meshes/{mesh}/apps/ztm/chat/api/peers/{bot}/messages
A->>P: 4. Return new messages
P->>P: 5. Check DM policy (allow/deny/pairing)
alt Policy allows
P->>B: 6. Route to AI agent
B->>P: 7. Generate response
P->>A: 8. Send via Chat App API
A->>U: 9. Deliver to recipient
else Policy denied
P->>P: 6. Ignore message
end
```
### Group Message
```mermaid
sequenceDiagram
participant M as ZTM Member
participant A as ZTM Agent
participant P as Plugin
participant B as AI Agent
M->>A: 1. Send @mention to group
A->>A: 2. Store message
P->>A: 3. Poll Chat App API for new messages
A->>P: 4. Return new messages
P->>P: 5. Get group permissions (creator/groupId)
P->>P: 6. Check: creator? → allow
P->>P: 7. Check: policy (open/allowlist/disabled)
P->>P: 8. Check: allowFrom whitelist
P->>P: 9. Check: requireMention (@{bot})
alt All checks pass
P->>B: 10. Route to AI agent (with tools filter)
B->>P: 11. Generate response
P->>A: 12. Send reply to group
A->>M: 13. Deliver to all members
else Any check fails
P->>P: Log and ignore message
end
```
### Message Processing Pipeline
```mermaid
flowchart TD
A[Incoming Message] --> B{isGroup?}
B -->|No| C[DM Flow]
B -->|Yes| D[Group Flow]
C --> C1{DM Policy}
C1 -->|allow| E[Route to AI]
C1 -->|deny| F[Ignore]
C1 -->|pairing| G{Paired?}
G -->|Yes| E
G -->|No| H[Send Pairing Request]
D --> D1{Get Group Permissions}
D1 --> D2{Is Creator?}
D2 -->|Yes| E
D2 -->|No| D3{Policy}
D3 -->|disabled| F
D3 -->|allowlist| D4{In allowFrom?}
D3 -->|open| D5{requireMention?}
D4 -->|Yes| E
D4 -->|No| F
D5 -->|Yes| D6{Mention?}
D5 -->|No| E
D6 -->|Yes| E
D6 -->|No| F
E --> E1{Resolve Agent Route}
E1 --> E2{Dispatch to Agent}
E2 --> E3{Filter Tools}
E3 --> I[Generate Response]
I --> J{Send Response}
J -->|DM| J1[sendZTMMessage]
J -->|Group| J2[sendGroupMessage]
F --> K[Log and Ignore]
H --> K
```
> **Note**: This diagram shows the **functional behavior** of policy checks from a user perspective. The internal implementation uses a unified policy checking architecture (ADR-010 Layer 3) where all policy decisions happen before message normalization. See [ADR-010: Multi-Layer Message Processing Pipeline](docs/adr/ADR-010-multi-layer-message-pipeline.md) for implementation details.
### Policy Decision Matrix
**DM Policy Check Order:**
| Step | Condition | Next Step |
|------|-----------|-----------|
| 1 | Empty sender | → Deny (ignore) |
| 2 | Sender in `allowFrom` config | → Allow (whitelisted) |
| 3 | Sender in pairing store | → Allow (whitelisted) |
| 4 | Policy = `allow` | → Allow (allowed) |
| 5 | Policy = `deny` | → Deny (denied) |
| 6 | Policy = `pairing` | → Send pairing request |
**Group Policy Check Order:**
| Step | Condition | Next Step |
|------|-----------|-----------|
| 1 | Empty sender | → Deny (ignore) |
| 2 | Sender = creator | → Step 6 (bypass policy) |
| 3 | groupPolicy = `disabled` | → Deny (denied) |
| 4 | groupPolicy = `allowlist` + not in allowFrom | → Deny (whitelisted) |
| 5 | groupPolicy = `open` | → Continue |
| 6 | requireMention = true + no @mention | → Deny (mention_required) |
| 7 | All checks passed | → Allow (creator/whitelisted/allowed) |
**Result Actions:**
| Action | Description |
|--------|-------------|
| `process` | Message allowed, route to AI agent |
| `ignore` | Message denied, log and discard |
| `pairing` | Send pairing request to user |
## ZTM API
The plugin uses the ZTM Agent API for identity/mesh operations and the Chat App API for messaging:
### Agent API (Identity & Mesh)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/identity` | Get agent identity (certificate) |
| GET | `/api/meshes/{meshName}` | Get mesh connection status |
| POST | `/api/meshes/{meshName}` | Join mesh with permit data |
### Chat App API
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/meshes/{meshName}/apps/ztm/chat/api/users` | List all users in mesh |
| GET | `/api/meshes/{meshName}/apps/ztm/chat/api/chats` | Get all chats (DMs and groups) |
| GET | `/api/meshes/{meshName}/apps/ztm/chat/api/peers/{peer}/messages` | Get peer messages (with since/before) |
| POST | `/api/meshes/{meshName}/apps/ztm/chat/api/peers/{peer}/messages` | Send message to peer |
| GET | `/api/meshes/{meshName}/apps/ztm/chat/api/groups/{creator}/{group}/messages` | Get group messages |
| POST | `/api/meshes/{meshName}/apps/ztm/chat/api/groups/{creator}/{group}/messages` | Send message to group |
### Message API Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `since` | number | Get messages after this timestamp (Unix ms) |
| `before` | number | Get messages before this timestamp (Unix ms) |
| `limit` | number | Maximum number of messages to return (default: 50) |
### Example API Calls
```bash
# Get agent identity (certificate)
curl http://localhost:7777/api/identity
# Get mesh status
curl http://localhost:7777/api/meshes/openclaw-mesh
# Join mesh (with permit data)
curl -X POST http://localhost:7777/api/meshes/openclaw-mesh \
-H "Content-Type: application/json" \
-d '{"ca":"...","agent":{"certificate":"..."},"bootstraps":["host:port"]}'
# List users
curl http://localhost:7777/api/meshes/openclaw-mesh/apps/ztm/chat/api/users
# Get peer messages (last 10)
curl "http://localhost:7777/api/meshes/openclaw-mesh/apps/ztm/chat/api/peers/alice/messages?limit=10"
# Send message to peer
curl -X POST http://localhost:7777/api/meshes/openclaw-mesh/apps/ztm/chat/api/peers/alice/messages \
-H "Content-Type: application/json" \
-d '{"text": "Hello!"}'
# Get group messages
curl "http://localhost:7777/api/meshes/openclaw-mesh/apps/ztm/chat/api/groups/alice/team/messages"
# Send message to group
curl -X POST http://localhost:7777/api/meshes/openclaw-mesh/apps/ztm/chat/api/groups/alice/team/messages \
-H "Content-Type: application/json" \
-d '{"text": "Hello everyone!"}'
```
## Troubleshooting
### Connection Failed
1. Verify ZTM Agent is running:
```bash
curl http://localhost:7777/api/meshes
```
2. Check mesh name matches:
```bash
curl http://localhost:7777/api/meshes
```
3. Check plugin logs:
```bash
openclaw logs --level debug --channel ztm-chat
```
### No Messages Received
1. Check bot username is correct in configuration
2. Verify ZTM Agent is running:
```bash
curl http://localhost:7777/api/meshes
```
3. Check mesh connectivity:
```bash
openclaw channels status ztm-chat --probe
```
## Development
### Running Tests
```bash
npm install
npm test # Run all tests
npm test:watch # Watch mode
npm run test:coverage # Run tests with coverage
```
### Debug Logging
```bash
ZTM_CHAT_LOG_LEVEL=debug openclaw restart
```