Skip to content

feat(mattermost): interactive buttons + channel name resolution#18151

Closed
tonydehnke wants to merge 9 commits intoopenclaw:mainfrom
tonydehnke:fix/mattermost-channel-name-target
Closed

feat(mattermost): interactive buttons + channel name resolution#18151
tonydehnke wants to merge 9 commits intoopenclaw:mainfrom
tonydehnke:fix/mattermost-channel-name-target

Conversation

@tonydehnke
Copy link
Contributor

@tonydehnke tonydehnke commented Feb 16, 2026

Summary

Adds interactive message buttons and channel name resolution for the Mattermost extension.

Depends on #19265 (directory adapter for channel/user name resolution).

Interactive buttons

  • interactions.ts: HMAC-SHA256 token verification (sorted keys), localhost-only validation, HTTP callback handler, button builder with type: "button"
  • client.ts: props param on createMattermostPost, new fetchMattermostPost and updateMattermostPost functions
  • send.ts: props passthrough in MattermostSendOpts
  • monitor.ts: register interaction HTTP route via registerPluginHttpRoute
  • channel.ts: send action with supportsButtons, button attachment building, media passthrough

Channel name resolution

  • normalize.ts: #channel-name targets return undefined (triggering directory lookup) instead of channel:name (which skipped directory and used raw name as ID, causing 403)
  • looksLikeMattermostTargetId: removed # from known-target check so #name goes through directory

Button click behavior

  • On click: ALL buttons are removed and replaced with "✓ [name] selected by @user"
  • Uses both callback update response (immediate UI refresh) and REST API PUT /posts/{id} (WebSocket clients)
  • All buttons removed on click because Mattermost strips integration data from API GET responses, making remaining buttons inert

Changes

File Change
interactions.ts (new) Full interactive callback system: HMAC tokens (sorted keys), localhost validation, button builder with type: "button", fetch-then-update handler
client.ts Add props to createMattermostPost, add fetchMattermostPost, add updateMattermostPost
send.ts Add props to MattermostSendOpts and pass through
channel.ts Add send action independent of reactions config, supportsButtons, handleSendAction with button attachment building
monitor.ts Register /mattermost/interactions/:accountId HTTP route, init HMAC secret from bot token
normalize.ts #nameundefined (directory lookup), looksLikeId returns false for #
channel.test.ts Updated test expectations for independent send action

Key implementation details

  • HMAC sorted keys: JSON.stringify(context, Object.keys(context).sort()) — Mattermost reorders context keys when storing payloads
  • type: "button" required: Without it, buttons render but callbacks silently never fire
  • Remove all on click: Mattermost strips integration (URL + context + token) from GET responses, so remaining buttons would lose their callback config
  • Dual update strategy: Callback update response for clicking user + REST API PUT for all clients

Test plan

  • npx oxfmt --check extensions/mattermost/ — formatting clean
  • pnpm tsgo — no new type errors
  • CI — all checks passing (Linux + Windows)
  • Manual test: openclaw message send --buttons with multiple buttons
  • Manual test: click button → all buttons removed, "✓ selected" shown
  • Manual test: #off-topic target resolves via directory (was 403, now works)
  • Manual test: bare off-topic target resolves via directory
  • Manual test: channel:<id> direct target still works

…AllowFrom

When a message in a channel is dropped because the sender is not in
groupAllowFrom (e.g. another agent's bot reply), the message is now
saved as pending history. This preserves conversation context so that
when an agent is later @mentioned, it sees the full conversation
including other agents' replies.

Previously, the early return in the groupAllowFrom check meant these
messages were silently discarded with no history record, causing agents
to miss parts of multi-agent channel conversations.
@openclaw-barnacle openclaw-barnacle bot added channel: mattermost Channel integration: mattermost size: S labels Feb 16, 2026
@steipete steipete closed this Feb 16, 2026
@steipete steipete reopened this Feb 17, 2026
Enables agents to send messages using channel names (e.g. target: "infra")
instead of 26-character Mattermost IDs.

- Add directory.ts: multi-account channel/user discovery
- Add directory property to channel plugin
- Fix normalize: bare names fall through to directory lookup
- Fix looksLikeId: strict 26-char regex + DM format

Closes openclaw#19264
…stPeers

Both functions accepted a limit param but never applied it to the
returned results. Slice entries before returning when limit is set.

Reported by Greptile review bot.
Keep both directory adapter and new actions adapter from upstream.
@tonydehnke tonydehnke force-pushed the fix/mattermost-channel-name-target branch from 9ced58b to 08ad331 Compare February 20, 2026 04:39
@tonydehnke tonydehnke changed the title fix(mattermost): resolve #channel-name targets to channel IDs feat(mattermost): interactive buttons + channel name resolution Feb 20, 2026
…ution

Interactive message buttons:
- New `interactions.ts`: HMAC-SHA256 token verification (sorted keys),
  localhost-only validation, HTTP callback handler, button builder with
  required `type: "button"` field
- `client.ts`: add `props` to `createMattermostPost`, new
  `fetchMattermostPost` and `updateMattermostPost` functions
- `send.ts`: `props` passthrough in `MattermostSendOpts`
- `monitor.ts`: register `/mattermost/interactions/:accountId` HTTP
  route, init HMAC secret from bot token
- `channel.ts`: `send` action (independent of reactions config),
  `supportsButtons`, `handleSendAction` with button attachment building

Button click behavior:
- All buttons removed on click (Mattermost strips integration data from
  GET responses, making remaining buttons inert)
- Replaced with "✓ [name] selected by @user"
- Uses both callback update response and REST API PUT for reliable
  UI refresh across all connected clients

Channel name resolution:
- `normalize.ts`: `#channel-name` returns `undefined` (triggering
  directory lookup) instead of `channel:name` (which skipped directory
  and caused 403)
- `looksLikeMattermostTargetId`: `#name` no longer treated as known
  target, allowing directory adapter resolution
@tonydehnke tonydehnke force-pushed the fix/mattermost-channel-name-target branch from 239ffa9 to 1be9e50 Compare February 20, 2026 06:28
The core message tool schema defines buttons as Array<Array<Button>>
(rows of buttons for Telegram's inline keyboard layout). The Mattermost
extension was treating this as a flat 1D array, so each "button" was
actually a row array — btn.text/btn.id resolved to undefined, producing
empty names that Mattermost rejected with "action must have a name".

Use flatMap to flatten rows into a single list (Mattermost has no row
layout) and filter out any buttons with empty id or name.
Use Object.defineProperty for socket mock and proper cast for destroy
to satisfy strict type checking with tsgo.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 21, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
@tonydehnke
Copy link
Contributor Author

Closing in favor of #19957 which now includes all features from this PR:

  • Interactive buttons with HMAC verification
  • Directory adapter for channel/user name resolution
  • Config schema and TypeScript types for interactions
  • Channel-level tests

Reactions were dropped since they landed upstream via #18608.

@tonydehnke tonydehnke closed this Feb 21, 2026
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 21, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 23, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 23, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 23, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 23, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 24, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 24, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 26, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 26, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 27, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Feb 27, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Mar 2, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Mar 2, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Mar 2, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Mar 2, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Mar 2, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Mar 2, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Mar 5, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
tonydehnke added a commit to tonydehnke/openclaw that referenced this pull request Mar 5, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
mukhtharcm pushed a commit to tonydehnke/openclaw that referenced this pull request Mar 5, 2026
…ests

Port missing pieces from PR openclaw#18151:
- Directory adapter for channel/user name resolution (listGroups, listPeers)
- Config schema validation for interactions.callbackBaseUrl
- TypeScript types for interactions config
- Channel-level tests for send/buttons action support
- Fix listActions to include "send" alongside "react"
mukhtharcm pushed a commit to tonydehnke/openclaw that referenced this pull request Mar 5, 2026
The core sends buttons as Array<Array<Button>> (2D for Telegram row
layout). The consolidation from openclaw#18151 into openclaw#19957 lost the flatMap
that flattens to 1D and the .filter() that drops malformed buttons.

Without flatMap, each "button" is actually a row array — btn.text is
undefined, producing empty-name buttons that render as white boxes
with a blue left border in Mattermost.
@tonydehnke tonydehnke deleted the fix/mattermost-channel-name-target branch March 8, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: mattermost Channel integration: mattermost size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants