Message Presentation
Message presentation is OpenClaw’s shared contract for rich outbound chat UI. It lets agents, CLI commands, approval flows, and plugins describe the message intent once, while each channel plugin renders the best native shape it can. Use presentation for portable message UI:- text sections
- small context/footer text
- dividers
- buttons
- select menus
- card title and tone
components, Slack
blocks, Telegram buttons, Teams card, or Feishu card to the shared
message tool. Those are renderer outputs owned by the channel plugin.
Contract
Plugin authors import the public contract from:valueis an application action value routed back through the channel’s existing interaction path when the channel supports clickable controls.urlis a link button. It can exist withoutvalue.labelis required and is also used in text fallback.styleis advisory. Renderers should map unsupported styles to a safe default, not fail the send.
options[].valueis the selected application value.placeholderis advisory and may be ignored by channels without native select support.- If a channel does not support selects, fallback text lists the labels.
Producer Examples
Simple card:Renderer Contract
Channel plugins declare render support on their outbound adapter:Core Render Flow
When aReplyPayload or message action includes presentation, core:
- Normalizes the presentation payload.
- Resolves the target channel’s outbound adapter.
- Reads
presentationCapabilities. - Calls
renderPresentationwhen the adapter can render the payload. - Falls back to conservative text when the adapter is absent or cannot render.
- Sends the resulting payload through the normal channel delivery path.
- Applies delivery metadata such as
delivery.pinafter the first successful sent message.
Degradation Rules
Presentation must be safe to send on limited channels. Fallback text includes:titleas the first linetextblocks as normal paragraphscontextblocks as compact context linesdividerblocks as a visual separator- button labels, including URLs for link buttons
- select option labels
- Telegram with inline buttons disabled sends text fallback.
- A channel without select support lists select options as text.
- A URL-only button becomes either a native link button or a fallback URL line.
- Optional pin failures do not fail the delivered message.
delivery.pin.required: true; if pinning is requested as
required and the channel cannot pin the sent message, delivery reports failure.
Provider Mapping
Current bundled renderers:| Channel | Native render target | Notes |
|---|---|---|
| Discord | Components and component containers | Preserves legacy channelData.discord.components for existing provider-native payload producers, but new shared sends should use presentation. |
| Slack | Block Kit | Preserves legacy channelData.slack.blocks for existing provider-native payload producers, but new shared sends should use presentation. |
| Telegram | Text plus inline keyboards | Buttons/selects require inline button capability for the target surface; otherwise text fallback is used. |
| Mattermost | Text plus interactive props | Other blocks degrade to text. |
| Microsoft Teams | Adaptive Cards | Plain message text is included with the card when both are provided. |
| Feishu | Interactive cards | Card header can use title; body avoids duplicating that title. |
| Plain channels | Text fallback | Channels without a renderer still get readable output. |
Presentation vs InteractiveReply
InteractiveReply is the older internal subset used by approval and interaction
helpers. It supports:
- text
- buttons
- selects
MessagePresentation is the canonical shared send contract. It adds:
- title
- tone
- context
- divider
- URL-only buttons
- generic delivery metadata through
ReplyPayload.delivery
openclaw/plugin-sdk/interactive-runtime when bridging older
code:
MessagePresentation directly.
Delivery Pin
Pinning is delivery behavior, not presentation. Usedelivery.pin instead of
provider-native fields such as channelData.telegram.pin.
Semantics:
pin: truepins the first successfully delivered message.pin.notifydefaults tofalse.pin.requireddefaults tofalse.- Optional pin failures degrade and leave the sent message intact.
- Required pin failures fail delivery.
- Chunked messages pin the first delivered chunk, not the tail chunk.
pin, unpin, and pins message actions still exist for existing
messages where the provider supports those operations.
Plugin Author Checklist
- Declare
presentationfromdescribeMessageTool(...)when the channel can render or safely degrade semantic presentation. - Add
presentationCapabilitiesto the runtime outbound adapter. - Implement
renderPresentationin runtime code, not control-plane plugin setup code. - Keep native UI libraries out of hot setup/catalog paths.
- Preserve platform limits in the renderer and tests.
- Add fallback tests for unsupported buttons, selects, URL buttons, title/text
duplication, and mixed
messagepluspresentationsends. - Add delivery pin support through
deliveryCapabilities.pinandpinDeliveredMessageonly when the provider can pin the sent message id. - Do not expose new provider-native card/block/component/button fields through the shared message action schema.