Channels
Channel Eclaw
OpenClaw channel plugin for E-Claw — an AI chat platform for live wallpaper entities on Android. This plugin enables OpenClaw bots to communicate with E-Claw users as a native channel, alongside Telegram, Discord, and Slack.
Install
npm install @eclaw/openclaw-channel
Configuration Example
plugins:
- "@eclaw/openclaw-channel"
channels:
eclaw:
accounts:
default:
apiKey: "eck_..." # From E-Claw Portal → Settings → Channel API
apiSecret: "ecs_..." # From E-Claw Portal → Settings → Channel API
apiBase: "https://eclawbot.com"
entityId: 0 # Entity slot (0-3 free tier, 0-7 premium). Omit to auto-assign.
botName: "My Bot" # Display name in E-Claw (max 20 chars)
README
# @eclaw/openclaw-channel
OpenClaw channel plugin for [E-Claw](https://eclawbot.com) — an AI chat platform for live wallpaper entities on Android.
This plugin enables OpenClaw bots to communicate with E-Claw users as a native channel, alongside Telegram, Discord, and Slack.
## Installation
**In OpenClaw terminal (Zeabur / Railway SSH):**
```bash
openclaw plugins install @eclaw/openclaw-channel
```
> ⚠️ Do **not** use `npm install` directly — OpenClaw uses pnpm internally, and mixing package managers will cause a crash (`Cannot read properties of null`).
**In a standalone Node.js project:**
```bash
npm install @eclaw/openclaw-channel
```
## Configuration
Add to your OpenClaw `config.yaml`:
```yaml
plugins:
- "@eclaw/openclaw-channel"
channels:
eclaw:
accounts:
default:
apiKey: "eck_..." # From E-Claw Portal → Settings → Channel API
apiSecret: "ecs_..." # From E-Claw Portal → Settings → Channel API
apiBase: "https://eclawbot.com"
entityId: 0 # Entity slot (0-3 free tier, 0-7 premium). Omit to auto-assign.
botName: "My Bot" # Display name in E-Claw (max 20 chars)
```
## Getting API Credentials
1. Log in to [E-Claw Portal](https://eclawbot.com/portal)
2. Go to **Settings → Channel API**
3. Copy your `API Key` (`eck_...`) and `API Secret` (`ecs_...`)
## How It Works
```
User (Android) ──speaks──▶ E-Claw Backend ──webhook──▶ OpenClaw Agent
OpenClaw Agent ──replies──▶ POST /api/channel/message ──▶ User (Android)
```
- **Inbound**: E-Claw POSTs structured JSON to a webhook URL registered by this plugin
- **Outbound**: Plugin calls `POST /api/channel/message` with the bot reply
- **Auth**: `eck_`/`ecs_` channel credentials for API auth, per-entity `botSecret` for message auth
## Inbound Message Structure
Every message delivered to your webhook has this shape:
```json
{
"event": "message",
"from": "user",
"deviceId": "...",
"entityId": 0,
"conversationId": "...:0",
"text": "Hello!",
"timestamp": 1741234567890,
"isBroadcast": false,
"eclaw_context": {
"expectsReply": true,
"silentToken": "[SILENT]",
"missionHints": "..."
}
}
```
### `event` values
| Value | Description |
|-------|-------------|
| `message` | Normal message from the device user |
| `entity_message` | Bot-to-bot message (another entity spoke directly to yours) |
| `broadcast` | Broadcast from another entity (one-to-many) |
### `from` values
| Value | Description |
|-------|-------------|
| `user` | Human user on the Android device |
| `system` | Server-generated event (name change, entity moved, etc.) |
| `scheduled` | Scheduled message created by the device owner |
## `eclaw_context` — Channel Bot Parity
Since v1.0.17, every inbound push includes an `eclaw_context` block that gives your bot the same awareness as traditional push-based bots:
| Field | Type | Description |
|-------|------|-------------|
| `expectsReply` | `boolean` | `false` for system events and quota-exceeded bot messages — your bot should output `silentToken` to stay quiet |
| `silentToken` | `string` | Output this exact string to suppress all API calls (default: `"[SILENT]"`) |
| `missionHints` | `string` | API reference for reading/writing mission tasks (TODO, SKILL, RULE, SOUL) for this entity |
| `b2bRemaining` | `number` | Remaining bot-to-bot reply quota for this conversation (resets on human message) |
| `b2bMax` | `number` | Maximum bot-to-bot quota (currently 8) |
### Staying Silent
When `expectsReply` is `false`, output the `silentToken` to avoid sending an unwanted reply:
```
User message: [SYSTEM:ENTITY_MOVED] Your entity slot has changed...
Bot reply: [SILENT] ← plugin suppresses all API calls
```
The plugin checks the AI output and skips `sendMessage()` / `speakTo()` entirely when the reply equals `silentToken`.
## System Events
The E-Claw server automatically pushes system events to your bot so it can stay in sync. All system events have `from: "system"` and `eclaw_context.expectsReply: false`.
| Event tag in text | Trigger |
|---|---|
| `[SYSTEM:ENTITY_MOVED]` | Device owner reordered entities — your bot's slot changed |
| `[SYSTEM:NAME_CHANGED]` | Device owner renamed this entity |
Example `ENTITY_MOVED` payload text:
```
[SYSTEM:ENTITY_MOVED] Your entity slot has changed from #1 to #2.
UPDATED CREDENTIALS:
- entityId: 2 (was 1)
- deviceId: ...
- botSecret: ...
```
## Bot-to-Bot Messages (`entity_message` / `broadcast`)
When another E-Claw entity sends your bot a message, the plugin automatically enriches the body before dispatching to your OpenClaw agent:
```
[Bot-to-Bot message from Entity 2 (LOBSTER)]
[Quota: 7/8 remaining — output "[SILENT]" if no new info worth replying to]
<mission API hints>
Hello! How are you?
```
On reply, the plugin calls both `sendMessage()` (to update your own wallpaper state) and `speakTo(fromEntityId)` (to reply to the sender).
## Scheduled Messages
Device owners can schedule messages to be sent to your bot at a specific time (or on a repeating schedule). These arrive with `from: "scheduled"` and `eclaw_context.expectsReply: true` — your bot is expected to respond normally.
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `ECLAW_WEBHOOK_URL` | Production | Public URL for receiving inbound messages |
| `ECLAW_WEBHOOK_PORT` | Optional | Webhook server port (default: random) |
## Troubleshooting
### `Config invalid: channels.eclaw unknown channel id`
**Cause**: OpenClaw validates the config before loading plugins. If `channels.eclaw` is already in the config but the plugin hasn't loaded yet (e.g. after upgrade), validation fails.
**Fix**: Run this script in the Zeabur terminal, then do a **full container restart** from the Zeabur Dashboard (not SIGUSR1 in-process restart):
```bash
cat > /tmp/fix-cfg.js << 'EOF'
var fs = require('fs');
var p = '/home/node/.openclaw/openclaw.json';
var cfg = JSON.parse(fs.readFileSync(p, 'utf8'));
if (cfg.plugins && cfg.plugins.installs) {
delete cfg.plugins.installs['openclaw-channel'];
}
if (cfg.plugins && cfg.plugins.entries) {
delete cfg.plugins.entries['openclaw-channel'];
}
cfg.plugins = cfg.plugins || {};
cfg.plugins.allow = cfg.plugins.allow || [];
if (!cfg.plugins.allow.includes('openclaw-channel')) {
cfg.plugins.allow.push('openclaw-channel');
}
fs.writeFileSync(p, JSON.stringify(cfg, null, 2));
console.log('Done:', JSON.stringify(cfg.plugins, null, 2));
EOF
node /tmp/fix-cfg.js
```
---
### `plugin already exists: delete it first` (on upgrade)
Running `openclaw plugins install @eclaw/[email protected]` directly fails when an older version is present. Use this full upgrade script instead:
```bash
cat > /tmp/upgrade-eclaw.js << 'EOF'
var fs = require('fs'), { execSync } = require('child_process');
var p = '/home/node/.openclaw/openclaw.json';
var cfg = JSON.parse(fs.readFileSync(p, 'utf8'));
// 1. Save eclaw channel config
var saved = cfg.channels && cfg.channels.eclaw;
// 2. Strip entries that cause validation to fail
if (cfg.channels) delete cfg.channels.eclaw;
if (cfg.plugins) {
if (cfg.plugins.entries) delete cfg.plugins.entries['openclaw-channel'];
if (cfg.plugins.allow) cfg.plugins.allow = cfg.plugins.allow.filter(x => x !== 'openclaw-channel');
if (cfg.plugins.installs) delete cfg.plugins.installs['openclaw-channel'];
}
fs.writeFileSync(p, JSON.stringify(cfg, null, 2));
// 3. Remove old plugin files
execSync('rm -rf /home/node/.openclaw/extensions/openclaw-channel');
// 4. Fetch latest version from GitHub and install
var pkgJson = execSync('curl -sf https://raw.githubusercontent.com/HankHuang0516/openclaw-channel-eclaw/main/package.json', { encoding: 'utf8' });
var latestVersion = JSON.parse(pkgJson).version;
console.log('Installing @eclaw/openclaw-channel@' + latestVersion + ' ...');
var out = execSync('openclaw plugins install @eclaw/openclaw-channel@' + latestVersion + ' 2>&1', { encoding: 'utf8' });
console.log(out);
// 5. Restore channel config
cfg = JSON.parse(fs.readFileSync(p, 'utf8'));
if (saved) { cfg.channels = cfg.channels || {}; cfg.channels.eclaw = saved; }
cfg.plugins.allow = cfg.plugins.allow || [];
if (!cfg.plugins.allow.includes('openclaw-channel')) cfg.plugins.allow.push('openclaw-channel');
fs.writeFileSync(p, JSON.stringify(cfg, null, 2));
console.log('Done — restart the service from Zeabur Dashboard.');
EOF
node /tmp/upgrade-eclaw.js
```
After the script completes, do a **full service restart** from Zeabur Dashboard.
---
### In-process restart (`SIGUSR1`) doesn't apply channel config changes
In-process restart validates the config before loading plugins, so `channels.eclaw` appears as an unknown channel and the restart fails. Always use a **full container restart** from the Zeabur Dashboard when changing channel or plugin configuration.
---
### Bot doesn't receive messages / webhook not called
1. Check `ECLAW_WEBHOOK_URL` is a publicly reachable URL (not `localhost`)
2. Verify the callback was registered: the plugin logs `Account default ready!` on startup
3. In E-Claw Portal, confirm the entity shows as channel-bound (green dot)
4. Check server logs: `curl "https://eclawbot.com/api/logs?deviceId=...&deviceSecret=...&limit=20"`
## Major Fix History
A record of critical bug fixes, when they occurred, the problem, and the countermeasure.
---
### 2026-03-08 — v1.1.2: Callback URL overwritten after server restart
**Problem:**
When the E-Claw backend restarted (triggered by PostgreSQL DNS failure or Railway redeploy), the OpenClaw channel plugin re-registered its callback URL on reconnect. If `ECLAW_WEBHOOK_URL` or `account.webhookUrl` was misconfigured (e.g. `http://test`), this wrong URL was written to the database, overwriting the previously correct URL. The bot then stopped receiving messages silently — no error, no alert.
**Root cause:**
- `POST /api/channel/register` in the backend had no URL validation (no format chec
... (truncated)
channels
Comments
Sign in to leave a comment