@flomesh/ztm-chat
v2026.3.11
Published
ZTM Chat channel plugin for OpenClaw
Readme
ZTM Chat Channel Plugin for OpenClaw
This plugin integrates OpenClaw with ZTM (Zero Trust Mesh) Chat, enabling decentralized P2P messaging through the ZTM network.
Architecture
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 --> PairingData Flow:
- User sends message to ZTM Agent
- Plugin polls Chat App API for new messages
- Dispatcher checks DM/Group policy
- If allowed, route to AI Agent
- AI Agent generates response
- Plugin sends response via Chat App API
- 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:
# 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 version2. Start ZTM Agent
ztm start agentThe agent will start listening on http://localhost:7777 by default.
3. Install Plugin
Option A: Install from npm (recommended for users)
openclaw plugins install @flomesh/ztm-chatOption B: Local development installation
# 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
openclaw onboardSelect "ZTM Chat (P2P)" from the channel list.
The wizard will guide you through:
- ZTM Agent URL (default:
http://localhost:7777) - Permit Source Selection
- Permit Source:
server(from permit server) orfile(from local file) - Permit Server URL (when server):
https://clawparty.flomesh.io:7779/permit - Permit File Path (when file): path to local permit.json
- Permit Source:
- Bot Username (default:
openclaw-bot) - Security Settings
- DM Policy:
pairing(recommended),allow, ordeny - Allow From: Whitelist of usernames (or
*for all)
- DM Policy:
- Group Chat Settings (if enabled)
- Enable Groups: Yes/No
- Group Policy:
allowlist,open, ordisabled - Require Mention: Yes/No (default: Yes)
- Summary & Save
5. Restart OpenClaw
openclaw gateway restartGroup 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
flowchart TB
subgraph Remote["Remote User Environment"]
User1["Member 1"]
User2["Member 2"]
User3["Member 3"]
RemotePlugin["ZTM Chat Plugin<br/>(HTTP API Client)"]
RemoteAgent["ZTM Agent<br/>(Remote)"]
RemoteFiles["Message Files<br/>(Remote)"]
end
subgraph P2P["ZTM P2P Network"]
Hub["Mesh Hub<br/>(Coordinates Sync)"]
end
subgraph Local["Local Bot Environment"]
LocalAgent["ZTM Agent<br/>(Local)"]
LocalFiles["Message Files<br/>(Local)"]
LocalPlugin["ZTM Chat Plugin<br/>(HTTP API Client)"]
subgraph PluginFlow["Bot Message Processing"]
Watch["Watch Loop<br/>(1s interval)"]
Classify["Classify Changes<br/>(peer/group)"]
Fetch["Fetch Messages<br/>(HTTP API)"]
Policy["Policy Check"]
Dispatch["Dispatch Callbacks"]
AI["AI Agent"]
Send["Send Response<br/>(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"| User3Mention 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 morningNote: 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:
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: disabledTool Restrictions
Control which tools are available in each group:
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
- fsTool 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 messagesgroup: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"):
- New users must be approved before they can send messages
- When an unapproved user sends a message, the bot sends them a pairing code
- Approve users using the CLI with their pairing code
List Pending Requests
openclaw pairing list ztm-chatApprove a Pairing Request
openclaw pairing approve ztm-chat <code>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
# 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 instructionsChannel Commands
# 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 groupsPairing Commands
# List pending pairing requests
openclaw pairing list ztm-chat
# Approve a pairing request
openclaw pairing approve ztm-chat <code>Configuration
Configuration File
Configuration is stored in openclaw.yaml under channels.ztm-chat:
Mode 1: Server (from permit server)
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)
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:
- execConfiguration 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)
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
endGroup Message
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
endMessage Processing Pipeline
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 --> KNote: 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 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
# 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
Verify ZTM Agent is running:
curl http://localhost:7777/api/meshesCheck mesh name matches:
curl http://localhost:7777/api/meshesCheck plugin logs:
openclaw logs --level debug --channel ztm-chat
No Messages Received
- Check bot username is correct in configuration
- Verify ZTM Agent is running:
curl http://localhost:7777/api/meshes - Check mesh connectivity:
openclaw channels status ztm-chat --probe
Development
Running Tests
npm install
npm test # Run all tests
npm test:watch # Watch mode
npm run test:coverage # Run tests with coverageDebug Logging
ZTM_CHAT_LOG_LEVEL=debug openclaw restart