Plugin maintainer reference
Prezentacja wiadomości
Prezentacja wiadomości to wspólny kontrakt OpenClaw dla rozbudowanego interfejsu czatu wychodzącego. Pozwala agentom, poleceniom CLI, przepływom zatwierdzania i pluginom opisać intencję wiadomości raz, a każdy plugin kanału renderuje najlepszą natywną postać, jaką potrafi.
Używaj prezentacji dla przenośnego interfejsu wiadomości:
- sekcje tekstowe
- krótki tekst kontekstu/stopki
- separatory
- przyciski
- menu wyboru
- tytuł i ton karty
Nie dodawaj nowych pól natywnych dla dostawcy, takich jak Discord components, Slack
blocks, Telegram buttons, Teams card lub Feishu card, do współdzielonego
narzędzia wiadomości. To są wyniki renderera, których właścicielem jest plugin kanału.
Kontrakt
Autorzy Pluginów importują publiczny kontrakt z:
MessagePresentation, ReplyPayloadDelivery,} from "openclaw/plugin-sdk/interactive-runtime";Kształt:
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; };};Semantyka przycisków:
action.type: "command"uruchamia natywne polecenie z ukośnikiem przez ścieżkę poleceń rdzenia. Używaj tego dla wbudowanych przycisków poleceń i menu.action.type: "callback"przenosi nieprzezroczyste dane pluginu przez ścieżkę interakcji kanału. Pluginy kanałów nie mogą ponownie interpretować danych zwrotnych jako poleceń z ukośnikiem.valueto starsza nieprzezroczysta wartość zwrotna. Nowe kontrolki powinny używaćaction, aby pluginy kanałów mogły mapować polecenia i wywołania zwrotne bez zgadywania na podstawie tekstu.urlto przycisk linku. Może istnieć bezvalue.webAppopisuje natywny dla kanału przycisk aplikacji webowej. Telegram renderuje go jakoweb_appi obsługuje tylko w czatach prywatnych.web_appnadal jest akceptowane w luźnych ładunkach JSON dla kompatybilności, ale producenci TypeScript powinni używaćwebApp.labeljest wymagane i jest również używane w rezerwowym tekście.stylejest wskazówką. Renderery powinny mapować nieobsługiwane style na bezpieczną wartość domyślną, a nie powodować niepowodzenie wysyłki.priorityjest opcjonalne. Gdy kanał deklaruje limity akcji i kontrolki muszą zostać odrzucone, rdzeń zachowuje najpierw przyciski o wyższym priorytecie i zachowuje pierwotną kolejność wśród przycisków o równym priorytecie. Gdy wszystkie kontrolki się mieszczą, zachowywana jest kolejność autora.disabledjest opcjonalne. Kanały muszą jawnie włączyć tę obsługę przezsupportsDisabled; w przeciwnym razie rdzeń degraduje wyłączoną kontrolkę do nieinteraktywnego tekstu rezerwowego.reusablejest opcjonalne. Kanały obsługujące natywne wywołania zwrotne wielokrotnego użytku mogą utrzymać akcję dostępną po udanej interakcji. Używaj go dla powtarzalnych lub idempotentnych akcji, takich jak odświeżenie, inspekcja lub więcej szczegółów; pozostaw nieustawione dla zwykłych jednorazowych zatwierdzeń i akcji destrukcyjnych.
Semantyka wyboru:
options[].actionma takie samo znaczenie polecenia/wywołania zwrotnego jakactionprzycisku.options[].valueto starsza wybrana wartość aplikacji.placeholderjest wskazówką i może być ignorowane przez kanały bez natywnej obsługi wyboru.- Jeśli kanał nie obsługuje wyborów, tekst rezerwowy wyświetla etykiety.
Przykłady producenta
Prosta karta:
{ "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" } ] } ]}Przycisk linku tylko z adresem URL:
{ "blocks": [ { "type": "text", "text": "Release notes are ready." }, { "type": "buttons", "buttons": [{ "label": "Open notes", "url": "https://example.com/release" }] } ]}Przycisk Telegram Mini App:
{ "blocks": [ { "type": "buttons", "buttons": [{ "label": "Launch", "web_app": { "url": "https://example.com/app" } }] } ]}Menu wyboru:
{ "title": "Choose environment", "blocks": [ { "type": "select", "placeholder": "Environment", "options": [ { "label": "Canary", "value": "env:canary" }, { "label": "Production", "value": "env:prod" } ] } ]}Wysyłka 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"}]}]}'Przypięta dostawa:
openclaw message send --channel telegram \ --target -1001234567890 \ --message "Topic opened" \ --pinPrzypięta dostawa z jawnym JSON:
{ "pin": { "enabled": true, "notify": true, "required": false }}Kontrakt renderera
Pluginy kanałów deklarują obsługę renderowania na swoim adapterze wychodzącym:
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 }); },};Wartości logiczne możliwości opisują, co renderer może uczynić interaktywnym. Opcjonalne
limits opisują ogólną otoczkę, którą rdzeń może dostosować przed wywołaniem
renderera:
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; }; };};Rdzeń stosuje ogólne limity do semantycznych kontrolek przed renderowaniem. Renderery nadal odpowiadają za końcową walidację i przycinanie specyficzne dla dostawcy dla natywnej liczby bloków, rozmiaru karty, limitów URL oraz niuansów dostawcy, których nie da się wyrazić w ogólnym kontrakcie. Jeśli limity usuną każdą kontrolkę z bloku, rdzeń zachowuje etykiety jako nieinteraktywny tekst kontekstowy, aby dostarczona wiadomość nadal miała widoczną wersję rezerwową.
Przepływ renderowania rdzenia
Gdy ReplyPayload lub akcja wiadomości zawiera presentation, rdzeń:
- Normalizuje ładunek prezentacji.
- Rozwiązuje adapter wychodzący kanału docelowego.
- Odczytuje
presentationCapabilities. - Stosuje ogólne limity możliwości, takie jak liczba akcji, długość etykiety i liczba opcji wyboru, gdy adapter je deklaruje.
- Wywołuje
renderPresentation, gdy adapter może wyrenderować ładunek. - Przechodzi na zachowawczy tekst, gdy adapter jest nieobecny lub nie może renderować.
- Wysyła wynikowy ładunek zwykłą ścieżką dostarczania kanału.
- Stosuje metadane dostawy, takie jak
delivery.pin, po pierwszej udanej wysłanej wiadomości.
Rdzeń odpowiada za zachowanie rezerwowe, aby producenci mogli pozostać niezależni od kanałów. Pluginy kanałów odpowiadają za natywne renderowanie i obsługę interakcji.
Reguły degradacji
Prezentacja musi być bezpieczna do wysłania na ograniczonych kanałach.
Tekst rezerwowy zawiera:
titlejako pierwszy wiersz- bloki
textjako zwykłe akapity - bloki
contextjako zwarte wiersze kontekstu - bloki
dividerjako wizualny separator - etykiety przycisków, w tym adresy URL dla przycisków linków
- etykiety opcji wyboru
Widoczność rezerwowa wartości przycisku
Gdy kanał nie może renderować interaktywnych kontrolek, wartości przycisków i wyboru przechodzą na zwykły tekst. Zachowanie rezerwowe zachowuje użyteczność, jednocześnie utrzymując nieprzezroczyste dane wywołań zwrotnych jako prywatne:
- Akcje typu
commandrenderują się jakolabel: \command``, aby użytkownicy mogli skopiować polecenie i uruchomić je ręcznie w polu wejściowym kanału. - Akcje typu
callbacki starsze polavaluerenderują się tylko jako etykieta. Nieprzezroczysta wartość wywołania zwrotnego nie jest ujawniana w tekście rezerwowym. - Przyciski
url/webApprenderują tekst URL obok etykiety przycisku, ponieważ URL jest widoczny dla użytkownika. - Opcje wyboru renderują się tylko jako etykieta. Bazowa wartość opcji nie jest ujawniana w tekście rezerwowym.
Adaptery kanałów, które dodają wskazówki dotyczące ręcznych poleceń w swoim rezerwowym interfejsie (np. instrukcje komentarzy dokumentu Feishu), muszą wyprowadzać sprawdzenie obecności polecenia z tych samych bloków prezentacji, których używa renderer rezerwowy, aby tekst wskazówek pojawiał się tylko wtedy, gdy ręczne polecenie jest faktycznie pokazane.
Nieobsługiwane natywne kontrolki powinny degradować się zamiast powodować niepowodzenie całej wysyłki. Przykłady:
- Telegram z wyłączonymi przyciskami w treści wysyła tekst rezerwowy.
- Kanał bez obsługi wyboru wyświetla opcje wyboru jako tekst.
- Przycisk tylko z adresem URL staje się natywnym przyciskiem linku albo rezerwowym wierszem URL.
- Opcjonalne niepowodzenia przypięcia nie powodują niepowodzenia dostarczonej wiadomości.
Głównym wyjątkiem jest delivery.pin.required: true; jeśli przypięcie jest wymagane
i kanał nie może przypiąć wysłanej wiadomości, dostawa zgłasza niepowodzenie.
Mapowanie dostawców
Obecne dołączone renderery:
| Kanał | Natywny cel renderowania | Uwagi |
|---|---|---|
| Discord | Komponenty i kontenery komponentów | Zachowuje starsze channelData.discord.components dla istniejących producentów ładunków natywnych dla providera, ale nowe współdzielone wysyłki powinny używać presentation. |
| Slack | Block Kit | Zachowuje starsze channelData.slack.blocks dla istniejących producentów ładunków natywnych dla providera, ale nowe współdzielone wysyłki powinny używać presentation. |
| Telegram | Tekst oraz klawiatury inline | Przyciski/listy wyboru wymagają funkcji przycisków inline dla docelowej powierzchni; w przeciwnym razie używany jest awaryjny tekst. |
| Mattermost | Tekst oraz interaktywne props | Inne bloki degradują się do tekstu. |
| Microsoft Teams | Adaptive Cards | Zwykły tekst message jest dołączany do karty, gdy podano oba elementy. |
| Feishu | Karty interaktywne | Nagłówek karty może używać title; treść unika duplikowania tego tytułu. |
| Kanały zwykłe | Awaryjny tekst | Kanały bez renderera nadal otrzymują czytelne wyjście. |
Zgodność ładunków natywnych dla providera jest przejściowym udogodnieniem dla istniejących producentów odpowiedzi. Nie jest to powód, aby dodawać nowe współdzielone pola natywne.
Presentation a InteractiveReply
InteractiveReply to starszy wewnętrzny podzbiór używany przez pomocniki zatwierdzania i interakcji.
Obsługuje:
- tekst
- przyciski
- listy wyboru
MessagePresentation to kanoniczny współdzielony kontrakt wysyłania. Dodaje:
- tytuł
- ton
- kontekst
- separator
- przyciski tylko z adresem URL
- ogólne metadane dostarczania przez
ReplyPayload.delivery
Używaj pomocników z openclaw/plugin-sdk/interactive-runtime podczas łączenia ze starszym
kodem:
OC_I18N_900011
Nowy kod powinien bezpośrednio akceptować lub tworzyć MessagePresentation. Istniejące
ładunki interactive są przestarzałym podzbiorem presentation; obsługa środowiska uruchomieniowego
pozostaje dla starszych producentów.
Starsze typy InteractiveReply* i pomocniki konwersji są oznaczone
@deprecated w SDK:
InteractiveReply,InteractiveReplyBlock,InteractiveReplyButton,InteractiveReplyOption,InteractiveReplySelectBlockiInteractiveReplyTextBlocknormalizeInteractiveReply(...)hasInteractiveReplyBlocks(...)interactiveReplyToPresentation(...)presentationToInteractiveReply(...)presentationToInteractiveControlsReply(...)resolveInteractiveTextFallback(...)reduceInteractiveReply(...)
presentationToInteractiveReply(...) i
presentationToInteractiveControlsReply(...) pozostają dostępne jako pomosty renderera
dla starszych implementacji kanałów. Nowy kod producenta nie powinien ich wywoływać;
wysyłaj presentation i pozwól rdzeniowi/adaptacji kanału obsłużyć renderowanie.
Pomocniki zatwierdzania mają też zamienniki preferujące prezentację:
- użyj
buildApprovalPresentationFromActionDescriptors(...)zamiastbuildApprovalInteractiveReplyFromActionDescriptors(...) - użyj
buildApprovalPresentation(...)zamiastbuildApprovalInteractiveReply(...) - użyj
buildExecApprovalPresentation(...)zamiastbuildExecApprovalInteractiveReply(...)
renderMessagePresentationFallbackText(...) zwraca pusty ciąg dla
bloków prezentacji, które nie mają awaryjnego tekstu, takich jak prezentacja
zawierająca tylko separator. Transporty wymagające niepustej treści wysyłki mogą przekazać
emptyFallback, aby włączyć minimalną treść bez zmiany domyślnego kontraktu awaryjnego.
Przypięcie dostarczania
Przypinanie jest zachowaniem dostarczania, a nie prezentacji. Używaj delivery.pin zamiast
pól natywnych dla providera, takich jak channelData.telegram.pin.
Semantyka:
pin: trueprzypina pierwszą pomyślnie dostarczoną wiadomość.pin.notifydomyślnie ma wartośćfalse.pin.requireddomyślnie ma wartośćfalse.- Opcjonalne niepowodzenia przypięcia degradują się i pozostawiają wysłaną wiadomość bez zmian.
- Wymagane niepowodzenia przypięcia powodują niepowodzenie dostarczenia.
- Wiadomości dzielone na fragmenty przypinają pierwszy dostarczony fragment, a nie końcowy fragment.
Ręczne akcje wiadomości pin, unpin i pins nadal istnieją dla istniejących
wiadomości, gdy provider obsługuje te operacje.
Lista kontrolna autora Plugin
- Zadeklaruj
presentationzdescribeMessageTool(...), gdy kanał może renderować albo bezpiecznie degradować prezentację semantyczną. - Dodaj
presentationCapabilitiesdo adaptera wyjściowego środowiska uruchomieniowego. - Zaimplementuj
renderPresentationw kodzie środowiska uruchomieniowego, a nie w kodzie konfiguracji Plugin w płaszczyźnie sterowania. - Trzymaj natywne biblioteki UI poza gorącymi ścieżkami konfiguracji/katalogu.
- Deklaruj ogólne limity funkcji w
presentationCapabilities.limits, gdy są znane. - Zachowuj końcowe limity platformy w rendererze i testach.
- Dodaj testy awaryjne dla nieobsługiwanych przycisków, list wyboru, przycisków URL, duplikacji tytułu/tekstu
oraz mieszanych wysyłek
messagepluspresentation. - Dodaj obsługę przypinania dostarczenia przez
deliveryCapabilities.pinipinDeliveredMessagetylko wtedy, gdy provider może przypiąć identyfikator wysłanej wiadomości. - Nie ujawniaj nowych pól kart/bloków/komponentów/przycisków natywnych dla providera przez współdzielony schemat akcji wiadomości.