Plugins
Tworzenie Pluginów kanałów
Ten przewodnik prowadzi przez budowanie Plugin kanału, który łączy OpenClaw z platformą komunikacyjną. Na końcu będziesz mieć działający kanał z zabezpieczeniami DM, parowaniem, wątkowaniem odpowiedzi i wysyłaniem wiadomości wychodzących.
Jak działają Pluginy kanałów
Pluginy kanałów nie potrzebują własnych narzędzi wysyłania/edycji/reakcji. OpenClaw utrzymuje jedno
wspólne narzędzie message w rdzeniu. Twój Plugin odpowiada za:
- Konfiguracja - rozpoznawanie konta i kreator konfiguracji
- Bezpieczeństwo - polityka DM i listy dozwolonych
- Parowanie - przepływ zatwierdzania DM
- Gramatyka sesji - sposób mapowania identyfikatorów rozmów specyficznych dla dostawcy na czaty bazowe, identyfikatory wątków i zapasowe elementy nadrzędne
- Wiadomości wychodzące - wysyłanie tekstu, multimediów i ankiet na platformę
- Wątkowanie - sposób wątkowania odpowiedzi
- Sygnalizacja pisania Heartbeat - opcjonalne sygnały pisania/zajętości dla celów dostarczania heartbeat
Rdzeń odpowiada za wspólne narzędzie wiadomości, podłączanie promptów, zewnętrzny kształt klucza sesji,
ogólne księgowanie :thread: i wysyłkę.
Nowe Pluginy kanałów powinny także udostępniać adapter message za pomocą
defineChannelMessageAdapter z openclaw/plugin-sdk/channel-message. Adapter
deklaruje, które trwałe możliwości końcowego wysyłania są faktycznie obsługiwane przez transport natywny,
i kieruje wysyłanie tekstu/multimediów do tych samych funkcji transportu co
starszy adapter outbound. Deklaruj możliwość tylko wtedy, gdy test kontraktu
potwierdza natywny efekt uboczny i zwracane potwierdzenie.
Pełny kontrakt API, przykłady, macierz możliwości, reguły potwierdzeń, finalizację
podglądu na żywo, politykę ack odbioru, testy i tabelę migracji znajdziesz w
API wiadomości kanału.
Jeśli istniejący adapter outbound ma już odpowiednie metody wysyłania i
metadane możliwości, użyj createChannelMessageAdapterFromOutbound(...), aby
wyprowadzić adapter message zamiast ręcznie pisać kolejny most.
Wysyłki adaptera powinny zwracać wartości MessageReceipt. Gdy kod zgodności
nadal potrzebuje starszych identyfikatorów, wyprowadź je za pomocą listMessageReceiptPlatformIds(...)
lub resolveMessageReceiptPrimaryId(...) zamiast utrzymywać równoległe
pola messageIds w nowym kodzie cyklu życia.
Kanały obsługujące podgląd powinny także deklarować message.live.capabilities z
dokładnym cyklem życia na żywo, którym zarządzają, takim jak draftPreview,
previewFinalization, progressUpdates, nativeStreaming lub
quietFinalization. Kanały finalizujące roboczy podgląd w miejscu powinny
także deklarować message.live.finalizer.capabilities, takie jak finalEdit,
normalFallback, discardPending, previewReceipt i
retainOnAmbiguousFailure, oraz kierować logikę środowiska uruchomieniowego przez
defineFinalizableLivePreviewAdapter(...) wraz z
deliverWithFinalizableLivePreviewAdapter(...). Utrzymuj te możliwości potwierdzone
testami verifyChannelMessageLiveCapabilityAdapterProofs(...) i
verifyChannelMessageLiveFinalizerProofs(...), aby natywny podgląd,
postęp, edycja, fallback/zachowanie, czyszczenie i zachowanie potwierdzeń nie mogły
po cichu się rozjechać.
Odbiorniki wejściowe, które odraczają potwierdzenia platformy, powinny deklarować
message.receive.defaultAckPolicy i supportedAckPolicies zamiast ukrywać
czas ack w stanie lokalnym monitora. Obejmij każdą zadeklarowaną politykę testem
verifyChannelMessageReceiveAckPolicyAdapterProofs(...).
Starsze helpery odpowiedzi/tur, takie jak createChannelTurnReplyPipeline,
dispatchInboundReplyWithBase i recordInboundSessionAndDispatchReply,
pozostają dostępne dla dyspozytorów zgodności. Nie używaj tych nazw w nowym
kodzie kanału; nowe Pluginy powinny zaczynać od adaptera message, potwierdzeń oraz
helperów cyklu życia odbioru/wysyłki w openclaw/plugin-sdk/channel-message.
Jeśli Twój kanał obsługuje wskaźniki pisania poza odpowiedziami wejściowymi, udostępnij
heartbeat.sendTyping(...) w Plugin kanału. Rdzeń wywołuje go z
rozpoznanym celem dostarczania heartbeat przed rozpoczęciem przebiegu modelu heartbeat i
używa wspólnego cyklu życia keepalive/czyszczenia pisania. Dodaj heartbeat.clearTyping(...),
gdy platforma wymaga jawnego sygnału zatrzymania.
Jeśli Twój kanał dodaje parametry narzędzia wiadomości, które przenoszą źródła multimediów, udostępnij te
nazwy parametrów przez describeMessageTool(...).mediaSourceParams. Rdzeń używa
tej jawnej listy do normalizacji ścieżek piaskownicy i polityki dostępu do multimediów wychodzących,
więc Pluginy nie potrzebują specjalnych przypadków we wspólnym rdzeniu dla parametrów specyficznych dla dostawcy:
awatara, załącznika lub obrazu okładki.
Preferuj zwracanie mapy kluczowanej akcjami, takiej jak
{ "set-profile": ["avatarUrl", "avatarPath"] }, aby niepowiązane akcje nie
dziedziczyły argumentów multimediów z innej akcji. Płaska tablica nadal działa dla parametrów,
które są celowo współdzielone przez każdą udostępnioną akcję.
Jeśli Twój kanał wymaga kształtowania specyficznego dla dostawcy dla message(action="send"),
preferuj actions.prepareSendPayload(...). Umieszczaj natywne karty, bloki, osadzenia lub
inne trwałe dane w payload.channelData.<channel> i pozwól rdzeniowi wykonać
właściwą wysyłkę przez adapter outbound/message. Używaj
actions.handleAction(...) dla wysyłania tylko jako zapasowego mechanizmu zgodności dla
ładunków, których nie można zserializować i ponowić.
Jeśli Twoja platforma przechowuje dodatkowy zakres w identyfikatorach rozmów, utrzymuj to parsowanie
w Plugin za pomocą messaging.resolveSessionConversation(...). To jest
kanoniczny hook do mapowania rawId na bazowy identyfikator rozmowy, opcjonalny identyfikator
wątku, jawne baseConversationId i dowolne parentConversationCandidates.
Gdy zwracasz parentConversationCandidates, utrzymuj je uporządkowane od
najwęższego elementu nadrzędnego do najszerszej/bazowej rozmowy.
Używaj openclaw/plugin-sdk/channel-route, gdy kod Plugin musi normalizować
pola podobne do tras, porównywać wątek podrzędny z jego trasą nadrzędną albo budować
stabilny klucz deduplikacji z { channel, to, accountId, threadId }. Helper
normalizuje numeryczne identyfikatory wątków tak samo jak rdzeń, więc Pluginy powinny preferować
go zamiast doraźnych porównań String(threadId).
Pluginy z gramatyką celów specyficzną dla dostawcy mogą wstrzyknąć swój parser do
resolveChannelRouteTargetWithParser(...) i nadal uzyskać ten sam kształt celu trasy
oraz semantykę zapasowego wątku, której używa rdzeń.
Wbudowane Pluginy, które potrzebują tego samego parsowania przed uruchomieniem rejestru kanałów,
mogą także udostępnić plik najwyższego poziomu session-key-api.ts z pasującym
eksportem resolveSessionConversation(...). Rdzeń używa tej powierzchni bezpiecznej dla bootstrapu
tylko wtedy, gdy rejestr Plugin środowiska uruchomieniowego nie jest jeszcze dostępny.
messaging.resolveParentConversationCandidates(...) pozostaje dostępne jako
starszy zapasowy mechanizm zgodności, gdy Plugin potrzebuje tylko zapasowych elementów nadrzędnych na podstawie
ogólnego/surowego identyfikatora. Jeśli istnieją oba hooki, rdzeń najpierw używa
resolveSessionConversation(...).parentConversationCandidates i sięga po
resolveParentConversationCandidates(...) tylko wtedy, gdy kanoniczny hook
je pomija.
Zatwierdzenia i możliwości kanału
Większość Pluginów kanałów nie potrzebuje kodu specyficznego dla zatwierdzeń.
- Rdzeń odpowiada za
/approvew tym samym czacie, współdzielone payloady przycisków zatwierdzania oraz ogólne dostarczanie awaryjne. - Preferuj jeden obiekt
approvalCapabilityw Pluginie kanału, gdy kanał potrzebuje zachowania specyficznego dla zatwierdzeń. ChannelPlugin.approvalszostał usunięty. Umieść fakty dotyczące dostarczania/renderowania/uwierzytelniania oraz natywnych zatwierdzeń wapprovalCapability.plugin.authsłuży tylko do logowania/wylogowania; rdzeń nie odczytuje już hooków uwierzytelniania zatwierdzeń z tego obiektu.approvalCapability.authorizeActorActioniapprovalCapability.getActionAvailabilityStatesą kanoniczną seamą uwierzytelniania zatwierdzeń.- Używaj
approvalCapability.getActionAvailabilityStatedla dostępności uwierzytelniania zatwierdzeń w tym samym czacie. - Jeśli Twój kanał udostępnia natywne zatwierdzenia exec, używaj
approvalCapability.getExecInitiatingSurfaceStatedla stanu powierzchni inicjującej/natywnego klienta, gdy różni się on od uwierzytelniania zatwierdzeń w tym samym czacie. Rdzeń używa tego hooka specyficznego dla exec, aby odróżnićenabledoddisabled, zdecydować, czy kanał inicjujący obsługuje natywne zatwierdzenia exec, oraz uwzględnić kanał w wskazówkach awaryjnych dla natywnego klienta.createApproverRestrictedNativeApprovalCapability(...)wypełnia to dla typowego przypadku. - Używaj
outbound.shouldSuppressLocalPayloadPromptluboutbound.beforeDeliverPayloaddla zachowania cyklu życia payloadu specyficznego dla kanału, takiego jak ukrywanie zduplikowanych lokalnych monitów zatwierdzenia lub wysyłanie wskaźników pisania przed dostarczeniem. - Używaj
approvalCapability.deliverytylko do natywnego routingu zatwierdzeń lub tłumienia fallbacku. - Używaj
approvalCapability.nativeRuntimedla faktów natywnych zatwierdzeń należących do kanału. Utrzymuj je lazy na gorących entrypointach kanału za pomocącreateLazyChannelApprovalNativeRuntimeAdapter(...), który może importować moduł runtime na żądanie, nadal pozwalając rdzeniowi złożyć cykl życia zatwierdzeń. - Używaj
approvalCapability.rendertylko wtedy, gdy kanał naprawdę potrzebuje niestandardowych payloadów zatwierdzeń zamiast współdzielonego renderera. - Używaj
approvalCapability.describeExecApprovalSetup, gdy kanał chce, aby odpowiedź na ścieżce wyłączenia wyjaśniała dokładne przełączniki konfiguracyjne potrzebne do włączenia natywnych zatwierdzeń exec. Hook otrzymuje{ channel, channelLabel, accountId }; kanały z nazwanymi kontami powinny renderować ścieżki zakresowane do konta, takie jakchannels.<channel>.accounts.<id>.execApprovals.*, zamiast domyślnych ustawień najwyższego poziomu. - Jeśli kanał może wywnioskować stabilne, podobne do właściciela tożsamości DM z istniejącej konfiguracji, użyj
createResolvedApproverActionAuthAdapterzopenclaw/plugin-sdk/approval-runtime, aby ograniczyć/approvew tym samym czacie bez dodawania logiki rdzenia specyficznej dla zatwierdzeń. - Jeśli kanał potrzebuje natywnego dostarczania zatwierdzeń, utrzymuj kod kanału skupiony na normalizacji celu oraz faktach transportu/prezentacji. Używaj
createChannelExecApprovalProfile,createChannelNativeOriginTargetResolver,createChannelApproverDmTargetResolvericreateApproverRestrictedNativeApprovalCapabilityzopenclaw/plugin-sdk/approval-runtime. Umieść fakty specyficzne dla kanału zaapprovalCapability.nativeRuntime, najlepiej przezcreateChannelApprovalNativeRuntimeAdapter(...)lubcreateLazyChannelApprovalNativeRuntimeAdapter(...), aby rdzeń mógł złożyć handler i odpowiadać za filtrowanie żądań, routing, deduplikację, wygaśnięcie, subskrypcję Gateway oraz powiadomienia o przekierowaniu gdzie indziej.nativeRuntimejest podzielony na kilka mniejszych seamów: createChannelNativeOriginTargetResolverdomyślnie używa współdzielonego matchera tras kanałów dla celów{ to, accountId, threadId }. PrzekazujtargetsMatchtylko wtedy, gdy kanał ma reguły równoważności specyficzne dla dostawcy, takie jak dopasowywanie prefiksu timestampu w Slack.- Przekaż
normalizeTargetForMatchdocreateChannelNativeOriginTargetResolver, gdy kanał musi kanonizować identyfikatory dostawcy przed uruchomieniem domyślnego matchera tras lub niestandardowego callbackatargetsMatch, zachowując oryginalny cel do dostarczenia. UżywajnormalizeTargettylko wtedy, gdy sam rozwiązany cel dostarczenia powinien zostać skanonizowany. availability- czy konto jest skonfigurowane i czy żądanie powinno zostać obsłużonepresentation- mapuje współdzielony model widoku zatwierdzenia na oczekujące/rozwiązane/wygasłe natywne payloady lub końcowe akcjetransport- przygotowuje cele oraz wysyła/aktualizuje/usuwa natywne wiadomości zatwierdzeńinteractions- opcjonalne hooki bind/unbind/clear-action dla natywnych przycisków lub reakcjiobserve- opcjonalne hooki diagnostyki dostarczania- Jeśli kanał potrzebuje obiektów należących do runtime, takich jak klient, token, aplikacja Bolt lub odbiornik Webhook, zarejestruj je przez
openclaw/plugin-sdk/channel-runtime-context. Ogólny rejestr kontekstu runtime pozwala rdzeniowi bootstrapować handlery sterowane capability ze stanu startowego kanału bez dodawania wrapper glue specyficznego dla zatwierdzeń. - Sięgaj po niższopoziomowe
createChannelApprovalHandlerlubcreateChannelNativeApprovalRuntimetylko wtedy, gdy seam sterowana capability nie jest jeszcze wystarczająco ekspresyjna. - Natywne kanały zatwierdzeń muszą routować zarówno
accountId, jak iapprovalKindprzez te helpery.accountIdutrzymuje politykę zatwierdzeń dla wielu kont w zakresie właściwego konta bota, aapprovalKindudostępnia kanałowi zachowanie zatwierdzeń exec vs Plugin bez zakodowanych gałęzi w rdzeniu. - Rdzeń odpowiada teraz także za powiadomienia o przekierowaniu zatwierdzeń. Pluginy kanałów nie powinny wysyłać własnych wiadomości uzupełniających typu „zatwierdzenie trafiło do DM / innego kanału” z
createChannelNativeApprovalRuntime; zamiast tego powinny udostępniać dokładny routing pochodzenia + DM zatwierdzającego przez współdzielone helpery capability zatwierdzeń i pozwolić rdzeniowi zagregować faktyczne dostarczenia przed opublikowaniem powiadomienia z powrotem na czacie inicjującym. - Zachowaj rodzaj dostarczonego identyfikatora zatwierdzenia end-to-end. Natywni klienci nie powinni zgadywać ani przepisywać routingu zatwierdzeń exec vs Plugin ze stanu lokalnego kanału.
- Różne rodzaje zatwierdzeń mogą celowo udostępniać różne natywne powierzchnie.
Aktualne przykłady wbudowane:
- Slack utrzymuje natywny routing zatwierdzeń dostępny zarówno dla identyfikatorów exec, jak i Plugin.
- Matrix utrzymuje ten sam natywny routing DM/kanału oraz UX reakcji dla zatwierdzeń exec i Plugin, nadal pozwalając, aby uwierzytelnianie różniło się według rodzaju zatwierdzenia.
createApproverRestrictedNativeApprovalAdapternadal istnieje jako wrapper kompatybilności, ale nowy kod powinien preferować builder capability i udostępniaćapprovalCapabilityw Pluginie.
Dla gorących entrypointów kanału preferuj węższe podścieżki runtime, gdy potrzebujesz tylko jednej części tej rodziny:
openclaw/plugin-sdk/approval-auth-runtimeopenclaw/plugin-sdk/approval-client-runtimeopenclaw/plugin-sdk/approval-delivery-runtimeopenclaw/plugin-sdk/approval-gateway-runtimeopenclaw/plugin-sdk/approval-handler-adapter-runtimeopenclaw/plugin-sdk/approval-handler-runtimeopenclaw/plugin-sdk/approval-native-runtimeopenclaw/plugin-sdk/approval-reply-runtimeopenclaw/plugin-sdk/channel-runtime-context
Podobnie preferuj openclaw/plugin-sdk/setup-runtime,
openclaw/plugin-sdk/setup-adapter-runtime,
openclaw/plugin-sdk/reply-runtime,
openclaw/plugin-sdk/reply-dispatch-runtime,
openclaw/plugin-sdk/reply-reference oraz
openclaw/plugin-sdk/reply-chunking, gdy nie potrzebujesz szerszej powierzchni
parasola.
Konkretnie dla setupu:
openclaw/plugin-sdk/setup-runtimeobejmuje helpery setupu bezpieczne dla runtime: importowo bezpieczne adaptery patchy setupu (createPatchedAccountSetupAdapter,createEnvPatchedAccountSetupAdapter,createSetupInputPresenceValidator), wynik notatki lookup,promptResolvedAllowFrom,splitSetupEntriesoraz delegowane buildery setup-proxyopenclaw/plugin-sdk/setup-adapter-runtimeto wąska, świadoma env seama adaptera dlacreateEnvPatchedAccountSetupAdapteropenclaw/plugin-sdk/channel-setupobejmuje buildery setupu opcjonalnej instalacji oraz kilka prymitywów bezpiecznych dla setupu:createOptionalChannelSetupSurface,createOptionalChannelSetupAdapter,
Jeśli Twój kanał obsługuje setup lub uwierzytelnianie sterowane przez env, a ogólne przepływy startupu/konfiguracji
powinny znać te nazwy env przed załadowaniem runtime, zadeklaruj je w
manifeście Plugin za pomocą channelEnvVars. Zachowaj runtime kanału envVars lub lokalne
stałe wyłącznie dla tekstu skierowanego do operatora.
Jeśli Twój kanał może pojawić się w status, channels list, channels status lub
skanach SecretRef przed startem runtime Plugin, dodaj openclaw.setupEntry w
package.json. Ten entrypoint powinien być bezpieczny do importu w ścieżkach poleceń
tylko do odczytu i powinien zwracać metadane kanału, bezpieczny dla setupu adapter konfiguracji, adapter statusu
oraz metadane celów sekretów kanału potrzebne do tych podsumowań. Nie
uruchamiaj klientów, listenerów ani runtime transportu z entry setupu.
Utrzymuj wąską także główną ścieżkę importu entry kanału. Discovery może ocenić
entry i moduł Plugin kanału, aby zarejestrować capability bez aktywowania
kanału. Pliki takie jak channel-plugin-api.ts powinny eksportować obiekt Plugin
kanału bez importowania kreatorów setupu, klientów transportu, listenerów socketów,
launcherów subprocessów ani modułów startu usług. Umieść te części runtime
w modułach ładowanych z registerFull(...), setterów runtime lub lazy
adapterów capability.
createOptionalChannelSetupWizard, DEFAULT_ACCOUNT_ID,
createTopLevelChannelDmPolicy, setSetupChannelEnabled oraz
splitSetupEntries
- używaj szerszej seamy
openclaw/plugin-sdk/setuptylko wtedy, gdy potrzebujesz także cięższych współdzielonych helperów setupu/konfiguracji, takich jakmoveSingleAccountChannelSectionToDefaultAccount(...)
Jeśli Twój kanał chce tylko reklamować „najpierw zainstaluj ten Plugin” w powierzchniach setupu, preferuj createOptionalChannelSetupSurface(...). Wygenerowany
adapter/kreator kończy się bezpieczną odmową przy zapisach konfiguracji i finalizacji oraz ponownie używa
tego samego komunikatu o wymaganej instalacji w walidacji, finalizacji i tekście
linku do dokumentacji.
Dla innych gorących ścieżek kanału preferuj wąskie helpery zamiast szerszych starszych powierzchni:
openclaw/plugin-sdk/account-core,openclaw/plugin-sdk/account-id,openclaw/plugin-sdk/account-resolutionorazopenclaw/plugin-sdk/account-helpersdla konfiguracji wielu kont i fallbacku domyślnego kontaopenclaw/plugin-sdk/inbound-envelopeorazopenclaw/plugin-sdk/inbound-reply-dispatchdla przychodzącej trasy/koperty i okablowania record-and-dispatchopenclaw/plugin-sdk/messaging-targetsdla parsowania/dopasowywania celówopenclaw/plugin-sdk/outbound-mediaorazopenclaw/plugin-sdk/outbound-runtimedla ładowania mediów oraz delegatów tożsamości/wysyłania wychodzącego i planowania payloadubuildThreadAwareOutboundSessionRoute(...)zopenclaw/plugin-sdk/channel-core, gdy trasa wychodząca powinna zachować jawnereplyToId/threadIdalbo odzyskać bieżącą sesję:thread:po tym, jak bazowy klucz sesji nadal pasuje. Pluginy dostawców mogą nadpisywać priorytet, zachowanie sufiksów i normalizację identyfikatora wątku, gdy ich platforma ma natywną semantykę dostarczania wątków.openclaw/plugin-sdk/thread-bindings-runtimedla cyklu życia thread-binding i rejestracji adapteraopenclaw/plugin-sdk/agent-media-payloadtylko wtedy, gdy starszy układ pól payloadu agenta/mediów jest nadal wymaganyopenclaw/plugin-sdk/telegram-command-configdla normalizacji niestandardowych poleceń Telegram, walidacji duplikatów/konfliktów oraz stabilnego fallbackiem kontraktu konfiguracji poleceń
Kanały wyłącznie uwierzytelniające mogą zwykle zatrzymać się na domyślnej ścieżce: rdzeń obsługuje zatwierdzenia, a Plugin po prostu udostępnia capability wychodzące/uwierzytelniania. Natywne kanały zatwierdzeń, takie jak Matrix, Slack, Telegram i niestandardowe transporty czatu, powinny używać współdzielonych natywnych helperów zamiast tworzyć własny cykl życia zatwierdzeń.
Zasady dotyczące wzmianek przychodzących
Obsługę wzmianek przychodzących utrzymuj podzieloną na dwie warstwy:
- zbieranie dowodów należące do Plugin
- współdzielona ewaluacja zasad
Używaj openclaw/plugin-sdk/channel-mention-gating do decyzji dotyczących zasad wzmianek.
Używaj openclaw/plugin-sdk/channel-inbound tylko wtedy, gdy potrzebujesz szerszego barrela helperów
przychodzących.
Dobre dopasowanie dla logiki lokalnej Plugin:
- wykrywanie odpowiedzi do bota
- wykrywanie cytowanego bota
- sprawdzanie uczestnictwa w wątku
- wykluczenia wiadomości serwisowych/systemowych
- natywne cache platformy potrzebne do udowodnienia uczestnictwa bota
Dobre dopasowanie dla współdzielonego helpera:
requireMention- wynik jawnej wzmianki
- lista dozwolonych wzmiankowania niejawnego
- obejście komendy
- ostateczna decyzja o pominięciu
Preferowany przepływ:
- Oblicz lokalne fakty dotyczące wzmianki.
- Przekaż te fakty do
resolveInboundMentionDecision({ facts, policy }). - Użyj
decision.effectiveWasMentioned,decision.shouldBypassMentionidecision.shouldSkipw swojej bramce przychodzącej.
implicitMentionKindWhen,
matchesMentionWithExplicit,
resolveInboundMentionDecision,
} from "openclaw/plugin-sdk/channel-inbound";
const mentionMatch = matchesMentionWithExplicit(text, {
mentionRegexes,
mentionPatterns,
});
const facts = {
canDetectMention: true,
wasMentioned: mentionMatch.matched,
hasAnyMention: mentionMatch.hasExplicitMention,
implicitMentionKinds: [
...implicitMentionKindWhen("reply_to_bot", isReplyToBot),
...implicitMentionKindWhen("quoted_bot", isQuoteOfBot),
],
};
const decision = resolveInboundMentionDecision({
facts,
policy: {
isGroup,
requireMention,
allowedImplicitMentionKinds: requireExplicitMention ? [] : ["reply_to_bot", "quoted_bot"],
allowTextCommands,
hasControlCommand,
commandAuthorized,
},
});
if (decision.shouldSkip) return;
api.runtime.channel.mentions udostępnia te same współdzielone pomocniki wzmianek dla
wbudowanych channel plugins, które już zależą od wstrzykiwania runtime:
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
Jeśli potrzebujesz tylko implicitMentionKindWhen i
resolveInboundMentionDecision, importuj z
openclaw/plugin-sdk/channel-mention-gating, aby uniknąć ładowania niepowiązanych pomocników
runtime dla obsługi przychodzącej.
Starsze pomocniki resolveMentionGating* pozostają w
openclaw/plugin-sdk/channel-inbound wyłącznie jako eksporty zgodności. Nowy kod
powinien używać resolveInboundMentionDecision({ facts, policy }).
Przewodnik
Pakiet i manifest
Utwórz standardowe pliki Plugin. Pole channel w package.json jest tym,
co sprawia, że jest to channel plugin. Pełny zakres metadanych pakietu
znajdziesz w Konfiguracja i ustawienia Plugin:
{
"name": "@myorg/openclaw-acme-chat",
"version": "1.0.0",
"type": "module",
"openclaw": {
"extensions": ["./index.ts"],
"setupEntry": "./setup-entry.ts",
"channel": {
"id": "acme-chat",
"label": "Acme Chat",
"blurb": "Connect OpenClaw to Acme Chat."
}
}
}
{
"id": "acme-chat",
"kind": "channel",
"channels": ["acme-chat"],
"name": "Acme Chat",
"description": "Acme Chat channel plugin",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
},
"channelConfigs": {
"acme-chat": {
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"token": { "type": "string" },
"allowFrom": {
"type": "array",
"items": { "type": "string" }
}
}
},
"uiHints": {
"token": {
"label": "Bot token",
"sensitive": true
}
}
}
}
}
configSchema waliduje plugins.entries.acme-chat.config. Używaj go dla
ustawień należących do Plugin, które nie są konfiguracją konta kanału. channelConfigs
waliduje channels.acme-chat i jest źródłem zimnej ścieżki używanym przez schemat
konfiguracji, konfigurację początkową i powierzchnie UI, zanim załaduje się runtime Plugin.
Zbuduj obiekt channel plugin
Interfejs ChannelPlugin ma wiele opcjonalnych powierzchni adapterów. Zacznij od
minimum - id i setup - i dodawaj adaptery w miarę potrzeb.
Utwórz src/channel.ts:
import {
createChatChannelPlugin,
createChannelPluginBase,
} from "openclaw/plugin-sdk/channel-core";
import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core";
import { acmeChatApi } from "./client.js"; // your platform API client
type ResolvedAccount = {
accountId: string | null;
token: string;
allowFrom: string[];
dmPolicy: string | undefined;
};
function resolveAccount(
cfg: OpenClawConfig,
accountId?: string | null,
): ResolvedAccount {
const section = (cfg.channels as Record<string, any>)?.["acme-chat"];
const token = section?.token;
if (!token) throw new Error("acme-chat: token is required");
return {
accountId: accountId ?? null,
token,
allowFrom: section?.allowFrom ?? [],
dmPolicy: section?.dmSecurity,
};
}
export const acmeChatPlugin = createChatChannelPlugin<ResolvedAccount>({
base: createChannelPluginBase({
id: "acme-chat",
setup: {
resolveAccount,
inspectAccount(cfg, accountId) {
const section =
(cfg.channels as Record<string, any>)?.["acme-chat"];
return {
enabled: Boolean(section?.token),
configured: Boolean(section?.token),
tokenStatus: section?.token ? "available" : "missing",
};
},
},
}),
// DM security: who can message the bot
security: {
dm: {
channelKey: "acme-chat",
resolvePolicy: (account) => account.dmPolicy,
resolveAllowFrom: (account) => account.allowFrom,
defaultPolicy: "allowlist",
},
},
// Pairing: approval flow for new DM contacts
pairing: {
text: {
idLabel: "Acme Chat username",
message: "Send this code to verify your identity:",
notify: async ({ target, code }) => {
await acmeChatApi.sendDm(target, `Pairing code: ${code}`);
},
},
},
// Threading: how replies are delivered
threading: { topLevelReplyToMode: "reply" },
// Outbound: send messages to the platform
outbound: {
attachedResults: {
sendText: async (params) => {
const result = await acmeChatApi.sendMessage(
params.to,
params.text,
);
return { messageId: result.id };
},
},
base: {
sendMedia: async (params) => {
await acmeChatApi.sendFile(params.to, params.filePath);
},
},
},
});
W przypadku kanałów, które akceptują zarówno kanoniczne klucze DM najwyższego poziomu, jak i starsze klucze zagnieżdżone, używaj pomocników z plugin-sdk/channel-config-helpers: resolveChannelDmAccess, resolveChannelDmPolicy, resolveChannelDmAllowFrom i normalizeChannelDmPolicy utrzymują wartości lokalne dla konta przed odziedziczonymi wartościami głównymi. Połącz ten sam resolver z naprawą doctor przez normalizeLegacyDmAliases, aby runtime i migracja odczytywały ten sam kontrakt.
Co createChatChannelPlugin robi za Ciebie
Zamiast ręcznie implementować niskopoziomowe interfejsy adapterów, przekazujesz opcje deklaratywne, a builder je komponuje:
| Opcja | Co podłącza |
|---|---|
security.dm |
Zakresowy resolver zabezpieczeń DM z pól konfiguracji |
pairing.text |
Tekstowy przepływ parowania DM z wymianą kodu |
threading |
Resolver trybu reply-to (stały, zakresowy dla konta albo niestandardowy) |
outbound.attachedResults |
Funkcje wysyłania zwracające metadane wyniku (identyfikatory wiadomości) |
Możesz też przekazać surowe obiekty adapterów zamiast opcji deklaratywnych, jeśli potrzebujesz pełnej kontroli.
Surowe adaptery wychodzące mogą definiować funkcję chunker(text, limit, ctx).
Opcjonalne ctx.formatting przenosi decyzje formatowania z czasu dostarczania,
takie jak maxLinesPerMessage; zastosuj je przed wysłaniem, aby wątkowanie odpowiedzi
i granice fragmentów zostały rozstrzygnięte raz przez współdzielone dostarczanie wychodzące.
Konteksty wysyłania zawierają też replyToIdSource (implicit lub explicit),
gdy natywny cel odpowiedzi został rozstrzygnięty, więc pomocniki payload mogą zachować
jawne tagi odpowiedzi bez zużywania niejawnego, jednorazowego miejsca odpowiedzi.
Podłącz punkt wejścia
Utwórz index.ts:
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
import { acmeChatPlugin } from "./src/channel.js";
export default defineChannelPluginEntry({
id: "acme-chat",
name: "Acme Chat",
description: "Acme Chat channel plugin",
plugin: acmeChatPlugin,
registerCliMetadata(api) {
api.registerCli(
({ program }) => {
program
.command("acme-chat")
.description("Acme Chat management");
},
{
descriptors: [
{
name: "acme-chat",
description: "Acme Chat management",
hasSubcommands: false,
},
],
},
);
},
registerFull(api) {
api.registerGatewayMethod(/* ... */);
},
});
Umieść deskryptory CLI należące do kanału w registerCliMetadata(...), aby OpenClaw
mógł pokazywać je w pomocy głównej bez aktywowania pełnego runtime kanału,
podczas gdy normalne pełne ładowania nadal pobierają te same deskryptory do rzeczywistej
rejestracji komend. Zachowaj registerFull(...) dla pracy wyłącznie runtime.
Jeśli registerFull(...) rejestruje metody RPC Gateway, użyj
prefiksu specyficznego dla Plugin. Główne przestrzenie nazw administracyjnych (config.*,
exec.approvals.*, wizard.*, update.*) pozostają zarezerwowane i zawsze
rozwiązują się do operator.admin.
defineChannelPluginEntry automatycznie obsługuje podział trybu rejestracji. Zobacz
Punkty wejścia, aby poznać wszystkie
opcje.
Dodaj wpis konfiguracji początkowej
Utwórz setup-entry.ts dla lekkiego ładowania podczas wdrażania:
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
import { acmeChatPlugin } from "./src/channel.js";
export default defineSetupPluginEntry(acmeChatPlugin);
OpenClaw ładuje to zamiast pełnego wpisu, gdy kanał jest wyłączony lub nieskonfigurowany. Pozwala to uniknąć wciągania ciężkiego kodu runtime podczas przepływów konfiguracji początkowej. Szczegóły znajdziesz w Konfiguracja początkowa i konfiguracja.
Wbudowane kanały przestrzeni roboczej, które wydzielają eksporty bezpieczne dla konfiguracji początkowej do modułów pomocniczych,
mogą używać defineBundledChannelSetupEntry(...) z
openclaw/plugin-sdk/channel-entry-contract, gdy potrzebują też
jawnego settera runtime na czas konfiguracji początkowej.
Obsłuż wiadomości przychodzące
Twój Plugin musi odbierać wiadomości z platformy i przekazywać je do OpenClaw. Typowy wzorzec to Webhook, który weryfikuje żądanie i przekazuje je przez obsługę przychodzącą Twojego kanału:
registerFull(api) {
api.registerHttpRoute({
path: "/acme-chat/webhook",
auth: "plugin", // plugin-managed auth (verify signatures yourself)
handler: async (req, res) => {
const event = parseWebhookPayload(req);
// Your inbound handler dispatches the message to OpenClaw.
// The exact wiring depends on your platform SDK -
// see a real example in the bundled Microsoft Teams or Google Chat plugin package.
await handleAcmeChatInbound(api, event);
res.statusCode = 200;
res.end("ok");
return true;
},
});
}
Test
Napisz testy współlokowane w src/channel.test.ts:
import { describe, it, expect } from "vitest";
import { acmeChatPlugin } from "./channel.js";
describe("acme-chat plugin", () => {
it("resolves account from config", () => {
const cfg = {
channels: {
"acme-chat": { token: "test-token", allowFrom: ["user1"] },
},
} as any;
const account = acmeChatPlugin.setup!.resolveAccount(cfg, undefined);
expect(account.token).toBe("test-token");
});
it("inspects account without materializing secrets", () => {
const cfg = {
channels: { "acme-chat": { token: "test-token" } },
} as any;
const result = acmeChatPlugin.setup!.inspectAccount!(cfg, undefined);
expect(result.configured).toBe(true);
expect(result.tokenStatus).toBe("available");
});
it("reports missing config", () => {
const cfg = { channels: {} } as any;
const result = acmeChatPlugin.setup!.inspectAccount!(cfg, undefined);
expect(result.configured).toBe(false);
});
});
pnpm test -- <bundled-plugin-root>/acme-chat/
Współdzielone pomocniki testowe znajdziesz w Testowanie.
Struktura plików
<bundled-plugin-root>/acme-chat/
├── package.json # openclaw.channel metadata
├── openclaw.plugin.json # Manifest with config schema
├── index.ts # defineChannelPluginEntry
├── setup-entry.ts # defineSetupPluginEntry
├── api.ts # Public exports (optional)
├── runtime-api.ts # Internal runtime exports (optional)
└── src/
├── channel.ts # ChannelPlugin via createChatChannelPlugin
├── channel.test.ts # Tests
├── client.ts # Platform API client
└── runtime.ts # Runtime store (if needed)
Tematy zaawansowane
Stałe tryby odpowiedzi, ograniczone do konta lub niestandardowe
describeMessageTool i wykrywanie akcji
inferTargetChatType, looksLikeId, resolveTarget
TTS, STT, media, subagent przez api.runtime
Współdzielony cykl życia tury przychodzącej: ingest, resolve, record, dispatch, finalize
Następne kroki
- Plugin dostawców - jeśli Twój Plugin udostępnia także modele
- Omówienie SDK - pełna referencja importów podścieżek
- Testowanie SDK - narzędzia testowe i testy kontraktowe
- Manifest Plugin - pełny schemat manifestu