Plugins
Présentation des messages
La présentation des messages est le contrat partagé d’OpenClaw pour une interface de chat sortante enrichie. Elle permet aux agents, commandes CLI, flux d’approbation et plugins de décrire une seule fois l’intention du message, tandis que chaque plugin de canal l’affiche sous la meilleure forme native possible.
Utilisez la présentation pour une interface de message portable :
- sections de texte
- petit texte de contexte/pied de page
- séparateurs
- boutons
- menus de sélection
- titre et ton de carte
N’ajoutez pas de nouveaux champs natifs de fournisseur comme Discord components, Slack blocks, Telegram buttons, Teams card ou Feishu card à l’outil de message partagé. Ce sont des sorties de rendu détenues par le plugin de canal.
Contrat
Les auteurs de plugins importent le contrat public depuis :
MessagePresentation,
ReplyPayloadDelivery,
} from "openclaw/plugin-sdk/interactive-runtime";
Forme :
type 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;
};
};
Sémantique des boutons :
valueest une valeur d’action applicative renvoyée par le chemin d’interaction existant du canal lorsque celui-ci prend en charge les contrôles cliquables.urlest un bouton de lien. Il peut exister sansvalue.labelest obligatoire et est aussi utilisé dans le texte de repli.styleest indicatif. Les moteurs de rendu doivent faire correspondre les styles non pris en charge à une valeur par défaut sûre, sans faire échouer l’envoi.
Sémantique de la sélection :
options[].valueest la valeur applicative sélectionnée.placeholderest indicatif et peut être ignoré par les canaux sans prise en charge native des sélections.- Si un canal ne prend pas en charge les sélections, le texte de repli liste les libellés.
Exemples de producteurs
Carte simple :
{
"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" }
]
}
]
}
Bouton de lien avec URL uniquement :
{
"blocks": [
{ "type": "text", "text": "Release notes are ready." },
{
"type": "buttons",
"buttons": [{ "label": "Open notes", "url": "https://example.com/release" }]
}
]
}
Menu de sélection :
{
"title": "Choose environment",
"blocks": [
{
"type": "select",
"placeholder": "Environment",
"options": [
{ "label": "Canary", "value": "env:canary" },
{ "label": "Production", "value": "env:prod" }
]
}
]
}
Envoi CLI :
openclaw 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"}]}]}'
Livraison épinglée :
openclaw message send --channel telegram \
--target -1001234567890 \
--message "Topic opened" \
--pin
Livraison épinglée avec JSON explicite :
{
"pin": {
"enabled": true,
"notify": true,
"required": false
}
}
Contrat de rendu
Les plugins de canal déclarent la prise en charge du rendu sur leur adaptateur sortant :
const 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 });
},
};
Les champs de capacité sont volontairement de simples booléens. Ils décrivent ce que le moteur de rendu peut rendre interactif, pas toutes les limites de la plateforme native. Les moteurs de rendu restent responsables des limites propres à chaque plateforme, comme le nombre maximal de boutons, le nombre de blocs et la taille de carte.
Flux de rendu du cœur
Lorsqu’un ReplyPayload ou une action de message inclut presentation, le cœur :
- Normalise la charge utile de présentation.
- Résout l’adaptateur sortant du canal cible.
- Lit
presentationCapabilities. - Appelle
renderPresentationlorsque l’adaptateur peut afficher la charge utile. - Revient à un texte prudent lorsque l’adaptateur est absent ou ne peut pas effectuer le rendu.
- Envoie la charge utile obtenue par le chemin de livraison normal du canal.
- Applique les métadonnées de livraison comme
delivery.pinaprès le premier message envoyé avec succès.
Le cœur détient le comportement de repli afin que les producteurs puissent rester indépendants des canaux. Les plugins de canal détiennent le rendu natif et la gestion des interactions.
Règles de dégradation
La présentation doit pouvoir être envoyée sans risque sur les canaux limités.
Le texte de repli inclut :
titlecomme première ligne- les blocs
textcomme paragraphes normaux - les blocs
contextcomme lignes de contexte compactes - les blocs
dividercomme séparateur visuel - les libellés de boutons, y compris les URL des boutons de lien
- les libellés des options de sélection
Les contrôles natifs non pris en charge doivent se dégrader plutôt que faire échouer tout l’envoi. Exemples :
- Telegram avec les boutons en ligne désactivés envoie un texte de repli.
- Un canal sans prise en charge des sélections liste les options de sélection sous forme de texte.
- Un bouton avec URL uniquement devient soit un bouton de lien natif, soit une ligne d’URL de repli.
- Les échecs d’épinglage facultatifs ne font pas échouer le message livré.
La principale exception est delivery.pin.required: true ; si l’épinglage est demandé comme obligatoire et que le canal ne peut pas épingler le message envoyé, la livraison signale un échec.
Correspondance des fournisseurs
Moteurs de rendu groupés actuels :
| Canal | Cible de rendu native | Notes |
|---|---|---|
| Discord | Composants et conteneurs de composants | Préserve l’ancien channelData.discord.components pour les producteurs de charges utiles natives de fournisseur existants, mais les nouveaux envois partagés doivent utiliser presentation. |
| Slack | Block Kit | Préserve l’ancien channelData.slack.blocks pour les producteurs de charges utiles natives de fournisseur existants, mais les nouveaux envois partagés doivent utiliser presentation. |
| Telegram | Texte avec claviers en ligne | Les boutons/sélections nécessitent la capacité de bouton en ligne pour la surface cible ; sinon, le texte de repli est utilisé. |
| Mattermost | Texte avec propriétés interactives | Les autres blocs se dégradent en texte. |
| Microsoft Teams | Adaptive Cards | Le texte message brut est inclus avec la carte lorsque les deux sont fournis. |
| Feishu | Cartes interactives | L’en-tête de carte peut utiliser title ; le corps évite de dupliquer ce titre. |
| Canaux simples | Texte de repli | Les canaux sans moteur de rendu reçoivent tout de même une sortie lisible. |
La compatibilité des charges utiles natives de fournisseur est une facilité de transition pour les producteurs de réponses existants. Ce n’est pas une raison d’ajouter de nouveaux champs natifs partagés.
Présentation ou InteractiveReply
InteractiveReply est l’ancien sous-ensemble interne utilisé par les assistants d’approbation et d’interaction. Il prend en charge :
- texte
- boutons
- sélections
MessagePresentation est le contrat canonique d’envoi partagé. Il ajoute :
- titre
- ton
- contexte
- séparateur
- boutons avec URL uniquement
- métadonnées de livraison génériques via
ReplyPayload.delivery
Utilisez les assistants de openclaw/plugin-sdk/interactive-runtime lors de l’adaptation d’ancien code :
interactiveReplyToPresentation,
normalizeMessagePresentation,
presentationToInteractiveReply,
renderMessagePresentationFallbackText,
} from "openclaw/plugin-sdk/interactive-runtime";
Le nouveau code doit accepter ou produire directement MessagePresentation.
Épinglage de livraison
L’épinglage est un comportement de livraison, pas une présentation. Utilisez delivery.pin au lieu de champs natifs de fournisseur comme channelData.telegram.pin.
Sémantique :
pin: trueépingle le premier message livré avec succès.pin.notifyvautfalsepar défaut.pin.requiredvautfalsepar défaut.- Les échecs d’épinglage facultatifs se dégradent et laissent le message envoyé intact.
- Les échecs d’épinglage obligatoires font échouer la livraison.
- Les messages découpés en morceaux épinglent le premier morceau livré, pas le dernier.
Les actions de message manuelles pin, unpin et pins existent toujours pour les messages existants lorsque le fournisseur prend en charge ces opérations.
Liste de contrôle pour les auteurs de plugins
- Déclarez
presentationdepuisdescribeMessageTool(...)lorsque le canal peut afficher ou dégrader sans risque la présentation sémantique. - Ajoutez
presentationCapabilitiesà l’adaptateur sortant d’exécution. - Implémentez
renderPresentationdans le code d’exécution, pas dans le code de configuration de plugin du plan de contrôle. - Gardez les bibliothèques d’interface native hors des chemins de configuration/catalogue critiques.
- Préservez les limites de la plateforme dans le moteur de rendu et les tests.
- Ajoutez des tests de repli pour les boutons non pris en charge, les sélections, les boutons d’URL, la duplication titre/texte et les envois mixtes
messagepluspresentation. - Ajoutez la prise en charge de l’épinglage de livraison via
deliveryCapabilities.pinetpinDeliveredMessageuniquement lorsque le fournisseur peut épingler l’identifiant du message envoyé. - N’exposez pas de nouveaux champs de carte/bloc/composant/bouton natifs de fournisseur via le schéma d’action de message partagé.