Plugin maintainer reference
Подання повідомлень
Представлення повідомлень — це спільний контракт OpenClaw для насиченого вихідного інтерфейсу чату. Воно дає агентам, командам CLI, потокам затвердження та плагінам змогу один раз описати намір повідомлення, тоді як кожен плагін каналу відтворює найкращу доступну нативну форму.
Використовуйте представлення для переносного інтерфейсу повідомлень:
- текстові секції
- короткий контекстний текст або текст нижнього колонтитула
- розділювачі
- кнопки
- меню вибору
- заголовок і тон картки
Не додавайте до спільного інструмента повідомлень нові поля, нативні для провайдерів, як-от Discord components, Slack
blocks, Telegram buttons, Teams card або Feishu card. Це виходи рендерера, за які відповідає плагін каналу.
Контракт
Автори плагінів імпортують публічний контракт із:
MessagePresentation, ReplyPayloadDelivery,} from "openclaw/plugin-sdk/interactive-runtime";Форма:
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 MessagePresentationAction = | { type: "command"; command: string } | { type: "callback"; value: string }; type MessagePresentationButton = { label: string; action?: MessagePresentationAction; /** Legacy callback value. Prefer action for new controls. */ value?: string; url?: string; webApp?: { url: string }; /** @deprecated Use webApp. Accepted for legacy JSON payloads only. */ web_app?: { url: string }; priority?: number; disabled?: boolean; reusable?: boolean; style?: "primary" | "secondary" | "success" | "danger";}; type MessagePresentationOption = { label: string; action?: MessagePresentationAction; /** Legacy callback value. Prefer action for new controls. */ value?: string;}; type ReplyPayloadDelivery = { pin?: | boolean | { enabled: boolean; notify?: boolean; required?: boolean; };};Семантика кнопок:
action.type: "command"запускає нативну slash-команду через шлях команд ядра. Використовуйте це для вбудованих командних кнопок і меню.action.type: "callback"передає непрозорі дані плагіна через шлях взаємодії каналу. Плагіни каналів не повинні переінтерпретовувати callback-дані як slash-команди.value— це застаріле непрозоре callback-значення. Нові елементи керування мають використовуватиaction, щоб плагіни каналів могли зіставляти команди й callback-и без здогадок за текстом.url— це кнопка-посилання. Вона може існувати безvalue.webAppописує нативну для каналу кнопку вебзастосунку. Telegram відтворює її якweb_appі підтримує лише в приватних чатах.web_appдосі приймається у вільних JSON-навантаженнях для сумісності, але TypeScript-продюсери мають використовуватиwebApp.labelє обов'язковим і також використовується в текстовому fallback.styleмає рекомендаційний характер. Рендерери мають зіставляти непідтримувані стилі з безпечним значенням за замовчуванням, а не провалювати надсилання.priorityє необов'язковим. Коли канал оголошує обмеження дій і елементи керування потрібно відкинути, ядро спочатку зберігає кнопки з вищим пріоритетом і зберігає початковий порядок серед кнопок з однаковим пріоритетом. Коли всі елементи керування вміщуються, зберігається авторський порядок.disabledє необов'язковим. Канали мають явно підтримати це черезsupportsDisabled; інакше ядро знижує disabled-елемент керування до неінтерактивного fallback-тексту.reusableє необов'язковим. Канали, що підтримують повторно використовувані нативні callback-и, можуть залишати дію доступною після успішної взаємодії. Використовуйте це для повторюваних або ідемпотентних дій, як-от оновлення, перегляд або докладніші відомості; залишайте невстановленим для звичайних одноразових затверджень і деструктивних дій.
Семантика вибору:
options[].actionмає те саме значення command/callback, що й кнопковийaction.options[].value— це застаріле вибране значення застосунку.placeholderмає рекомендаційний характер і може ігноруватися каналами без нативної підтримки вибору.- Якщо канал не підтримує вибори, fallback-текст перелічує мітки.
Приклади продюсерів
Проста картка:
{ "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:
{ "blocks": [ { "type": "text", "text": "Release notes are ready." }, { "type": "buttons", "buttons": [{ "label": "Open notes", "url": "https://example.com/release" }] } ]}Кнопка Telegram Mini App:
{ "blocks": [ { "type": "buttons", "buttons": [{ "label": "Launch", "web_app": { "url": "https://example.com/app" } }] } ]}Меню вибору:
{ "title": "Choose environment", "blocks": [ { "type": "select", "placeholder": "Environment", "options": [ { "label": "Canary", "value": "env:canary" }, { "label": "Production", "value": "env:prod" } ] } ]}Надсилання через 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"}]}]}'Закріплена доставка:
openclaw message send --channel telegram \ --target -1001234567890 \ --message "Topic opened" \ --pinЗакріплена доставка з явним JSON:
{ "pin": { "enabled": true, "notify": true, "required": false }}Контракт рендерера
Плагіни каналів оголошують підтримку рендерингу у своєму вихідному адаптері:
const adapter: ChannelOutboundAdapter = { deliveryMode: "direct", presentationCapabilities: { supported: true, buttons: true, selects: true, context: true, divider: true, limits: { actions: { maxActions: 25, maxActionsPerRow: 5, maxRows: 5, maxLabelLength: 80, maxValueBytes: 100, supportsStyles: true, supportsDisabled: false, }, selects: { maxOptions: 25, maxLabelLength: 100, maxValueBytes: 100, }, text: { maxLength: 2000, encoding: "characters", markdownDialect: "discord-markdown", }, }, }, 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 }); },};Булеві значення можливостей описують, що рендерер може зробити інтерактивним. Необов'язкові limits описують загальну оболонку, яку ядро може адаптувати перед викликом рендерера:
type ChannelPresentationCapabilities = { supported?: boolean; buttons?: boolean; selects?: boolean; context?: boolean; divider?: boolean; limits?: { actions?: { maxActions?: number; maxActionsPerRow?: number; maxRows?: number; maxLabelLength?: number; maxValueBytes?: number; supportsStyles?: boolean; supportsDisabled?: boolean; supportsLayoutHints?: boolean; }; selects?: { maxOptions?: number; maxLabelLength?: number; maxValueBytes?: number; }; text?: { maxLength?: number; encoding?: "characters" | "utf8-bytes" | "utf16-units"; markdownDialect?: "plain" | "markdown" | "html" | "slack-mrkdwn" | "discord-markdown"; supportsEdit?: boolean; }; };};Ядро застосовує загальні обмеження до семантичних елементів керування перед рендерингом. Рендерери все одно відповідають за фінальну специфічну для провайдера валідацію та обрізання кількості нативних блоків, розміру картки, обмежень URL і особливостей провайдера, які неможливо виразити в загальному контракті. Якщо обмеження вилучають усі елементи керування з блока, ядро зберігає мітки як неінтерактивний контекстний текст, щоб доставлене повідомлення все одно мало видимий fallback.
Потік рендерингу ядра
Коли ReplyPayload або дія повідомлення містить presentation, ядро:
- Нормалізує навантаження представлення.
- Визначає вихідний адаптер цільового каналу.
- Читає
presentationCapabilities. - Застосовує загальні обмеження можливостей, як-от кількість дій, довжина мітки та кількість варіантів вибору, коли адаптер їх оголошує.
- Викликає
renderPresentation, коли адаптер може відрендерити навантаження. - Переходить до консервативного текстового fallback, коли адаптера немає або він не може рендерити.
- Надсилає отримане навантаження через звичайний шлях доставки каналу.
- Застосовує метадані доставки, як-от
delivery.pin, після першого успішно надісланого повідомлення.
Ядро відповідає за fallback-поведінку, щоб продюсери могли залишатися незалежними від каналів. Плагіни каналів відповідають за нативний рендеринг і обробку взаємодій.
Правила деградації
Представлення має бути безпечним для надсилання на обмежених каналах.
Fallback-текст включає:
titleяк перший рядок- блоки
textяк звичайні абзаци - блоки
contextяк компактні контекстні рядки - блоки
dividerяк візуальний розділювач - мітки кнопок, зокрема URL для кнопок-посилань
- мітки варіантів вибору
Видимість fallback для значень кнопок
Коли канал не може відрендерити інтерактивні елементи керування, значення кнопок і вибору переходять у звичайний текст. Fallback-поведінка зберігає зручність використання, водночас тримаючи непрозорі callback-дані приватними:
- Дії з типом
commandрендеряться якlabel: \command``, щоб користувачі могли скопіювати команду й запустити її вручну у введенні каналу. - Дії з типом
callbackі застарілі поляvalueрендеряться лише як мітка. Непрозоре callback-значення не розкривається у fallback-тексті. - Кнопки
url/webAppрендерять текст URL поруч із міткою кнопки, оскільки URL призначений для користувача. - Варіанти вибору рендеряться лише як мітка. Базове значення варіанта не розкривається у fallback-тексті.
Адаптери каналів, які додають у свій fallback-інтерфейс інструкції для ручних команд (наприклад, інструкції для коментарів до документів Feishu), мають визначати перевірку наявності команди з тих самих блоків представлення, які використовує fallback-рендерер, щоб текст підказки з'являвся лише тоді, коли ручна команда справді показана.
Непідтримувані нативні елементи керування мають деградувати, а не провалювати все надсилання. Приклади:
- Telegram з вимкненими inline-кнопками надсилає текстовий fallback.
- Канал без підтримки вибору перелічує варіанти вибору як текст.
- Кнопка лише з URL стає або нативною кнопкою-посиланням, або fallback-рядком URL.
- Необов'язкові помилки закріплення не провалюють доставлене повідомлення.
Головний виняток — delivery.pin.required: true; якщо закріплення запитано як обов'язкове, а канал не може закріпити надіслане повідомлення, доставка повідомляє про помилку.
Зіставлення провайдерів
Поточні вбудовані рендерери:
| Канал | Нативна ціль рендерингу | Примітки |
|---|---|---|
| Discord | Компоненти та контейнери компонентів | Зберігає застаріле channelData.discord.components для наявних виробників provider-native payload, але нові спільні надсилання мають використовувати presentation. |
| Slack | Block Kit | Зберігає застаріле channelData.slack.blocks для наявних виробників provider-native payload, але нові спільні надсилання мають використовувати presentation. |
| Telegram | Текст плюс вбудовані клавіатури | Кнопки/вибори потребують можливості вбудованих кнопок для цільової поверхні; інакше використовується текстовий fallback. |
| Mattermost | Текст плюс інтерактивні props | Інші блоки деградують до тексту. |
| Microsoft Teams | Adaptive Cards | Звичайний текст message додається до картки, коли надано обидва. |
| Feishu | Інтерактивні картки | Заголовок картки може використовувати title; тіло уникає дублювання цього заголовка. |
| Звичайні канали | Текстовий fallback | Канали без рендерера все одно отримують читабельний вивід. |
Сумісність provider-native payload є перехідною можливістю для наявних виробників відповідей. Це не причина додавати нові спільні нативні поля.
Presentation проти InteractiveReply
InteractiveReply — це старіша внутрішня підмножина, яку використовують helper-и
схвалення та взаємодії. Вона підтримує:
- текст
- кнопки
- вибори
MessagePresentation — це канонічний спільний контракт надсилання. Він додає:
- заголовок
- тон
- контекст
- розділювач
- кнопки лише з URL
- загальні метадані доставки через
ReplyPayload.delivery
Використовуйте helper-и з openclaw/plugin-sdk/interactive-runtime під час
зв’язування зі старішим кодом:
OC_I18N_900011
Новий код має напряму приймати або створювати MessagePresentation. Наявні
payload-и interactive є застарілою підмножиною presentation; підтримка в runtime
зберігається для старіших виробників.
Застарілі типи InteractiveReply* та helper-и перетворення позначені
@deprecated у SDK:
InteractiveReply,InteractiveReplyBlock,InteractiveReplyButton,InteractiveReplyOption,InteractiveReplySelectBlockіInteractiveReplyTextBlocknormalizeInteractiveReply(...)hasInteractiveReplyBlocks(...)interactiveReplyToPresentation(...)presentationToInteractiveReply(...)presentationToInteractiveControlsReply(...)resolveInteractiveTextFallback(...)reduceInteractiveReply(...)
presentationToInteractiveReply(...) і
presentationToInteractiveControlsReply(...) залишаються доступними як мости
рендерера для застарілих реалізацій каналів. Новий код-виробник не має викликати
їх; надсилайте presentation і дозвольте адаптації core/каналу виконати
рендеринг.
Helper-и схвалення також мають заміни, орієнтовані насамперед на presentation:
- використовуйте
buildApprovalPresentationFromActionDescriptors(...)замістьbuildApprovalInteractiveReplyFromActionDescriptors(...) - використовуйте
buildApprovalPresentation(...)замістьbuildApprovalInteractiveReply(...) - використовуйте
buildExecApprovalPresentation(...)замістьbuildExecApprovalInteractiveReply(...)
renderMessagePresentationFallbackText(...) повертає порожній рядок для блоків
presentation, які не мають текстового fallback, наприклад presentation лише з
розділювачем. Транспорти, яким потрібне непорожнє тіло надсилання, можуть передати
emptyFallback, щоб увімкнути мінімальне тіло без зміни стандартного контракту
fallback.
Закріплення доставки
Закріплення — це поведінка доставки, а не presentation. Використовуйте
delivery.pin замість provider-native полів, таких як channelData.telegram.pin.
Семантика:
pin: trueзакріплює перше успішно доставлене повідомлення.pin.notifyза замовчуванням має значенняfalse.pin.requiredза замовчуванням має значенняfalse.- Необов’язкові збої закріплення деградують і залишають надіслане повідомлення без змін.
- Обов’язкові збої закріплення спричиняють збій доставки.
- Поділені на частини повідомлення закріплюють першу доставлену частину, а не останню.
Ручні дії повідомлення pin, unpin і pins досі існують для наявних
повідомлень, коли provider підтримує ці операції.
Контрольний список автора Plugin
- Оголошуйте
presentationзdescribeMessageTool(...), коли канал може рендерити або безпечно деградувати семантичну presentation. - Додайте
presentationCapabilitiesдо runtime-адаптера вихідних повідомлень. - Реалізуйте
renderPresentationу runtime-коді, а не в коді налаштування control-plane Plugin. - Не допускайте нативні UI-бібліотеки до гарячих шляхів setup/catalog.
- Оголошуйте загальні обмеження можливостей у
presentationCapabilities.limits, коли вони відомі. - Зберігайте фінальні обмеження платформи в рендерері та тестах.
- Додайте fallback-тести для непідтримуваних кнопок, виборів, URL-кнопок,
дублювання заголовка/тексту та змішаних надсилань
messageплюсpresentation. - Додайте підтримку закріплення доставки через
deliveryCapabilities.pinіpinDeliveredMessageлише тоді, коли provider може закріпити id надісланого повідомлення. - Не відкривайте нові provider-native поля картки/блока/компонента/кнопки через спільну схему дій повідомлення.