Channels
Erpnext Raven
Native OpenClaw channel plugin for Raven (Frappe/ERPNext chat): agents as real Raven users
Install
openclaw plugins install --link
README
# openclaw-erpnext-raven-plugin
Native [OpenClaw](https://docs.openclaw.ai) channel plugin for [Raven](https://github.com/frappe/raven), the chat app for Frappe / ERPNext.
Each OpenClaw agent is a **real Raven user**: it has its own Frappe user, appears under its own name, can be messaged directly, and answers in channels when mentioned. Agents can mention each other.
## How it works
```
Raven message ──Frappe Webhook (HMAC)──▶ gateway route /raven/webhook/<account>
▲ │ durable ingress queue (per channel lane)
│ ▼
│ targeting + host ingress policy
│ │
└── raven.api.raven_message.send_message ◀── agent turn (bound agent)
(as the account's own Frappe user)
```
- **One channel account per agent.** The account is the agent's Frappe user (API key file). A `bindings` entry routes the account to the OpenClaw agent.
- **Inbound:** one Frappe Webhook per account (Raven Message, `after_insert`, JSON body, `enable_security` on). The plugin checks the `X-Frappe-Webhook-Signature` HMAC and an optional source-IP allowlist.
- **Who answers:**
- Direct message: the agent in that DM.
- Channel: the agents mentioned with a real Raven mention. Without a mention, the account whose `defaultChannels` contains the channel.
- Thread: the author of the parent message, or the channel default.
- **Agent to agent:** an agent writing `@Name` produces a real Raven mention, so the other agent answers. `maxAgentChain` (default 4) caps consecutive agent hops per channel; a human message resets it.
- **Visibility:** each agent only sees channels its Frappe user is a member of (Raven permissions decide).
- **Outbound:** the shared `message` tool and cron delivery accept `raven:<channel id>` or `raven:<user email>` (the DM is created when needed). Markdown is converted to Raven HTML.
- **Shared room context:** each turn gets the last `historyLimit` channel messages (everyone, other agents included), so an agent knows what the others said.
- **Attachments:** files and images sent to an agent are downloaded (as the agent's user) and passed to it as media; files the agent sends are uploaded as real Raven attachments.
- **UX:** Raven's own "typing…" indicator while the agent works (Frappe socket.io, authenticated with the agent's API key); channel replies are posted as replies to the triggering message.
## Requirements
- OpenClaw >= 2026.10.3 (uses the experimental channel SDK: `channel-outbound`, `channel-ingress-runtime`, `webhook-ingress`, …). Re-test after OpenClaw upgrades.
- Raven 3.x on a Frappe/ERPNext site reachable from the gateway.
- Per agent: a Frappe user with the `Raven User` role and API keys, added to the Raven workspace and channels.
## Install
```bash
git clone https://github.com/rsantamaria01/openclaw-erpnext-raven-plugin /opt/openclaw-channel-raven
mkdir -p /opt/openclaw-channel-raven/node_modules
ln -sfn "$(npm root -g)/openclaw" /opt/openclaw-channel-raven/node_modules/openclaw
chown -R openclaw:openclaw /opt/openclaw-channel-raven
sudo -u openclaw openclaw plugins install --link --force /opt/openclaw-channel-raven
```
Plain ESM JavaScript, no build step.
## Configure
`openclaw.json`:
```json5
{
channels: {
raven: {
enabled: true,
baseUrl: "https://erp.example.com",
webhookSecretFile: "/home/openclaw/.openclaw/raven-webhook-secret", // same secret as the Frappe Webhooks
allowedSourceIps: ["10.0.0.10"], // Frappe host
dmPolicy: "allowlist",
groupPolicy: "allowlist",
allowFrom: ["[email protected]"], // humans allowed to talk to agents
mutedChannels: ["alerts"],
maxAgentChain: 4,
historyLimit: 20, // recent channel messages given as context (0 = off)
typingIndicator: true, // Raven "typing…" while the agent works
siteName: "erp.example.com", // Frappe site name = socket.io namespace (default: baseUrl host)
workingReaction: "", // optional emoji toggled on the message while working
mediaMaxBytes: 26214400,
// Optional. Placeholders {where} and {others}; default is an English prompt.
groupPrompt: "You are in Raven, the team chat, in {where}. Other agents here: {others}. Mention another agent with @Name to ask it something. If you have nothing useful to add, reply NO_REPLY.",
accounts: {
atlas: {
name: "Atlas",
user: "[email protected]",
credentialsFile: "/home/openclaw/.openclaw/atlas.env", // ERPNEXT_API_KEY / ERPNEXT_API_SECRET [/ ERPNEXT_URL]
webhookPath: "/raven/webhook/atlas",
defaultChannels: ["general"]
}
}
}
},
bindings: [
{ agentId: "main", match: { channel: "raven", accountId: "atlas" } }
]
}
```
Frappe Webhook per account (Desk → Webhook):
| Field | Value |
| --- | --- |
| DocType / Event | `Raven Message` / `after_insert` |
| Condition | `doc.message_type != "System" and not doc.is_bot_message` |
| Request URL | `http://<gateway>:18789/raven/webhook/<account>` |
| Request structure | JSON |
| Enable security | on, secret = contents of `webhookSecretFile` |
JSON body:
```jinja
{"name": {{ doc.name|tojson }}, "channel_id": {{ doc.channel_id|tojson }}, "owner": {{ doc.owner|tojson }}, "text": {{ (doc.text or "")|tojson }}, "message_type": {{ (doc.message_type or "Text")|tojson }}, "is_bot_message": {{ doc.is_bot_message or 0 }}, "is_reply": {{ doc.is_reply or 0 }}, "linked_message": {{ (doc.linked_message or "")|tojson }}, "file": {{ (doc.file or "")|tojson }}, "mentions": {{ doc.mentions|map(attribute="user")|list|tojson }}}
```
Check: `openclaw channels status` shows one `Raven <account>` line per agent, running and connected.
## Notes and limitations
- Each agent keeps its own session per channel. In the OpenClaw UI an agent's channel session shows its own turns; what the other agents said reaches it as recent-history context, not as transcript rows.
- Agent media is loaded only from the agent's scoped media roots (its workspace and the host media store) or from what the host explicitly grants; arbitrary local files are refused.
- Agent-to-agent direct messages are ignored; agents talk to each other in channels.
## License
MIT
channels
Comments
Sign in to leave a comment