Building plugins
Kanal-Plugins erstellen
Dieser Leitfaden führt Sie durch den Aufbau eines Channel-Plugins, das OpenClaw mit einer Messaging-Plattform verbindet. Am Ende verfügen Sie über einen funktionierenden Channel mit DM-Sicherheit, Pairing, Antwort-Threading und ausgehendem Messaging.
Funktionsweise von Channel-Plugins
Channel-Plugins benötigen keine eigenen Tools zum Senden, Bearbeiten oder Reagieren. OpenClaw hält ein
gemeinsames message-Tool im Core vor. Ihr Plugin ist zuständig für:
- Konfiguration - Kontoauflösung und Einrichtungsassistent
- Sicherheit - DM-Richtlinie und Allowlists
- Pairing - DM-Genehmigungsablauf
- Sitzungsgrammatik - wie providerspezifische Konversations-IDs Basischats, Thread-IDs und übergeordnete Fallbacks abbilden
- Ausgehend - Senden von Text, Medien und Umfragen an die Plattform
- Threading - wie Antworten in Threads eingeordnet werden
- Heartbeat-Tippen - optionale Tipp-/Beschäftigt-Signale für Heartbeat-Zustellziele
Der Core ist zuständig für das gemeinsame Message-Tool, Prompt-Verkabelung, die äußere Form des Sitzungsschlüssels,
generische :thread:-Buchhaltung und Dispatch.
Neue Channel-Plugins sollten außerdem einen message-Adapter mit
defineChannelMessageAdapter aus openclaw/plugin-sdk/channel-outbound bereitstellen. Der
Adapter deklariert, welche dauerhaften Final-Send-Fähigkeiten der native Transport
tatsächlich unterstützt, und leitet Text-/Medien-Sends an dieselben Transportfunktionen wie
der alte outbound-Adapter weiter. Deklarieren Sie eine Fähigkeit nur, wenn ein Contract-Test
den nativen Seiteneffekt und den zurückgegebenen Beleg nachweist.
Den vollständigen API-Vertrag, Beispiele, die Fähigkeitsmatrix, Belegregeln, Live-
Preview-Finalisierung, Receive-Ack-Richtlinie, Tests und Migrationstabelle finden Sie unter
Channel outbound API.
Wenn der vorhandene outbound-Adapter bereits die richtigen Sendemethoden und
Fähigkeitsmetadaten hat, verwenden Sie createChannelMessageAdapterFromOutbound(...), um
den message-Adapter abzuleiten, statt eine weitere Bridge von Hand zu schreiben.
Adapter-Sends sollten MessageReceipt-Werte zurückgeben. Wenn Kompatibilitätscode
weiterhin Legacy-IDs benötigt, leiten Sie sie mit listMessageReceiptPlatformIds(...)
oder resolveMessageReceiptPrimaryId(...) ab, statt parallele
messageIds-Felder in neuem Lifecycle-Code beizubehalten.
Preview-fähige Channels sollten außerdem message.live.capabilities mit
dem exakten Live-Lifecycle deklarieren, den sie besitzen, zum Beispiel draftPreview,
previewFinalization, progressUpdates, nativeStreaming oder
quietFinalization. Channels, die eine Draft-Preview direkt finalisieren, sollten
außerdem message.live.finalizer.capabilities deklarieren, zum Beispiel finalEdit,
normalFallback, discardPending, previewReceipt und
retainOnAmbiguousFailure, und die Runtime-Logik über
defineFinalizableLivePreviewAdapter(...) plus
deliverWithFinalizableLivePreviewAdapter(...) führen. Stützen Sie diese Fähigkeiten
durch Tests mit verifyChannelMessageLiveCapabilityAdapterProofs(...) und
verifyChannelMessageLiveFinalizerProofs(...), damit natives Preview-,
Fortschritts-, Bearbeitungs-, Fallback-/Aufbewahrungs-, Bereinigungs- und Belegverhalten
nicht unbemerkt abweichen kann.
Inbound-Empfänger, die Plattformbestätigungen verzögern, sollten
message.receive.defaultAckPolicy und supportedAckPolicies deklarieren, statt
Ack-Timing in monitorlokalem Zustand zu verbergen. Decken Sie jede deklarierte Richtlinie mit
verifyChannelMessageReceiveAckPolicyAdapterProofs(...) ab.
Legacy-Antworthelfer wie createChannelTurnReplyPipeline,
dispatchInboundReplyWithBase und recordInboundSessionAndDispatchReply
bleiben für Kompatibilitäts-Dispatcher verfügbar. Verwenden Sie diese Namen nicht für neuen
Channel-Code; neue Plugins sollten mit dem message-Adapter, Belegen und
Receive-/Send-Lifecycle-Helfern unter openclaw/plugin-sdk/channel-outbound beginnen.
Channels, die Inbound-Autorisierung migrieren, können den experimentellen
Subpfad openclaw/plugin-sdk/channel-ingress-runtime aus Runtime-Receive-
Pfaden verwenden. Der Subpfad belässt Plattform-Lookup und Seiteneffekte im Plugin,
während Allowlist-Zustandsauflösung, Routen-/Sender-/Command-/Event-/Aktivierungs-
Entscheidungen, redigierte Diagnosen und Turn-Admission-Mapping geteilt werden. Belassen Sie die
Normalisierung der Plugin-Identität in dem Descriptor, den Sie an den Resolver übergeben; serialisieren Sie keine
rohen Match-Werte aus dem aufgelösten Zustand oder der Entscheidung. Siehe
Channel ingress API für API-Design,
Owner-Grenze und Testerwartungen.
Wenn Ihr Channel Tippindikatoren außerhalb eingehender Antworten unterstützt, stellen Sie
heartbeat.sendTyping(...) im Channel-Plugin bereit. Der Core ruft es mit dem
aufgelösten Heartbeat-Zustellziel auf, bevor der Heartbeat-Modelllauf startet, und
verwendet den gemeinsamen Typing-Keepalive-/Cleanup-Lifecycle. Fügen Sie heartbeat.clearTyping(...)
hinzu, wenn die Plattform ein explizites Stoppsignal benötigt.
Wenn Ihr Channel Message-Tool-Parameter hinzufügt, die Medienquellen tragen, stellen Sie diese
Parameternamen über describeMessageTool(...).mediaSourceParams bereit. Der Core verwendet
diese explizite Liste für Sandbox-Pfadnormalisierung und ausgehende Medienzugriffs-
Richtlinie, sodass Plugins keine Shared-Core-Sonderfälle für providerspezifische
Avatar-, Attachment- oder Cover-Image-Parameter benötigen.
Bevorzugen Sie die Rückgabe einer nach Aktionen geschlüsselten Map wie
{ "set-profile": ["avatarUrl", "avatarPath"] }, damit nicht zusammenhängende Aktionen nicht
die Medienargumente einer anderen Aktion erben. Ein flaches Array funktioniert weiterhin für Parameter, die
absichtlich über jede bereitgestellte Aktion hinweg geteilt werden.
Channels, die eine temporäre öffentliche URL für einen plattformseitigen Medienabruf
bereitstellen müssen, können createHostedOutboundMediaStore(...) aus
openclaw/plugin-sdk/outbound-media mit Plugin-Zustandsspeichern verwenden. Belassen Sie Plattform-
Routenparsing und Token-Durchsetzung im Channel-Plugin; der gemeinsame Helper
ist nur für Medienladen, Ablaufmetadaten, Chunk-Zeilen und Cleanup zuständig.
Wenn Ihr Channel providerspezifische Formung für message(action="send") benötigt,
bevorzugen Sie actions.prepareSendPayload(...). Legen Sie native Cards, Blocks, Embeds oder
andere dauerhafte Daten unter payload.channelData.<channel> ab und lassen Sie den Core
den tatsächlichen Send über den Outbound-/Message-Adapter ausführen. Verwenden Sie
actions.handleAction(...) für Send nur als Kompatibilitäts-Fallback für
Payloads, die nicht serialisiert und erneut versucht werden können.
Wenn Ihre Plattform zusätzlichen Scope in Konversations-IDs speichert, belassen Sie dieses Parsing
im Plugin mit messaging.resolveSessionConversation(...). Das ist der
kanonische Hook zum Abbilden von rawId auf die Basiskonversations-ID, optionale Thread-
ID, explizite baseConversationId und alle parentConversationCandidates.
Wenn Sie parentConversationCandidates zurückgeben, halten Sie sie vom
engsten Parent bis zur breitesten/Basiskonversation sortiert.
Verwenden Sie openclaw/plugin-sdk/channel-route, wenn Plugin-Code routenartige
Felder normalisieren, einen untergeordneten Thread mit seiner Parent-Route vergleichen oder einen
stabilen Dedupe-Schlüssel aus { channel, to, accountId, threadId } erstellen muss. Der Helper
normalisiert numerische Thread-IDs auf dieselbe Weise wie der Core, daher sollten Plugins ihn gegenüber
Ad-hoc-String(threadId)-Vergleichen bevorzugen.
Plugins mit providerspezifischer Zielgrammatik sollten
messaging.resolveOutboundSessionRoute(...) bereitstellen, damit der Core provider-native
Sitzungs- und Thread-Identität erhält, ohne Parser-Shims zu verwenden.
Gebündelte Plugins, die dasselbe Parsing benötigen, bevor die Channel-Registry startet,
können außerdem eine Top-Level-Datei session-key-api.ts mit einem passenden
resolveSessionConversation(...)-Export bereitstellen. Der Core verwendet diese bootstrap-sichere Oberfläche
nur, wenn die Runtime-Plugin-Registry noch nicht verfügbar ist.
messaging.resolveParentConversationCandidates(...) bleibt als
Legacy-Kompatibilitäts-Fallback verfügbar, wenn ein Plugin nur Parent-Fallbacks zusätzlich
zur generischen/rohen ID benötigt. Wenn beide Hooks existieren, verwendet der Core zuerst
resolveSessionConversation(...).parentConversationCandidates und fällt nur dann
auf resolveParentConversationCandidates(...) zurück, wenn der kanonische Hook
sie auslässt.
Genehmigungen und Channel-Fähigkeiten
Die meisten Channel-Plugins benötigen keinen genehmigungsspezifischen Code.
- Core besitzt
/approveim selben Chat, gemeinsame Payloads für Genehmigungsbuttons und generische Fallback-Zustellung. - Bevorzugen Sie ein einzelnes
approvalCapability-Objekt auf dem Channel-Plugin, wenn der Channel genehmigungsspezifisches Verhalten benötigt. ChannelPlugin.approvalswurde entfernt. Legen Sie Fakten zu Genehmigungszustellung, nativem Verhalten, Rendering und Auth aufapprovalCapabilityab.plugin.authist nur für Login/Logout; Core liest Genehmigungs-Auth-Hooks nicht mehr aus diesem Objekt.approvalCapability.authorizeActorActionundapprovalCapability.getActionAvailabilityStatesind die kanonische Nahtstelle für Genehmigungs-Auth.- Verwenden Sie
approvalCapability.getActionAvailabilityStatefür die Auth-Verfügbarkeit von Genehmigungen im selben Chat. Halten Sie konfigurierte Genehmigende für/approveverfügbar, auch wenn native Zustellung deaktiviert ist; verwenden Sie stattdessen den Status der nativen initiierenden Oberfläche für Hinweise zu Zustellung/Einrichtung. - Wenn Ihr Channel native Exec-Genehmigungen bereitstellt, verwenden Sie
approvalCapability.getExecInitiatingSurfaceStatefür den Status der initiierenden Oberfläche/des nativen Clients, wenn er sich von der Genehmigungs-Auth im selben Chat unterscheidet. Core verwendet diesen Exec-spezifischen Hook, umenabledvondisabledzu unterscheiden, zu entscheiden, ob der initiierende Channel native Exec-Genehmigungen unterstützt, und den Channel in Fallback-Hinweise für native Clients aufzunehmen.createApproverRestrictedNativeApprovalCapability(...)ergänzt dies für den üblichen Fall. - Verwenden Sie
outbound.shouldSuppressLocalPayloadPromptoderoutbound.beforeDeliverPayloadfür Channel-spezifisches Payload-Lebenszyklusverhalten, etwa zum Ausblenden doppelter lokaler Genehmigungsaufforderungen oder zum Senden von Tippindikatoren vor der Zustellung. - Verwenden Sie
approvalCapability.deliverynur für natives Genehmigungsrouting oder Fallback-Unterdrückung. - Verwenden Sie
approvalCapability.nativeRuntimefür Channel-eigene native Genehmigungsfakten. Halten Sie dies auf heißen Channel-Einstiegspunkten mitcreateLazyChannelApprovalNativeRuntimeAdapter(...)lazy; der Adapter kann Ihr Runtime-Modul bei Bedarf importieren und Core trotzdem den Genehmigungslebenszyklus zusammensetzen lassen. - Verwenden Sie
approvalCapability.rendernur, wenn ein Channel wirklich benutzerdefinierte Genehmigungs-Payloads statt des gemeinsamen Renderers benötigt. - Verwenden Sie
approvalCapability.describeExecApprovalSetup, wenn der Channel in der Antwort für den deaktivierten Pfad die genauen Konfigurationsschalter erklären soll, die zum Aktivieren nativer Exec-Genehmigungen nötig sind. Der Hook erhält{ channel, channelLabel, accountId }; Channels mit benannten Accounts sollten accountbezogene Pfade wiechannels.<channel>.accounts.<id>.execApprovals.*statt Top-Level-Defaults ausgeben. - Verwenden Sie
approvalCapability.describePluginApprovalSetup, wenn Hinweise bei Plugin-Genehmigungsfehlern für No-Route- und Timeout-Fehler sicher angezeigt werden können.createApproverRestrictedNativeApprovalCapability(...)leitet dies nicht ausdescribeExecApprovalSetupab; übergeben Sie denselben Helper nur dann ausdrücklich, wenn Plugin- und Exec-Genehmigungen wirklich dieselbe native Einrichtung verwenden. - Wenn ein Channel aus vorhandener Konfiguration stabile eigentümerähnliche DM-Identitäten ableiten kann, verwenden Sie
createResolvedApproverActionAuthAdapterausopenclaw/plugin-sdk/approval-runtime, um/approveim selben Chat ohne genehmigungsspezifische Core-Logik einzuschränken. - Wenn benutzerdefinierte Genehmigungs-Auth absichtlich nur Fallback im selben Chat zulässt, geben Sie
markImplicitSameChatApprovalAuthorization({ authorized: true })ausopenclaw/plugin-sdk/approval-auth-runtimezurück; andernfalls behandelt Core das Ergebnis als ausdrückliche Genehmigenden-Autorisierung. - Wenn ein Channel-eigener nativer Callback Genehmigungen direkt auflöst, verwenden Sie
isImplicitSameChatApprovalAuthorization(...)vor der Auflösung, damit impliziter Fallback weiterhin durch die normale Actor-Autorisierung des Channels läuft. - Wenn ein Channel native Genehmigungszustellung benötigt, halten Sie den Channel-Code auf Zielnormalisierung sowie Transport-/Präsentationsfakten fokussiert. Verwenden Sie
createChannelExecApprovalProfile,createChannelNativeOriginTargetResolver,createChannelApproverDmTargetResolverundcreateApproverRestrictedNativeApprovalCapabilityausopenclaw/plugin-sdk/approval-runtime. Legen Sie die Channel-spezifischen Fakten hinterapprovalCapability.nativeRuntime, idealerweise übercreateChannelApprovalNativeRuntimeAdapter(...)odercreateLazyChannelApprovalNativeRuntimeAdapter(...), damit Core den Handler zusammensetzen und Anfragefilterung, Routing, Deduplizierung, Ablauf, Gateway-Abonnement und Hinweise auf anderweitiges Routing besitzen kann.nativeRuntimeist in einige kleinere Nahtstellen aufgeteilt: - Verwenden Sie
createNativeApprovalChannelRouteGatesausopenclaw/plugin-sdk/approval-native-runtime, wenn ein Channel sowohl native Zustellung vom Session-Ursprung als auch explizite Weiterleitungsziele für Genehmigungen unterstützt. Der Helper zentralisiert Auswahl der Genehmigungskonfiguration,mode-Behandlung, Agent-/Session-Filter, Account-Bindung, Session-Zielabgleich und Ziellistenabgleich, während Aufrufer weiterhin Channel-ID, Standard-Weiterleitungsmodus, Account-Lookup, Transport-Aktivierungsprüfung, Zielnormalisierung und Zielauflösung der Turn-Quelle besitzen. Verwenden Sie ihn nicht, um Core-eigene Channel-Policy-Defaults zu erstellen; übergeben Sie den dokumentierten Standardmodus des Channels ausdrücklich. createChannelNativeOriginTargetResolververwendet standardmäßig den gemeinsamen Channel-Route-Matcher für{ to, accountId, threadId }-Ziele. Übergeben SietargetsMatchnur, wenn ein Channel Provider-spezifische Äquivalenzregeln hat, etwa Slack-Zeitstempel-Präfixabgleich.- Übergeben Sie
normalizeTargetForMatchancreateChannelNativeOriginTargetResolver, wenn der Channel Provider-IDs kanonisieren muss, bevor der Standard-Route-Matcher oder ein benutzerdefiniertertargetsMatch-Callback läuft, während das ursprüngliche Ziel für die Zustellung erhalten bleibt. Verwenden SienormalizeTargetnur, wenn das aufgelöste Zustellziel selbst kanonisiert werden soll. availability- ob der Account konfiguriert ist und ob eine Anfrage verarbeitet werden sollpresentation- das gemeinsame Genehmigungs-View-Model auf ausstehende/aufgelöste/abgelaufene native Payloads oder finale Aktionen abbildentransport- Ziele vorbereiten und native Genehmigungsnachrichten senden/aktualisieren/löscheninteractions- optionale Hooks zum Binden/Aufheben/Löschen von Aktionen für native Buttons oder Reaktionen, plus ein optionalercancelDelivered-Hook. Implementieren SiecancelDelivered, wenndeliverPendingprozessinternen oder persistenten Zustand registriert (etwa einen Reaktionsziel-Store), damit dieser Zustand freigegeben werden kann, falls ein Handler-Stop die Zustellung abbricht, bevorbindPendingläuft, oder wennbindPendingkein Handle zurückgibtobserve- optionale Hooks für Zustellungsdiagnosen- Wenn der Channel Runtime-eigene Objekte wie Client, Token, Bolt-App oder Webhook-Empfänger benötigt, registrieren Sie sie über
openclaw/plugin-sdk/channel-runtime-context. Die generische Runtime-Kontext-Registry lässt Core capability-gesteuerte Handler aus dem Channel-Startzustand bootstrappen, ohne genehmigungsspezifischen Wrapper-Klebstoff hinzuzufügen. - Greifen Sie nur dann zu den niedrigeren Ebenen
createChannelApprovalHandlerodercreateChannelNativeApprovalRuntime, wenn die capability-gesteuerte Nahtstelle noch nicht ausdrucksstark genug ist. - Native Genehmigungs-Channels müssen sowohl
accountIdals auchapprovalKinddurch diese Helper routen.accountIdhält Multi-Account-Genehmigungsrichtlinien auf das richtige Bot-Konto beschränkt, undapprovalKindhält Exec- gegenüber Plugin-Genehmigungsverhalten für den Channel verfügbar, ohne hartcodierte Branches in Core. - Core besitzt jetzt auch Hinweise zur Genehmigungsumleitung. Channel-Plugins sollten keine eigenen Folgemeldungen wie „Genehmigung ging an DMs / einen anderen Channel“ aus
createChannelNativeApprovalRuntimesenden; stattdessen sollen sie korrektes Routing für Ursprung + Genehmigenden-DM über die gemeinsamen Genehmigungs-Capability-Helper bereitstellen und Core die tatsächlichen Zustellungen aggregieren lassen, bevor ein Hinweis zurück in den initiierenden Chat gepostet wird. - Bewahren Sie die Art der zugestellten Genehmigungs-ID Ende-zu-Ende. Native Clients sollten Exec- gegenüber Plugin-Genehmigungsrouting nicht aus Channel-lokalem Zustand erraten oder umschreiben.
- Unterschiedliche Genehmigungsarten können absichtlich unterschiedliche native Oberflächen bereitstellen.
Aktuelle gebündelte Beispiele:
- Slack hält natives Genehmigungsrouting sowohl für Exec- als auch Plugin-IDs verfügbar.
- Matrix behält dasselbe native DM-/Channel-Routing und dieselbe Reaktions-UX für Exec- und Plugin-Genehmigungen bei, während Auth weiterhin je nach Genehmigungsart abweichen kann.
createApproverRestrictedNativeApprovalAdapterexistiert weiterhin als Kompatibilitäts-Wrapper, aber neuer Code sollte den Capability-Builder bevorzugen undapprovalCapabilityauf dem Plugin bereitstellen.
Für heiße Channel-Einstiegspunkte bevorzugen Sie die schmaleren Runtime-Unterpfade, wenn Sie nur einen Teil dieser Familie benötigen:
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
Ebenso bevorzugen Sie openclaw/plugin-sdk/setup-runtime,
openclaw/plugin-sdk/setup-runtime,
openclaw/plugin-sdk/reply-runtime,
openclaw/plugin-sdk/reply-dispatch-runtime,
openclaw/plugin-sdk/reply-reference und
openclaw/plugin-sdk/reply-chunking, wenn Sie die breitere Umbrella-
Oberfläche nicht benötigen.
Speziell für die Einrichtung:
openclaw/plugin-sdk/setup-runtimedeckt die runtime-sicheren Setup-Helper ab:createSetupTranslator, importsichere Setup-Patch-Adapter (createPatchedAccountSetupAdapter,createEnvPatchedAccountSetupAdapter,createSetupInputPresenceValidator), Lookup-Note-Ausgabe,promptResolvedAllowFrom,splitSetupEntriesund die delegierten Setup-Proxy-Builderopenclaw/plugin-sdk/setup-runtimeenthält die env-bewusste Adapter-Nahtstelle fürcreateEnvPatchedAccountSetupAdapteropenclaw/plugin-sdk/channel-setupdeckt die Setup- Builder für optionale Installation plus einige setup-sichere Primitive ab:createOptionalChannelSetupSurface,createOptionalChannelSetupAdapter,
Wenn Ihr Channel env-gesteuerte Einrichtung oder Auth unterstützt und generische Startup-/Konfigurations-
Flows diese Env-Namen kennen sollen, bevor die Runtime lädt, deklarieren Sie sie im
Plugin-Manifest mit channelEnvVars. Behalten Sie Channel-Runtime-envVars oder lokale
Konstanten nur für operatorseitige Texte.
Wenn Ihr Channel in status, channels list, channels status oder
SecretRef-Scans erscheinen kann, bevor die Plugin-Runtime startet, fügen Sie openclaw.setupEntry in
package.json hinzu. Dieser Einstiegspunkt sollte in schreibgeschützten Befehls-
pfaden sicher importierbar sein und die Channel-Metadaten, den setup-sicheren Konfigurationsadapter, den Status-
Adapter und die Channel-Secret-Zielmetadaten zurückgeben, die für diese Zusammenfassungen nötig sind. Starten Sie
keine Clients, Listener oder Transport-Runtimes aus dem Setup-Einstieg.
Halten Sie auch den Importpfad des Haupt-Channel-Einstiegs schmal. Discovery kann den
Entry und das Channel-Plugin-Modul auswerten, um Capabilities zu registrieren, ohne den
Channel zu aktivieren. Dateien wie channel-plugin-api.ts sollten das Channel-
Plugin-Objekt exportieren, ohne Setup-Assistenten, Transport-Clients, Socket-
Listener, Subprozess-Starter oder Service-Startup-Module zu importieren. Legen Sie diese Runtime-
Teile in Module, die aus registerFull(...), Runtime-Settern oder lazy
Capability-Adaptern geladen werden.
createOptionalChannelSetupWizard, DEFAULT_ACCOUNT_ID,
createTopLevelChannelDmPolicy, setSetupChannelEnabled und
splitSetupEntries
- verwenden Sie die breitere
openclaw/plugin-sdk/setup-Nahtstelle nur, wenn Sie auch die schwereren gemeinsamen Setup-/Konfigurations-Helper benötigen, etwamoveSingleAccountChannelSectionToDefaultAccount(...)
Wenn Ihr Channel in Setup-Oberflächen nur „installieren Sie zuerst dieses Plugin“ anzeigen soll,
bevorzugen Sie createOptionalChannelSetupSurface(...). Der generierte
Adapter/Assistent schlägt bei Konfigurationsschreibvorgängen und Finalisierung geschlossen fehl und verwendet
dieselbe Installations-erforderlich-Meldung über Validierung, Finalisierung und Docs-Link-
Text hinweg wieder.
Für andere heiße Channel-Pfade bevorzugen Sie die schmalen Helper gegenüber breiteren Legacy- Oberflächen:
openclaw/plugin-sdk/account-core,openclaw/plugin-sdk/account-id,openclaw/plugin-sdk/account-resolutionundopenclaw/plugin-sdk/account-helpersfür Mehrkonto-Konfiguration und Fallback für Standardkontoopenclaw/plugin-sdk/inbound-envelopeundopenclaw/plugin-sdk/channel-inboundfür eingehende Route/Envelope und Record-and-Dispatch-Verdrahtungopenclaw/plugin-sdk/channel-targetsfür Hilfsfunktionen zum Parsen von Zielenopenclaw/plugin-sdk/outbound-mediafür das Laden von Medien undopenclaw/plugin-sdk/channel-outboundfür ausgehende Identität/Sende-Delegates und Payload-PlanungbuildThreadAwareOutboundSessionRoute(...)ausopenclaw/plugin-sdk/channel-core, wenn eine ausgehende Route eine explizitereplyToId/threadIdbeibehalten oder die aktuelle:thread:-Sitzung wiederherstellen soll, nachdem der Basis-Sitzungsschlüssel weiterhin übereinstimmt. Provider-Plugins können Priorität, Suffix-Verhalten und Thread-ID-Normalisierung überschreiben, wenn ihre Plattform native Thread-Zustellungssemantik hat.openclaw/plugin-sdk/thread-bindings-runtimefür den Lebenszyklus von Thread-Bindings und Adapter-Registrierungopenclaw/plugin-sdk/agent-media-payloadnur, wenn ein Legacy-Feldlayout für Agent/Medien- Payload weiterhin erforderlich istopenclaw/plugin-sdk/telegram-command-configfür die Normalisierung benutzerdefinierter Telegram-Befehle, Duplikat-/Konfliktvalidierung und einen fallback-stabilen Befehls- Konfigurationsvertrag
Auth-only-Kanäle können normalerweise beim Standardpfad bleiben: Core übernimmt Genehmigungen, und das Plugin stellt nur Outbound-/Auth-Fähigkeiten bereit. Native Genehmigungskanäle wie Matrix, Slack, Telegram und benutzerdefinierte Chat-Transporte sollten die gemeinsamen nativen Hilfsfunktionen verwenden, statt ihren eigenen Genehmigungslebenszyklus zu implementieren.
Richtlinie für eingehende Erwähnungen
Halten Sie die Verarbeitung eingehender Erwähnungen in zwei Schichten getrennt:
- Plugin-eigene Beweiserfassung
- gemeinsame Richtlinienauswertung
Verwenden Sie openclaw/plugin-sdk/channel-mention-gating für Entscheidungen zur Erwähnungsrichtlinie.
Verwenden Sie openclaw/plugin-sdk/channel-inbound nur, wenn Sie den breiteren Barrel für eingehende
Hilfsfunktionen benötigen.
Gut geeignet für Plugin-lokale Logik:
- Erkennung von Antworten an den Bot
- Erkennung von zitierten Bots
- Prüfungen der Thread-Teilnahme
- Ausschlüsse von Service-/Systemnachrichten
- plattformnative Caches, die erforderlich sind, um Bot-Teilnahme nachzuweisen
Gut geeignet für die gemeinsame Hilfsfunktion:
requireMention- explizites Erwähnungsergebnis
- Allowlist für implizite Erwähnungen
- Befehls-Bypass
- endgültige Überspringen-Entscheidung
Bevorzugter Ablauf:
- Berechnen Sie lokale Erwähnungsfakten.
- Übergeben Sie diese Fakten an
resolveInboundMentionDecision({ facts, policy }). - Verwenden Sie
decision.effectiveWasMentioned,decision.shouldBypassMentionunddecision.shouldSkipin Ihrem eingehenden Gate.
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 stellt dieselben gemeinsamen Erwähnungs-Hilfsfunktionen für
gebündelte Kanal-Plugins bereit, die bereits von Runtime-Injection abhängen:
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
Wenn Sie nur implicitMentionKindWhen und
resolveInboundMentionDecision benötigen, importieren Sie aus
openclaw/plugin-sdk/channel-mention-gating, um das Laden nicht zusammenhängender eingehender
Runtime-Hilfsfunktionen zu vermeiden.
Verwenden Sie resolveInboundMentionDecision({ facts, policy }) für Mention-Gating.
Schritt-für-Schritt-Anleitung
Package and manifest
Erstellen Sie die Standard-Plugin-Dateien. Das Feld channel in package.json ist
der Auslöser dafür, dass dies ein Kanal-Plugin ist. Die vollständige Oberfläche für Paketmetadaten
finden Sie unter Plugin-Einrichtung und -Konfiguration:
{"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 validiert plugins.entries.acme-chat.config. Verwenden Sie es für
Plugin-eigene Einstellungen, die nicht zur Kontokonfiguration des Kanals gehören. channelConfigs
validiert channels.acme-chat und ist die Cold-Path-Quelle, die von Konfigurationsschema,
Einrichtung und UI-Oberflächen verwendet wird, bevor die Plugin-Runtime geladen wird.
Build the channel plugin object
Die Schnittstelle ChannelPlugin hat viele optionale Adapter-Oberflächen. Beginnen Sie mit
dem Minimum - id und setup - und fügen Sie Adapter hinzu, wenn Sie sie benötigen.
Erstellen Sie 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); }, }, },});Für Kanäle, die sowohl kanonische DM-Schlüssel auf oberster Ebene als auch verschachtelte Legacy-Schlüssel akzeptieren, verwenden Sie die Hilfsfunktionen aus plugin-sdk/channel-config-helpers: resolveChannelDmAccess, resolveChannelDmPolicy, resolveChannelDmAllowFrom und normalizeChannelDmPolicy halten konto-lokale Werte vor geerbten Root-Werten. Kombinieren Sie denselben Resolver mit Doctor-Reparatur über normalizeLegacyDmAliases, damit Runtime und Migration denselben Vertrag lesen.
What createChatChannelPlugin does for you
Statt Low-Level-Adapter-Schnittstellen manuell zu implementieren, übergeben Sie deklarative Optionen, und der Builder setzt sie zusammen:
| Option | Was verdrahtet wird |
|---|---|
security.dm |
Bereichsbezogener DM-Sicherheits-Resolver aus Konfigurationsfeldern |
pairing.text |
Textbasierter DM-Pairing-Ablauf mit Code-Austausch |
threading |
Resolver für den Antwortmodus (fest, kontobezogen oder benutzerdefiniert) |
outbound.attachedResults |
Sendefunktionen, die Ergebnis-Metadaten zurückgeben (Nachrichten-IDs) |
Sie können statt der deklarativen Optionen auch rohe Adapterobjekte übergeben, wenn Sie vollständige Kontrolle benötigen.
Rohe ausgehende Adapter können eine Funktion chunker(text, limit, ctx) definieren.
Das optionale ctx.formatting trägt Formatierungsentscheidungen zur Zustellungszeit
wie maxLinesPerMessage; wenden Sie sie vor dem Senden an, damit Antwort-Threading
und Chunk-Grenzen einmal durch die gemeinsame ausgehende Zustellung aufgelöst werden.
Sendekontexte enthalten außerdem replyToIdSource (implicit oder explicit),
wenn ein natives Antwortziel aufgelöst wurde, sodass Payload-Hilfsfunktionen
explizite Antwort-Tags beibehalten können, ohne einen impliziten einmalig nutzbaren Antwort-Slot zu verbrauchen.
Wire the entry point
Erstellen Sie 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(/* ... */); },});Put channel-owned CLI descriptors in registerCliMetadata(...), damit OpenClaw
sie in der Root-Hilfe anzeigen kann, ohne die vollständige Channel-Runtime zu aktivieren,
während normale vollständige Ladevorgänge weiterhin dieselben Deskriptoren für die echte Befehlsregistrierung
übernehmen. Behalten Sie registerFull(...) für reine Runtime-Arbeit bei.
Wenn registerFull(...) Gateway-RPC-Methoden registriert, verwenden Sie ein
Plugin-spezifisches Präfix. Core-Admin-Namensräume (config.*,
exec.approvals.*, wizard.*, update.*) bleiben reserviert und werden immer
zu operator.admin aufgelöst.
defineChannelPluginEntry handhabt die Aufteilung des Registrierungsmodus automatisch. Siehe
Einstiegspunkte für alle
Optionen.
Add a setup entry
Erstellen Sie setup-entry.ts für leichtgewichtiges Laden während des Onboardings:
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";import { acmeChatPlugin } from "./src/channel.js"; export default defineSetupPluginEntry(acmeChatPlugin);OpenClaw lädt dies anstelle des vollständigen Einstiegspunkts, wenn der Channel deaktiviert oder nicht konfiguriert ist. Dadurch wird vermieden, dass während Setup-Abläufen schwergewichtiger Runtime-Code geladen wird. Weitere Details finden Sie unter Setup und Konfiguration.
Gebündelte Workspace-Channels, die setup-sichere Exporte in Sidecar-
Module aufteilen, können defineBundledChannelSetupEntry(...) aus
openclaw/plugin-sdk/channel-entry-contract verwenden, wenn sie außerdem einen
expliziten Runtime-Setter zur Setup-Zeit benötigen.
Handle inbound messages
Ihr Plugin muss Nachrichten von der Plattform empfangen und an OpenClaw weiterleiten. Das typische Muster ist ein Webhook, der die Anfrage verifiziert und sie über den Inbound-Handler Ihres Channels weiterleitet:
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
Schreiben Sie colocated Tests in 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/Gemeinsame Test-Helper finden Sie unter Testen.
Dateistruktur
<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)Fortgeschrittene Themen
Feste, kontobezogene oder benutzerdefinierte Antwortmodi
describeMessageTool und Action-Erkennung
inferTargetChatType, looksLikeId, reservedLiterals, resolveTarget
TTS, STT, Medien, Subagent über api.runtime
Gemeinsamer Lebenszyklus für eingehende Events: ingest, resolve, record, dispatch, finalize
Nächste Schritte
- Provider-Plugins - wenn Ihr Plugin auch Modelle bereitstellt
- SDK-Überblick - vollständige Referenz für Subpath-Importe
- SDK-Tests - Test-Dienstprogramme und Vertragstests
- Plugin-Manifest - vollständiges Manifest-Schema