Caricamento in corso...
Caricamento in corso...
Last synced: Today, 22:00
Technical reference for the OpenClaw framework. Real-time synchronization with the official documentation engine.
Use this file to discover all available pages before exploring further.
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:
Do not add new provider-native fields such as Discord
componentsblocksbuttonscardcardPlugin authors import the public contract from:
tsimport type { MessagePresentation, ReplyPayloadDelivery, } from "openclaw/plugin-sdk/interactive-runtime";
Shape:
tstype MessagePresentation = { title?: string; tone?: "neutral" | "info" | "success" | "warning" | "danger"; blocks: MessagePresentationBlock[]; }; type MessagePresentationBlock = | { type: "text"; text: string } | { type: "context"; text: string } | { type: "divider" } | { type: "buttons"; buttons: MessagePresentationButton[] } | { type: "select"; placeholder?: string; options: MessagePresentationOption[] }; type MessagePresentationButton = { label: string; value?: string; url?: string; style?: "primary" | "secondary" | "success" | "danger"; }; type MessagePresentationOption = { label: string; value: string; }; type ReplyPayloadDelivery = { pin?: | boolean | { enabled: boolean; notify?: boolean; required?: boolean; }; };
Button semantics:
valueurlvaluelabelstyleSelect semantics:
options[].valueplaceholderSimple card:
json{ "title": "Deploy approval", "tone": "warning", "blocks": [ { "type": "text", "text": "Canary is ready to promote." }, { "type": "context", "text": "Build 1234, staging passed." }, { "type": "buttons", "buttons": [ { "label": "Approve", "value": "deploy:approve", "style": "success" }, { "label": "Decline", "value": "deploy:decline", "style": "danger" } ] } ] }
URL-only link button:
json{ "blocks": [ { "type": "text", "text": "Release notes are ready." }, { "type": "buttons", "buttons": [{ "label": "Open notes", "url": "https://example.com/release" }] } ] }
Select menu:
json{ "title": "Choose environment", "blocks": [ { "type": "select", "placeholder": "Environment", "options": [ { "label": "Canary", "value": "env:canary" }, { "label": "Production", "value": "env:prod" } ] } ] }
CLI send:
bashopenclaw message send --channel slack \ --target channel:C123 \ --message "Deploy approval" \ --presentation '{"title":"Deploy approval","tone":"warning","blocks":[{"type":"text","text":"Canary is ready."},{"type":"buttons","buttons":[{"label":"Approve","value":"deploy:approve","style":"success"},{"label":"Decline","value":"deploy:decline","style":"danger"}]}]}'
Pinned delivery:
bashopenclaw message send --channel telegram \ --target -1001234567890 \ --message "Topic opened" \ --pin
Pinned delivery with explicit JSON:
json{ "pin": { "enabled": true, "notify": true, "required": false } }
Channel plugins declare render support on their outbound adapter:
tsconst adapter: ChannelOutboundAdapter = { deliveryMode: "direct", presentationCapabilities: { supported: true, buttons: true, selects: true, context: true, divider: true, }, deliveryCapabilities: { pin: true, }, renderPresentation({ payload, presentation, ctx }) { return renderNativePayload(payload, presentation, ctx); }, async pinDeliveredMessage({ target, messageId, pin }) { await pinNativeMessage(target, messageId, { notify: pin.notify === true }); }, };
Capability fields are intentionally simple booleans. They describe what the renderer can make interactive, not every native platform limit. Renderers still own platform-specific limits such as maximum button count, block count, and card size.
When a
ReplyPayloadpresentationpresentationCapabilitiesrenderPresentationdelivery.pinCore owns fallback behavior so producers can stay channel-agnostic. Channel plugins own native rendering and interaction handling.
Presentation must be safe to send on limited channels.
Fallback text includes:
titletextcontextdividerUnsupported native controls should degrade rather than fail the whole send. Examples:
The main exception is
delivery.pin.required: trueCurrent bundled renderers:
| Channel | Native render target | Notes |
|---|---|---|
| Discord | Components and component containers | Preserves legacy text channelData.discord.componentstext presentation |
| Slack | Block Kit | Preserves legacy text channelData.slack.blockstext 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 text message |
| Feishu | Interactive cards | Card header can use text title |
| Plain channels | Text fallback | Channels without a renderer still get readable output. |
Provider-native payload compatibility is a transition affordance for existing reply producers. It is not a reason to add new shared native fields.
InteractiveReplyMessagePresentationReplyPayload.deliveryUse helpers from
openclaw/plugin-sdk/interactive-runtimetsimport { interactiveReplyToPresentation, normalizeMessagePresentation, presentationToInteractiveReply, renderMessagePresentationFallbackText, } from "openclaw/plugin-sdk/interactive-runtime";
New code should accept or produce
MessagePresentationPinning is delivery behavior, not presentation. Use
delivery.pinchannelData.telegram.pinSemantics:
pin: truepin.notifyfalsepin.requiredfalseManual
pinunpinpinspresentationdescribeMessageTool(...)presentationCapabilitiesrenderPresentationmessagepresentationdeliveryCapabilities.pinpinDeliveredMessage© 2024 TaskFlow Mirror
Powered by TaskFlow Sync Engine