Plugins
Presentación de mensajes
La presentación de mensajes es el contrato compartido de OpenClaw para una interfaz de chat saliente enriquecida. Permite que agentes, comandos de CLI, flujos de aprobación y plugins describan la intención del mensaje una vez, mientras cada Plugin de canal representa la mejor forma nativa que pueda.
Usa la presentación para una interfaz de mensajes portable:
- secciones de texto
- texto breve de contexto/pie
- divisores
- botones
- menús de selección
- título y tono de tarjeta
No agregues nuevos campos nativos de proveedor como components de Discord, blocks de Slack,
buttons de Telegram, card de Teams o card de Feishu a la herramienta
compartida de mensajes. Esas son salidas de renderizador propiedad del Plugin de canal.
Contrato
Los autores de Plugin importan el contrato público desde:
MessagePresentation,
ReplyPayloadDelivery,
} from "openclaw/plugin-sdk/interactive-runtime";
Forma:
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;
};
};
Semántica de botones:
valuees un valor de acción de la aplicación que se enruta de vuelta por la ruta de interacción existente del canal cuando el canal admite controles clicables.urles un botón de enlace. Puede existir sinvalue.labeles obligatorio y también se usa en el texto de reserva.stylees orientativo. Los renderizadores deben asignar los estilos no admitidos a un valor predeterminado seguro, no hacer que el envío falle.
Semántica de selección:
options[].valuees el valor de aplicación seleccionado.placeholderes orientativo y puede ser ignorado por canales sin compatibilidad nativa con selecciones.- Si un canal no admite selecciones, el texto de reserva enumera las etiquetas.
Ejemplos de productores
Tarjeta 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" }
]
}
]
}
Botón de enlace solo con URL:
{
"blocks": [
{ "type": "text", "text": "Release notes are ready." },
{
"type": "buttons",
"buttons": [{ "label": "Open notes", "url": "https://example.com/release" }]
}
]
}
Menú de selección:
{
"title": "Choose environment",
"blocks": [
{
"type": "select",
"placeholder": "Environment",
"options": [
{ "label": "Canary", "value": "env:canary" },
{ "label": "Production", "value": "env:prod" }
]
}
]
}
Envío con 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"}]}]}'
Entrega fijada:
openclaw message send --channel telegram \
--target -1001234567890 \
--message "Topic opened" \
--pin
Entrega fijada con JSON explícito:
{
"pin": {
"enabled": true,
"notify": true,
"required": false
}
}
Contrato del renderizador
Los plugins de canal declaran la compatibilidad de renderizado en su adaptador saliente:
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 });
},
};
Los campos de capacidades son booleanos intencionalmente simples. Describen lo que el renderizador puede hacer interactivo, no todos los límites de la plataforma nativa. Los renderizadores siguen siendo responsables de límites específicos de plataforma como el número máximo de botones, el número de bloques y el tamaño de tarjeta.
Flujo de renderizado del núcleo
Cuando un ReplyPayload o una acción de mensaje incluye presentation, el núcleo:
- Normaliza la carga útil de presentación.
- Resuelve el adaptador saliente del canal de destino.
- Lee
presentationCapabilities. - Llama a
renderPresentationcuando el adaptador puede renderizar la carga útil. - Recurre a texto conservador cuando el adaptador está ausente o no puede renderizar.
- Envía la carga útil resultante por la ruta normal de entrega del canal.
- Aplica metadatos de entrega como
delivery.pindespués del primer mensaje enviado correctamente.
El núcleo es responsable del comportamiento de reserva para que los productores puedan permanecer independientes del canal. Los plugins de canal son responsables del renderizado nativo y la gestión de interacciones.
Reglas de degradación
La presentación debe poder enviarse con seguridad en canales limitados.
El texto de reserva incluye:
titlecomo la primera línea- bloques
textcomo párrafos normales - bloques
contextcomo líneas de contexto compactas - bloques
dividercomo separador visual - etiquetas de botones, incluidas las URL para botones de enlace
- etiquetas de opciones de selección
Los controles nativos no admitidos deben degradarse en lugar de hacer fallar todo el envío. Ejemplos:
- Telegram con botones en línea desactivados envía texto de reserva.
- Un canal sin compatibilidad con selección enumera las opciones de selección como texto.
- Un botón solo con URL se convierte en un botón de enlace nativo o en una línea de URL de reserva.
- Los fallos opcionales al fijar no hacen fallar el mensaje entregado.
La excepción principal es delivery.pin.required: true; si se solicita fijar como
obligatorio y el canal no puede fijar el mensaje enviado, la entrega informa un fallo.
Mapeo de proveedores
Renderizadores incluidos actuales:
| Canal | Destino de renderizado nativo | Notas |
|---|---|---|
| Discord | Componentes y contenedores de componentes | Conserva channelData.discord.components heredado para productores existentes de cargas útiles nativas del proveedor, pero los nuevos envíos compartidos deben usar presentation. |
| Slack | Block Kit | Conserva channelData.slack.blocks heredado para productores existentes de cargas útiles nativas del proveedor, pero los nuevos envíos compartidos deben usar presentation. |
| Telegram | Texto más teclados en línea | Los botones/selecciones requieren capacidad de botón en línea para la superficie de destino; de lo contrario se usa texto de reserva. |
| Mattermost | Texto más propiedades interactivas | Otros bloques se degradan a texto. |
| Microsoft Teams | Adaptive Cards | El texto sin formato message se incluye con la tarjeta cuando ambos se proporcionan. |
| Feishu | Tarjetas interactivas | El encabezado de tarjeta puede usar title; el cuerpo evita duplicar ese título. |
| Canales simples | Texto de reserva | Los canales sin renderizador siguen recibiendo una salida legible. |
La compatibilidad con cargas útiles nativas del proveedor es una facilidad de transición para productores de respuestas existentes. No es una razón para agregar nuevos campos nativos compartidos.
Presentación frente a InteractiveReply
InteractiveReply es el subconjunto interno anterior usado por ayudantes de aprobación e interacción.
Admite:
- texto
- botones
- selecciones
MessagePresentation es el contrato canónico compartido de envío. Agrega:
- título
- tono
- contexto
- divisor
- botones solo con URL
- metadatos de entrega genéricos mediante
ReplyPayload.delivery
Usa ayudantes de openclaw/plugin-sdk/interactive-runtime al conectar código
anterior:
interactiveReplyToPresentation,
normalizeMessagePresentation,
presentationToInteractiveReply,
renderMessagePresentationFallbackText,
} from "openclaw/plugin-sdk/interactive-runtime";
El código nuevo debe aceptar o producir MessagePresentation directamente.
Fijación de entrega
Fijar es comportamiento de entrega, no presentación. Usa delivery.pin en lugar de
campos nativos del proveedor como channelData.telegram.pin.
Semántica:
pin: truefija el primer mensaje entregado correctamente.pin.notifytienefalsecomo valor predeterminado.pin.requiredtienefalsecomo valor predeterminado.- Los fallos opcionales al fijar se degradan y dejan intacto el mensaje enviado.
- Los fallos obligatorios al fijar hacen fallar la entrega.
- Los mensajes fragmentados fijan el primer fragmento entregado, no el fragmento final.
Las acciones manuales de mensaje pin, unpin y pins siguen existiendo para mensajes
existentes cuando el proveedor admite esas operaciones.
Lista de verificación para autores de Plugin
- Declara
presentationdesdedescribeMessageTool(...)cuando el canal puede renderizar o degradar de forma segura la presentación semántica. - Agrega
presentationCapabilitiesal adaptador saliente en tiempo de ejecución. - Implementa
renderPresentationen código de tiempo de ejecución, no en código de configuración de Plugin del plano de control. - Mantén las bibliotecas de interfaz nativa fuera de rutas activas de configuración/catálogo.
- Conserva los límites de plataforma en el renderizador y las pruebas.
- Agrega pruebas de reserva para botones no admitidos, selecciones, botones URL, duplicación de título/texto
y envíos mixtos de
messagemáspresentation. - Agrega compatibilidad con fijación de entrega mediante
deliveryCapabilities.pinypinDeliveredMessagesolo cuando el proveedor puede fijar el id del mensaje enviado. - No expongas nuevos campos nativos de proveedor de tarjeta/bloque/componente/botón mediante el esquema de acción de mensaje compartido.