mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
feat(mattermost): add interactive buttons support (#19957)
Merged via squash.
Prepared head SHA: 8a25e60872
Co-authored-by: tonydehnke <36720180+tonydehnke@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
This commit is contained in:
@@ -175,6 +175,151 @@ Config:
|
||||
- `channels.mattermost.actions.reactions`: enable/disable reaction actions (default true).
|
||||
- Per-account override: `channels.mattermost.accounts.<id>.actions.reactions`.
|
||||
|
||||
## Interactive buttons (message tool)
|
||||
|
||||
Send messages with clickable buttons. When a user clicks a button, the agent receives the
|
||||
selection and can respond.
|
||||
|
||||
Enable buttons by adding `inlineButtons` to the channel capabilities:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
capabilities: ["inlineButtons"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Use `message action=send` with a `buttons` parameter. Buttons are a 2D array (rows of buttons):
|
||||
|
||||
```
|
||||
message action=send channel=mattermost target=channel:<channelId> buttons=[[{"text":"Yes","callback_data":"yes"},{"text":"No","callback_data":"no"}]]
|
||||
```
|
||||
|
||||
Button fields:
|
||||
|
||||
- `text` (required): display label.
|
||||
- `callback_data` (required): value sent back on click (used as the action ID).
|
||||
- `style` (optional): `"default"`, `"primary"`, or `"danger"`.
|
||||
|
||||
When a user clicks a button:
|
||||
|
||||
1. All buttons are replaced with a confirmation line (e.g., "✓ **Yes** selected by @user").
|
||||
2. The agent receives the selection as an inbound message and responds.
|
||||
|
||||
Notes:
|
||||
|
||||
- Button callbacks use HMAC-SHA256 verification (automatic, no config needed).
|
||||
- Mattermost strips callback data from its API responses (security feature), so all buttons
|
||||
are removed on click — partial removal is not possible.
|
||||
- Action IDs containing hyphens or underscores are sanitized automatically
|
||||
(Mattermost routing limitation).
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.mattermost.capabilities`: array of capability strings. Add `"inlineButtons"` to
|
||||
enable the buttons tool description in the agent system prompt.
|
||||
|
||||
### Direct API integration (external scripts)
|
||||
|
||||
External scripts and webhooks can post buttons directly via the Mattermost REST API
|
||||
instead of going through the agent's `message` tool. Use `buildButtonAttachments()` from
|
||||
the extension when possible; if posting raw JSON, follow these rules:
|
||||
|
||||
**Payload structure:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channel_id: "<channelId>",
|
||||
message: "Choose an option:",
|
||||
props: {
|
||||
attachments: [
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
id: "mybutton01", // alphanumeric only — see below
|
||||
type: "button", // required, or clicks are silently ignored
|
||||
name: "Approve", // display label
|
||||
style: "primary", // optional: "default", "primary", "danger"
|
||||
integration: {
|
||||
url: "http://localhost:18789/mattermost/interactions/default",
|
||||
context: {
|
||||
action_id: "mybutton01", // must match button id (for name lookup)
|
||||
action: "approve",
|
||||
// ... any custom fields ...
|
||||
_token: "<hmac>", // see HMAC section below
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Critical rules:**
|
||||
|
||||
1. Attachments go in `props.attachments`, not top-level `attachments` (silently ignored).
|
||||
2. Every action needs `type: "button"` — without it, clicks are swallowed silently.
|
||||
3. Every action needs an `id` field — Mattermost ignores actions without IDs.
|
||||
4. Action `id` must be **alphanumeric only** (`[a-zA-Z0-9]`). Hyphens and underscores break
|
||||
Mattermost's server-side action routing (returns 404). Strip them before use.
|
||||
5. `context.action_id` must match the button's `id` so the confirmation message shows the
|
||||
button name (e.g., "Approve") instead of a raw ID.
|
||||
6. `context.action_id` is required — the interaction handler returns 400 without it.
|
||||
|
||||
**HMAC token generation:**
|
||||
|
||||
The gateway verifies button clicks with HMAC-SHA256. External scripts must generate tokens
|
||||
that match the gateway's verification logic:
|
||||
|
||||
1. Derive the secret from the bot token:
|
||||
`HMAC-SHA256(key="openclaw-mattermost-interactions", data=botToken)`
|
||||
2. Build the context object with all fields **except** `_token`.
|
||||
3. Serialize with **sorted keys** and **no spaces** (the gateway uses `JSON.stringify`
|
||||
with sorted keys, which produces compact output).
|
||||
4. Sign: `HMAC-SHA256(key=secret, data=serializedContext)`
|
||||
5. Add the resulting hex digest as `_token` in the context.
|
||||
|
||||
Python example:
|
||||
|
||||
```python
|
||||
import hmac, hashlib, json
|
||||
|
||||
secret = hmac.new(
|
||||
b"openclaw-mattermost-interactions",
|
||||
bot_token.encode(), hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
ctx = {"action_id": "mybutton01", "action": "approve"}
|
||||
payload = json.dumps(ctx, sort_keys=True, separators=(",", ":"))
|
||||
token = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
|
||||
|
||||
context = {**ctx, "_token": token}
|
||||
```
|
||||
|
||||
Common HMAC pitfalls:
|
||||
|
||||
- Python's `json.dumps` adds spaces by default (`{"key": "val"}`). Use
|
||||
`separators=(",", ":")` to match JavaScript's compact output (`{"key":"val"}`).
|
||||
- Always sign **all** context fields (minus `_token`). The gateway strips `_token` then
|
||||
signs everything remaining. Signing a subset causes silent verification failure.
|
||||
- Use `sort_keys=True` — the gateway sorts keys before signing, and Mattermost may
|
||||
reorder context fields when storing the payload.
|
||||
- Derive the secret from the bot token (deterministic), not random bytes. The secret
|
||||
must be the same across the process that creates buttons and the gateway that verifies.
|
||||
|
||||
## Directory adapter
|
||||
|
||||
The Mattermost plugin includes a directory adapter that resolves channel and user names
|
||||
via the Mattermost API. This enables `#channel-name` and `@username` targets in
|
||||
`openclaw message send` and cron/webhook deliveries.
|
||||
|
||||
No configuration is needed — the adapter uses the bot token from the account config.
|
||||
|
||||
## Multi-account
|
||||
|
||||
Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||
@@ -197,3 +342,10 @@ Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||
- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
||||
- Auth errors: check the bot token, base URL, and whether the account is enabled.
|
||||
- Multi-account issues: env vars only apply to the `default` account.
|
||||
- Buttons appear as white boxes: the agent may be sending malformed button data. Check that each button has both `text` and `callback_data` fields.
|
||||
- Buttons render but clicks do nothing: verify `AllowedUntrustedInternalConnections` in Mattermost server config includes `127.0.0.1 localhost`, and that `EnablePostActionIntegration` is `true` in ServiceSettings.
|
||||
- Buttons return 404 on click: the button `id` likely contains hyphens or underscores. Mattermost's action router breaks on non-alphanumeric IDs. Use `[a-zA-Z0-9]` only.
|
||||
- Gateway logs `invalid _token`: HMAC mismatch. Check that you sign all context fields (not a subset), use sorted keys, and use compact JSON (no spaces). See the HMAC section above.
|
||||
- Gateway logs `missing _token in context`: the `_token` field is not in the button's context. Ensure it is included when building the integration payload.
|
||||
- Confirmation shows raw ID instead of button name: `context.action_id` does not match the button's `id`. Set both to the same sanitized value.
|
||||
- Agent doesn't know about buttons: add `capabilities: ["inlineButtons"]` to the Mattermost channel config.
|
||||
|
||||
Reference in New Issue
Block a user