Building plugins
Création de plugins de canal
Ce guide permet de créer un plugin de canal qui connecte OpenClaw à une plateforme de messagerie : sécurité des messages privés, appairage, réponses dans les fils de discussion et messagerie sortante.
Responsabilités de votre plugin
Les plugins de canal n’implémentent pas d’outils d’envoi, de modification ou de réaction ; le cœur fournit un
outil message partagé. Votre plugin gère :
- Configuration - résolution des comptes et assistant de configuration
- Sécurité - politique des messages privés et listes d’autorisation
- Appairage - processus d’approbation des messages privés
- Grammaire de session - correspondance entre les identifiants de conversation propres au fournisseur et les conversations de base, les identifiants de fil de discussion et les replis vers les conversations parentes
- Sortie - envoi de texte, de médias et de sondages à la plateforme
- Fils de discussion - organisation des réponses dans les fils de discussion
- Indication de saisie du Heartbeat - signaux facultatifs de saisie ou d’activité pour les cibles de livraison du Heartbeat
Le cœur gère l’outil de messagerie partagé, le raccordement aux prompts, la structure externe de la clé de session,
la comptabilité générique de :thread: et la distribution.
Adaptateur de messagerie
Exposez un adaptateur message avec defineChannelMessageAdapter depuis
openclaw/plugin-sdk/channel-outbound. Déclarez uniquement les fonctionnalités durables d’envoi final
que votre transport natif prend réellement en charge, en les étayant par un test de contrat
qui prouve l’effet secondaire natif et l’accusé de réception renvoyé. Faites pointer les envois de texte et de médias
vers les mêmes fonctions de transport que celles utilisées par l’adaptateur outbound historique. Pour
le contrat d’API complet, la matrice des fonctionnalités, les règles d’accusé de réception, la finalisation
des aperçus en direct, la politique d’acquittement des réceptions, les tests et la table de migration, consultez
API de sortie des canaux.
Si votre adaptateur outbound existant dispose déjà des bonnes méthodes d’envoi et
des métadonnées de fonctionnalités appropriées, dérivez l’adaptateur message avec
createChannelMessageAdapterFromOutbound(...) plutôt que d’écrire manuellement un autre
pont. Les envois de l’adaptateur renvoient des valeurs MessageReceipt. Pour les identifiants historiques, dérivez-les
avec listMessageReceiptPlatformIds(...) ou
resolveMessageReceiptPrimaryId(...) au lieu de conserver des champs messageIds
parallèles.
Déclarez précisément les fonctionnalités en direct et de finalisation : le cœur les utilise pour déterminer ce qu’un canal peut faire, et toute divergence entre le comportement déclaré et le comportement réel constitue un échec du test de contrat :
| Surface | Valeurs |
|---|---|
message.live.capabilities |
draftPreview, previewFinalization, progressUpdates, nativeStreaming, quietFinalization |
message.live.finalizer.capabilities |
finalEdit, normalFallback, discardPending, previewReceipt, retainOnAmbiguousFailure |
Les canaux qui finalisent sur place un brouillon d’aperçu doivent faire passer la logique d’exécution
par defineFinalizableLivePreviewAdapter(...) et
deliverWithFinalizableLivePreviewAdapter(...), et étayer les fonctionnalités
déclarées avec des tests verifyChannelMessageLiveCapabilityAdapterProofs(...)
et verifyChannelMessageLiveFinalizerProofs(...), afin que les comportements natifs d’aperçu,
de progression, de modification, de repli ou de conservation, de nettoyage et d’accusé de réception ne puissent pas diverger
silencieusement.
Les récepteurs entrants qui différèrent les acquittements de la plateforme doivent déclarer
message.receive.defaultAckPolicy et supportedAckPolicies au lieu de dissimuler
le moment de l’acquittement dans l’état local du moniteur. Couvrez chaque politique déclarée avec
verifyChannelMessageReceiveAckPolicyAdapterProofs(...).
Les anciens assistants de réponse tels que createChannelTurnReplyPipeline,
dispatchInboundReplyWithBase et recordInboundSessionAndDispatchReply
restent disponibles pour les distributeurs compatibles. Ne les utilisez pas pour le nouveau
code de canal ; commencez plutôt par l’adaptateur message, les accusés de réception et les assistants de cycle de vie
de réception et d’envoi disponibles dans openclaw/plugin-sdk/channel-outbound.
Réception entrante (expérimentale)
Les canaux qui migrent l’autorisation des messages entrants peuvent utiliser le sous-chemin expérimental
openclaw/plugin-sdk/channel-ingress-runtime depuis les chemins d’exécution de réception.
Il accepte les informations de la plateforme, les listes d’autorisation brutes, les descripteurs de route, les informations de commande
et la configuration des groupes d’accès, puis renvoie les projections de l’expéditeur, de la route, de la commande et de l’activation,
ainsi que le graphe de réception ordonné, tandis que la recherche sur la plateforme et les effets
secondaires restent dans le plugin. Conservez la normalisation de l’identité du plugin dans le
descripteur transmis au résolveur ; ne sérialisez pas les valeurs de correspondance brutes issues
de l’état résolu ou de la décision. Consultez
API de réception des canaux pour la conception de l’API,
la frontière de responsabilité et les attentes relatives aux tests. L’ancien sous-chemin
openclaw/plugin-sdk/channel-ingress reste exporté en tant que façade de compatibilité
obsolète pour les plugins tiers.
Indicateurs de saisie
Si votre canal prend en charge les indicateurs de saisie en dehors des réponses entrantes, exposez
heartbeat.sendTyping(...) sur le plugin de canal. Le cœur l’appelle avec la
cible de livraison résolue du Heartbeat avant le démarrage de l’exécution du modèle Heartbeat et
utilise le cycle de vie partagé de maintien et de nettoyage de l’indicateur de saisie. Ajoutez
heartbeat.clearTyping(...) lorsque la plateforme nécessite un signal d’arrêt explicite.
Paramètres de source des médias
Si votre canal ajoute à l’outil de messagerie des paramètres contenant des sources de médias, exposez
les noms de ces paramètres via plugin.actions.describeMessageTool(...).mediaSourceParams.
Le cœur utilise cette liste explicite pour normaliser les chemins du bac à sable et appliquer la politique
d’accès aux médias sortants, de sorte que les plugins n’aient pas besoin de cas particuliers dans le cœur partagé
pour les paramètres propres au fournisseur concernant les avatars, les pièces jointes ou les images de couverture.
Préférez une table indexée par action, telle que { "set-profile": ["avatarUrl", "avatarPath"] },
afin que les actions sans rapport n’héritent pas des arguments de médias d’une autre action. Un tableau plat
convient toujours aux paramètres volontairement partagés entre toutes les actions exposées.
Les canaux qui doivent exposer une URL publique temporaire pour permettre à la plateforme de récupérer
un média peuvent utiliser createHostedOutboundMediaStore(...) depuis
openclaw/plugin-sdk/outbound-media avec les magasins d’état du plugin. Conservez l’analyse
des routes de la plateforme et l’application des jetons dans le plugin de canal ; l’assistant partagé
gère uniquement le chargement des médias, les métadonnées d’expiration, les lignes de fragments et le nettoyage.
Mise en forme des charges utiles natives
Si votre canal nécessite une mise en forme propre au fournisseur pour message(action="send"),
préférez actions.prepareSendPayload(...). Placez les cartes, blocs, intégrations ou
autres données durables natives sous payload.channelData.<channel> et laissez le cœur effectuer l’envoi
par l’adaptateur de sortie ou de messagerie. Utilisez actions.handleAction(...) pour l’envoi
uniquement comme repli de compatibilité pour les charges utiles qui ne peuvent pas être sérialisées et
réessayées.
Grammaire des conversations de session
Si votre plateforme stocke une portée supplémentaire dans les identifiants de conversation, conservez cette analyse
dans le plugin avec messaging.resolveSessionConversation(...). Il s’agit du
point d’extension canonique permettant d’associer rawId à l’identifiant de conversation de base, à un
identifiant de fil de discussion facultatif, à un baseConversationId explicite et à d’éventuels
parentConversationCandidates. Lorsque vous renvoyez parentConversationCandidates,
classez-les du parent le plus précis à la conversation la plus générale ou de base.
messaging.resolveParentConversationCandidates(...) est un repli de compatibilité
obsolète destiné aux plugins qui ont uniquement besoin de replis vers les parents en complément de
l’identifiant générique ou brut. Si les deux points d’extension existent, le cœur utilise d’abord
resolveSessionConversation(...).parentConversationCandidates et ne se replie
sur resolveParentConversationCandidates(...) que lorsque le point d’extension canonique
les omet.
Les plugins intégrés qui ont besoin de la même analyse avant le démarrage du registre des canaux
peuvent exposer un fichier session-key-api.ts de premier niveau avec une exportation
resolveSessionConversation(...) correspondante (voir les plugins Feishu et Telegram).
Le cœur utilise cette surface compatible avec l’amorçage uniquement lorsque le registre des plugins d’exécution
n’est pas encore disponible.
Utilisez openclaw/plugin-sdk/channel-route lorsque le code du plugin doit normaliser
des champs de type route, comparer un fil enfant à sa route parente ou construire une
clé de déduplication stable à partir de { channel, to, accountId, threadId }. L’assistant
normalise les identifiants numériques de fils de discussion de la même manière que le cœur ; préférez-le donc aux comparaisons
ponctuelles avec String(threadId). Les plugins dotés d’une grammaire de cible propre au fournisseur
doivent exposer messaging.resolveOutboundSessionRoute(...) afin que le cœur obtienne
l’identité de session et de fil de discussion native du fournisseur sans adaptateurs d’analyse.
Prise en charge des liaisons de conversation propres au compte
Définissez conversationBindings.supportsCurrentConversationBinding lorsque le canal
prend en charge les liaisons génériques à la conversation actuelle. createChatChannelPlugin(...)
définit par défaut cette fonctionnalité statique sur true.
Si la prise en charge varie selon le compte configuré, implémentez également
conversationBindings.isCurrentConversationBindingSupported({ accountId }).
Le cœur n’évalue ce point d’extension synchrone qu’après l’activation de la fonctionnalité statique.
Le renvoi de false rend indisponibles pour ce compte la fonctionnalité générique de conversation actuelle,
ainsi que les opérations de liaison, de recherche, de liste, d’actualisation et de déliaison.
Si le point d’extension est omis, la fonctionnalité statique s’applique à chaque compte.
Déterminez la réponse à partir de la configuration du compte ou de l’état d’exécution déjà chargés. Ce
point d’extension contrôle uniquement les liaisons génériques à la conversation actuelle ; il ne remplace pas
les règles de liaison configurées ni le routage des sessions géré par le plugin. Les tests de contrat
doivent couvrir au moins un compte pris en charge et un compte non pris en charge au moyen du
contrat ChannelPlugin["conversationBindings"] exporté par
openclaw/plugin-sdk/channel-core.
Approbations et fonctionnalités des canaux
La plupart des plugins de canal n’ont pas besoin de code propre aux approbations. Le cœur gère la commande
/approve dans la même conversation, les charges utiles partagées des boutons d’approbation et la livraison de repli générique.
ChannelPlugin.approvals a été supprimé ; placez plutôt les informations de livraison, de rendu natif et d’autorisation
des approbations dans un seul objet approvalCapability. plugin.auth concerne uniquement la connexion et la déconnexion :
le cœur ne lit plus les points d’extension d’autorisation des approbations depuis cet objet.
Utilisez approvalCapability.delivery uniquement pour le routage natif des approbations ou la suppression
du repli, et approvalCapability.render uniquement lorsqu’un canal nécessite réellement
des charges utiles d’approbation personnalisées à la place du moteur de rendu partagé.
Autorisation des approbations
approvalCapability.authorizeActorActionetapprovalCapability.getActionAvailabilityStateconstituent le point d’extension canonique pour l’autorisation des approbations.- Utilisez
getActionAvailabilityStatepour connaître la disponibilité de l’autorisation des approbations dans la même conversation. Maintenez les approbateurs configurés disponibles pour/approve, même lorsque la livraison native est désactivée ; utilisez plutôt l’état de la surface native à l’origine de la demande pour les indications de livraison et de configuration. - Si votre canal expose des approbations natives d’exécution, utilisez
approvalCapability.getExecInitiatingSurfaceStatepour l’état de la surface à l’origine de la demande ou du client natif lorsqu’il diffère de l’autorisation des approbations dans la même conversation. Le cœur utilise ce point d’extension propre à l’exécution pour distinguerenableddedisabled, déterminer si le canal à l’origine de la demande prend en charge les approbations natives d’exécution et inclure le canal dans les indications de repli du client natif.createApproverRestrictedNativeApprovalCapability(...)le renseigne pour le cas courant. - Si un canal peut déduire des identités stables de type propriétaire dans les messages privés à partir de la configuration existante,
utilisez
createResolvedApproverActionAuthAdapterdepuisopenclaw/plugin-sdk/approval-runtimepour restreindre/approvedans la même conversation sans ajouter de logique propre aux approbations dans le cœur. - Si une autorisation d’approbation personnalisée permet intentionnellement uniquement le repli dans la même conversation, renvoyez
markImplicitSameChatApprovalAuthorization({ authorized: true })depuisopenclaw/plugin-sdk/approval-auth-runtime; sinon, le cœur considère le résultat comme une autorisation explicite de l’approbateur. - Si un rappel natif géré par le canal résout directement les approbations, utilisez
isImplicitSameChatApprovalAuthorization(...)avant la résolution, afin que le repli implicite passe toujours par l’autorisation normale de l’acteur par le canal.
Cycle de vie des charges utiles et indications de configuration
- Utilisez
outbound.shouldSuppressLocalPayloadPromptououtbound.beforeDeliverPayloadpour les comportements de cycle de vie de la charge utile propres au canal, comme masquer les invites d’approbation locales en double ou envoyer des indicateurs de saisie avant la livraison. - Utilisez
approvalCapability.describeExecApprovalSetuplorsque le canal souhaite que la réponse du chemin désactivé explique précisément les paramètres de configuration nécessaires pour activer les approbations d’exécution natives. Le hook reçoit{ channel, channelLabel, accountId }; les canaux avec comptes nommés doivent afficher des chemins limités au compte, tels quechannels.<channel>.accounts.<id>.execApprovals.*, au lieu des valeurs par défaut de premier niveau. - Utilisez
approvalCapability.describePluginApprovalSetuplorsque les instructions relatives aux échecs d’approbation de Plugin peuvent être affichées sans risque pour les échecs d’approbation de Plugin dus à l’absence de route ou à l’expiration du délai.createApproverRestrictedNativeApprovalCapability(...)ne déduit pas cela dedescribeExecApprovalSetup; transmettez explicitement le même assistant uniquement lorsque les approbations de Plugin et d’exécution utilisent réellement la même configuration native.
Livraison native des approbations
Si un canal nécessite une livraison native des approbations, limitez le code du
canal à la normalisation de la cible ainsi qu’aux informations de transport et de
présentation. Utilisez createChannelExecApprovalProfile,
createChannelNativeOriginTargetResolver,
createChannelApproverDmTargetResolver et
createApproverRestrictedNativeApprovalCapability depuis
openclaw/plugin-sdk/approval-runtime. Placez les informations propres au canal
derrière approvalCapability.nativeRuntime, idéalement via
createChannelApprovalNativeRuntimeAdapter(...) ou
createLazyChannelApprovalNativeRuntimeAdapter(...), afin que le cœur puisse
assembler le gestionnaire et prendre en charge le filtrage des requêtes, le
routage, la déduplication, l’expiration, l’abonnement au Gateway et les avis de
routage vers une autre destination.
nativeRuntime est divisé en plusieurs interfaces plus petites :
availability- indique si le compte est configuré et si une requête doit être traitéepresentation- convertit le modèle de vue d’approbation partagé en charges utiles natives en attente/résolues/expirées ou en actions finalestransport- prépare les cibles, puis envoie/met à jour/supprime les messages d’approbation natifsinteractions- hooks facultatifs d’association/dissociation/suppression d’action pour les boutons ou réactions natifs, ainsi qu’un hook facultatifcancelDelivered. ImplémentezcancelDeliveredlorsquedeliverPendingenregistre un état en cours de processus ou persistant, par exemple un stockage de cibles de réaction, afin que cet état puisse être libéré si l’arrêt d’un gestionnaire annule la livraison avant l’exécution debindPending, ou lorsquebindPendingne renvoie aucun handleobserve- hooks facultatifs de diagnostic de livraison
Autres assistants d’approbation :
- Utilisez
createNativeApprovalChannelRouteGatesdepuisopenclaw/plugin-sdk/approval-native-runtimelorsqu’un canal prend en charge à la fois la livraison native vers l’origine de la session et des cibles explicites de transfert d’approbation. L’assistant centralise la sélection de la configuration d’approbation, la gestion demode, les filtres d’agent/de session, l’association du compte, la correspondance avec la cible de session et la correspondance avec la liste de cibles, tandis que les appelants restent responsables de l’identifiant du canal, du mode de transfert par défaut, de la recherche du compte, de la vérification de l’activation du transport, de la normalisation de la cible et de la résolution de la cible depuis la source du tour. Ne l’utilisez pas pour créer des politiques de canal par défaut appartenant au cœur ; transmettez explicitement le mode par défaut documenté du canal. createChannelNativeOriginTargetResolverutilise par défaut le mécanisme partagé de correspondance des routes de canal pour les cibles{ to, accountId, threadId }. TransmetteztargetsMatchuniquement lorsqu’un canal possède des règles d’équivalence propres au fournisseur, comme la correspondance de préfixe d’horodatage de Slack. TransmetteznormalizeTargetForMatchlorsque le canal doit canonicaliser les identifiants du fournisseur avant l’exécution du mécanisme de correspondance de route par défaut ou d’un rappeltargetsMatchpersonnalisé, tout en conservant la cible d’origine pour la livraison. UtiliseznormalizeTargetuniquement lorsque la cible de livraison résolue elle-même doit être canonicalisée.- Si le canal nécessite des objets appartenant à l’environnement d’exécution,
comme un client, un jeton, une application Bolt ou un récepteur de Webhook,
enregistrez-les via
openclaw/plugin-sdk/channel-runtime-context. Le registre générique de contexte d’exécution permet au cœur d’initialiser les gestionnaires fondés sur les capacités à partir de l’état de démarrage du canal, sans ajouter de code de liaison spécifique aux approbations. - N’utilisez les fonctions de plus bas niveau
createChannelApprovalHandleroucreateChannelNativeApprovalRuntimeque lorsque l’interface fondée sur les capacités n’est pas encore suffisamment expressive. - Les canaux d’approbation natifs doivent acheminer à la fois
accountIdetapprovalKindvia ces assistants.accountIdlimite la politique d’approbation multicomptes au bon compte de bot, tandis queapprovalKindpermet au canal de distinguer le comportement des approbations d’exécution de celui des approbations de Plugin, sans branches codées en dur dans le cœur. - Le cœur est également responsable des avis de réacheminement des approbations.
Les Plugins de canal ne doivent pas envoyer leurs propres messages de suivi du
type « l’approbation a été envoyée dans les messages privés / un autre canal »
depuis
createChannelNativeApprovalRuntime; exposez plutôt un routage précis vers l’origine et vers les messages privés de l’approbateur au moyen des assistants partagés de capacité d’approbation, puis laissez le cœur agréger les livraisons réelles avant de publier un éventuel avis dans la discussion d’origine. - Préservez de bout en bout le type d’identifiant de l’approbation livrée. Les clients natifs ne doivent pas deviner ni réécrire le routage des approbations d’exécution ou de Plugin à partir d’un état local au canal.
- Transmettez cet
approvalKindexplicite àresolveApprovalOverGateway. Cette fonction utilise le service canoniqueapproval.resolveet renvoie le gagnant enregistré lorsqu’une autre surface répond en premier. L’ancienne entrée expliciteresolveMethodreste disponible pour les contrôles reposant sur des commandes ; les nouvelles actions natives ne doivent pas l’utiliser ni déduire le type à partir d’un identifiant. - Différents types d’approbation peuvent intentionnellement exposer des surfaces natives différentes. Exemples intégrés actuels : Matrix conserve le même routage natif vers les messages privés/le canal et la même expérience de réactions pour les approbations d’exécution et de Plugin, tout en permettant à l’authentification de différer selon le type d’approbation ; Slack conserve le routage natif des approbations pour les identifiants d’exécution et de Plugin.
createApproverRestrictedNativeApprovalAdapterexiste toujours comme wrapper de compatibilité, mais le nouveau code doit privilégier le générateur de capacités et exposerapprovalCapabilitysur le Plugin.
Sous-chemins plus ciblés de l’environnement d’exécution des approbations
Pour les points d’entrée de canal critiques, privilégiez ces sous-chemins plus
ciblés plutôt que le barrel plus large approval-runtime lorsque vous n’avez
besoin que d’une partie de cette famille :
openclaw/plugin-sdk/approval-auth-runtimeopenclaw/plugin-sdk/approval-client-runtimeopenclaw/plugin-sdk/approval-delivery-runtimeopenclaw/plugin-sdk/approval-gateway-runtimeopenclaw/plugin-sdk/approval-reference-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
De même, privilégiez openclaw/plugin-sdk/reply-runtime,
openclaw/plugin-sdk/reply-dispatch-runtime,
openclaw/plugin-sdk/reply-reference et
openclaw/plugin-sdk/reply-chunking plutôt que des surfaces génériques plus
larges lorsque vous n’en avez pas besoin dans leur ensemble.
Sous-chemins de configuration
openclaw/plugin-sdk/setup-runtimecouvre les assistants de configuration sûrs pour l’environnement d’exécution :createSetupTranslator, les adaptateurs de correctifs de configuration sûrs à importer (createPatchedAccountSetupAdapter,createEnvPatchedAccountSetupAdapter,createSetupInputPresenceValidator), la sortie des notes de recherche,promptResolvedAllowFrom,splitSetupEntrieset les générateurs de proxy de configuration délégués.openclaw/plugin-sdk/channel-setupcouvre les générateurs de configuration avec installation facultative, ainsi que quelques primitives sûres pour la configuration :createOptionalChannelSetupSurface,createOptionalChannelSetupAdapter,createOptionalChannelSetupWizard,DEFAULT_ACCOUNT_ID,createTopLevelChannelDmPolicy,setSetupChannelEnabledetsplitSetupEntries.- Utilisez l’interface plus large
openclaw/plugin-sdk/setupuniquement si vous avez également besoin d’assistants partagés de configuration plus lourds, commemoveSingleAccountChannelSectionToDefaultAccount(...).
Si votre canal souhaite uniquement afficher « installez d’abord ce Plugin » dans
les surfaces de configuration, privilégiez
createOptionalChannelSetupSurface(...). L’adaptateur et l’assistant générés
échouent de manière fermée lors des écritures de configuration et de la
finalisation, et réutilisent le même message indiquant que l’installation est
requise pour la validation, la finalisation et le texte du lien vers la
documentation.
Si votre canal prend en charge une configuration ou une authentification pilotée
par des variables d’environnement et que les flux génériques de
démarrage/configuration doivent connaître les noms de ces variables avant le
chargement de l’environnement d’exécution, déclarez-les dans le manifeste du
Plugin avec channelEnvVars. Conservez les envVars de l’environnement
d’exécution du canal ou les constantes locales uniquement pour le texte destiné
aux opérateurs.
Si votre canal peut apparaître dans status, channels list, channels status
ou les analyses SecretRef avant le démarrage de l’environnement d’exécution du
Plugin, ajoutez openclaw.setupEntry dans package.json. Ce point d’entrée doit
pouvoir être importé sans risque dans les chemins de commande en lecture seule et
doit renvoyer les métadonnées du canal, l’adaptateur de configuration sûr,
l’adaptateur d’état et les métadonnées des cibles de secrets du canal nécessaires
à ces résumés. Ne démarrez aucun client, écouteur ni environnement d’exécution de
transport depuis le point d’entrée de configuration.
Maintenez également un chemin d’importation étroit pour le point d’entrée principal
du canal. La découverte peut évaluer le point d’entrée et le module du Plugin de
canal afin d’enregistrer les capacités sans activer le canal. Les fichiers tels
que channel-plugin-api.ts doivent exporter l’objet Plugin du canal sans importer
d’assistants de configuration, de clients de transport, d’écouteurs de socket, de
lanceurs de sous-processus ni de modules de démarrage de service. Placez ces
éléments d’exécution dans des modules chargés depuis registerFull(...), des
setters d’exécution ou des adaptateurs de capacités paresseux.
Autres sous-chemins ciblés des canaux
Pour les autres chemins de canal critiques, privilégiez les assistants ciblés plutôt que les surfaces héritées plus larges :
openclaw/plugin-sdk/account-core,openclaw/plugin-sdk/account-id,openclaw/plugin-sdk/account-resolutionetopenclaw/plugin-sdk/account-helperspour la configuration multicomptes et le repli vers le compte par défautopenclaw/plugin-sdk/inbound-envelopeetopenclaw/plugin-sdk/channel-inboundpour le routage/l’enveloppe entrants et le câblage d’enregistrement et de distributionopenclaw/plugin-sdk/channel-targetspour les assistants d’analyse des ciblesopenclaw/plugin-sdk/outbound-mediapour le chargement des médias etopenclaw/plugin-sdk/channel-outboundpour les délégués d’identité/d’envoi sortants et la planification des charges utilesbuildThreadAwareOutboundSessionRoute(...)depuisopenclaw/plugin-sdk/channel-corelorsqu’une route sortante doit préserver unreplyToId/threadIdexplicite ou récupérer la session:thread:actuelle après que la clé de session de base correspond toujours. Les Plugins de fournisseur peuvent remplacer la priorité, le comportement des suffixes et la normalisation de l’identifiant de fil de discussion lorsque leur plateforme possède une sémantique native de livraison dans les fils de discussion.openclaw/plugin-sdk/thread-bindings-runtimepour le cycle de vie des associations de fils de discussion et l’enregistrement des adaptateursopenclaw/plugin-sdk/agent-media-payloaduniquement lorsqu’une disposition héritée des champs de charge utile d’agent/média reste nécessaireopenclaw/plugin-sdk/telegram-command-config(obsolète : aucun Plugin intégré ne l’utilise en production) pour la normalisation des commandes personnalisées de Telegram, la validation des doublons/conflits et un contrat de configuration de commandes stable en cas de repli ; privilégiez une gestion de la configuration des commandes locale au Plugin pour le nouveau code de Plugin
Les canaux limités à l’authentification peuvent généralement s’en tenir au chemin par défaut : le cœur gère les approbations et le Plugin expose simplement les capacités de sortie/d’authentification. Les canaux d’approbation natifs tels que Matrix, Slack, Telegram et les transports de discussion personnalisés doivent utiliser les assistants natifs partagés au lieu d’implémenter leur propre cycle de vie des approbations.
Politique des mentions entrantes
Conservez la gestion des mentions entrantes en deux couches distinctes :
- collecte des éléments probants appartenant au Plugin
- évaluation de la politique partagée
Utilisez openclaw/plugin-sdk/channel-mention-gating pour les décisions de
politique relatives aux mentions. Utilisez
openclaw/plugin-sdk/channel-inbound uniquement lorsque vous avez besoin du
barrel plus large d’assistants entrants.
Éléments adaptés à une logique locale au Plugin :
- détection des réponses au bot
- détection des citations du bot
- vérification de la participation au fil de discussion
- exclusion des messages de service/système
- caches natifs à la plateforme nécessaires pour prouver la participation du bot
Éléments adaptés à l’assistant partagé :
requireMention- résultat de la mention explicite
- liste d’autorisation des mentions implicites
- contournement par commande
- décision finale d’ignorer
Flux recommandé :
- Calculez les informations locales relatives aux mentions.
- Transmettez ces informations à
resolveInboundMentionDecision({ facts, policy }). - Utilisez
decision.effectiveWasMentioned,decision.shouldBypassMentionetdecision.shouldSkipdans votre filtre entrant.
implicitMentionKindWhen, matchesMentionWithExplicit, resolveInboundMentionDecision,} from "openclaw/plugin-sdk/channel-inbound"; const wasMentioned = matchesMentionWithExplicit({ text, mentionRegexes, explicit: { hasAnyMention, isExplicitlyMentioned, canResolveExplicit, },}); const facts = { canDetectMention: true, wasMentioned, hasAnyMention, 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;matchesMentionWithExplicit(...) renvoie une valeur booléenne. hasAnyMention,
isExplicitlyMentioned et canResolveExplicit proviennent des métadonnées de
mention natives du canal (entités du message, indicateurs de réponse au bot et
éléments similaires) ; fournissez les valeurs false/undefined lorsque votre
plateforme ne peut pas les détecter.
api.runtime.channel.mentions expose les mêmes utilitaires partagés de gestion
des mentions pour les plugins de canal intégrés qui dépendent déjà de
l’injection à l’exécution : buildMentionRegexes, matchesMentionPatterns,
matchesMentionWithExplicit, implicitMentionKindWhen,
resolveInboundMentionDecision.
Si vous avez uniquement besoin de implicitMentionKindWhen et de
resolveInboundMentionDecision, importez-les depuis
openclaw/plugin-sdk/channel-mention-gating afin d’éviter de charger des
utilitaires d’exécution entrants sans rapport.
Procédure détaillée
Paquet et manifeste
Créez les fichiers de plugin standard. Le champ channels dans
openclaw.plugin.json (et non un champ kind) indique qu’un manifeste
possède un canal. Pour connaître l’ensemble des métadonnées du paquet,
consultez Configuration du 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": "Discussion Acme", "blurb": "Connectez OpenClaw à Discussion Acme." }}}{"id": "acme-chat","channels": ["acme-chat"],"name": "Discussion Acme","description": "Plugin de canal Discussion Acme","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": "Jeton du bot", "sensitive": true } } }}}configSchema valide plugins.entries.acme-chat.config. Utilisez-le pour
les paramètres appartenant au plugin qui ne font pas partie de la
configuration du compte de canal.
channelConfigs.acme-chat.schema valide channels.acme-chat et constitue
la source du chemin non critique utilisée par le schéma de configuration,
la configuration initiale et les interfaces utilisateur avant le
chargement de l’exécution du plugin. Consultez
Manifeste du Plugin pour la référence complète des
champs de premier niveau.
Créer l’objet du plugin de canal
L’interface ChannelPlugin comporte de nombreuses surfaces d’adaptateur
facultatives. Commencez par le minimum — id, config et setup — puis
ajoutez des adaptateurs selon vos besoins.
Créez 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"; // votre client d’API de plateforme 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 : le jeton est requis"); return { accountId: accountId ?? null, token, allowFrom: section?.allowFrom ?? [], dmPolicy: section?.dmSecurity, };} export const acmeChatPlugin = createChatChannelPlugin<ResolvedAccount>({ base: createChannelPluginBase({ id: "acme-chat", // La résolution et l’inspection du compte appartiennent à `config`, pas à `setup`. // `setup` couvre les écritures d’intégration (applyAccountConfig, validateInput). config: { listAccountIds: () => ["default"], 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", }; }, }, setup: { applyAccountConfig: ({ cfg, input }) => ({ ...cfg, channels: { ...cfg.channels, "acme-chat": { ...(cfg.channels as any)?.["acme-chat"], ...input }, }, }), }, }), // Sécurité des messages privés : qui peut envoyer un message au bot security: { dm: { channelKey: "acme-chat", resolvePolicy: (account) => account.dmPolicy, resolveAllowFrom: (account) => account.allowFrom, defaultPolicy: "allowlist", }, }, // Appairage : processus d’approbation des nouveaux contacts par message privé pairing: { text: { idLabel: "Nom d’utilisateur Discussion Acme", message: "Envoyez ce code pour vérifier votre identité :", notify: async ({ target, code }) => { await acmeChatApi.sendDm(target, `Code d’appairage : ${code}`); }, }, }, // Fils de discussion : mode de distribution des réponses threading: { topLevelReplyToMode: "reply" }, // Sortant : envoyer des messages à la plateforme outbound: { attachedResults: { channel: "acme-chat", 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); }, }, },});Pour les canaux qui acceptent à la fois les clés de messages privés
canoniques de premier niveau et les anciennes clés imbriquées, utilisez les
utilitaires de plugin-sdk/channel-config-helpers :
resolveChannelDmAccess, resolveChannelDmPolicy,
resolveChannelDmAllowFrom et normalizeChannelDmPolicy donnent la
priorité aux valeurs locales du compte sur les valeurs racines héritées.
Associez le même résolveur à la réparation effectuée par doctor via
normalizeLegacyDmAliases, afin que l’exécution et la migration utilisent
le même contrat.
Ce que createChatChannelPlugin fait pour vous
Au lieu d’implémenter manuellement les interfaces d’adaptateur de bas niveau, vous fournissez des options déclaratives et le générateur les compose :
| Option | Ce qu’elle connecte |
|---|---|
security.dm |
Résolveur de sécurité des messages privés, limité à la portée des champs de configuration |
pairing.text |
Processus d’appairage par message privé basé sur du texte, avec échange de code |
threading |
Résolveur du mode de réponse (fixe, limité à la portée du compte ou personnalisé) |
outbound.attachedResults |
Fonctions d’envoi qui renvoient les métadonnées du résultat (identifiants de message) ; nécessite un identifiant channel adjacent afin que le cœur puisse marquer le résultat de distribution renvoyé |
Vous pouvez également fournir des objets d’adaptateur bruts à la place des options déclaratives si vous avez besoin d’un contrôle total.
Les adaptateurs sortants bruts peuvent définir une fonction
chunker(text, limit, ctx). La propriété facultative ctx.formatting
contient les décisions de mise en forme prises au moment de la
distribution, telles que maxLinesPerMessage ; appliquez-les avant
l’envoi afin que le fil de réponse et les limites de segments soient
déterminés une seule fois par le mécanisme partagé de distribution
sortante. Les contextes d’envoi incluent également replyToIdSource
(implicit ou explicit) lorsqu’une cible de réponse native a été
résolue, afin que les utilitaires de charge utile puissent préserver les
balises de réponse explicites sans consommer un emplacement de réponse
implicite à usage unique.
Connecter le point d’entrée
Créez index.ts :
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";import { acmeChatPlugin } from "./src/channel.js"; export default defineChannelPluginEntry({ id: "acme-chat", name: "Discussion Acme", description: "Plugin de canal Discussion Acme", plugin: acmeChatPlugin, registerCliMetadata(api) { api.registerCli( ({ program }) => { program .command("acme-chat") .description("Gestion de Discussion Acme"); }, { descriptors: [ { name: "acme-chat", description: "Gestion de Discussion Acme", hasSubcommands: false, }, ], }, ); }, registerFull(api) { api.registerGatewayMethod(/* ... */); },});Placez les descripteurs de CLI appartenant au canal dans
registerCliMetadata(...) afin qu’OpenClaw puisse les afficher dans l’aide
racine sans activer l’exécution complète du canal, tandis que les
chargements complets normaux récupèrent toujours ces mêmes descripteurs pour
l’enregistrement réel des commandes. Réservez registerFull(...) aux
opérations d’exécution uniquement. defineChannelPluginEntry gère
automatiquement la séparation entre les modes d’enregistrement.
Si registerFull(...) enregistre des méthodes RPC du Gateway, utilisez un
préfixe propre au plugin. Les espaces de noms d’administration du cœur
(config.*, exec.approvals.*, wizard.*, update.*) restent réservés et
correspondent toujours à operator.admin. Consultez
Points d’entrée pour
connaître toutes les options.
Ajouter un point d’entrée de configuration
Créez setup-entry.ts pour permettre un chargement léger pendant
l’intégration :
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";import { acmeChatPlugin } from "./src/channel.js"; export default defineSetupPluginEntry(acmeChatPlugin);OpenClaw charge ce point d’entrée à la place du point d’entrée complet lorsque le canal est désactivé ou non configuré. Cela évite de charger du code d’exécution lourd pendant les processus de configuration. Consultez Configuration pour plus de détails.
Les canaux intégrés à l’espace de travail qui répartissent les exportations
utilisables sans risque pendant la configuration dans des modules annexes
peuvent utiliser defineBundledChannelSetupEntry(...) depuis
openclaw/plugin-sdk/channel-entry-contract lorsqu’ils ont également besoin
d’un mécanisme explicite de définition de l’exécution au moment de la
configuration.
Gérer les messages entrants
Votre plugin doit recevoir les messages de la plateforme et les transférer à OpenClaw. Le modèle habituel consiste à utiliser un Webhook qui vérifie la requête et la distribue via le gestionnaire entrant de votre canal :
registerFull(api) { api.registerHttpRoute({ path: "/acme-chat/webhook", auth: "plugin", // authentification gérée par le plugin (vérifiez vous-même les signatures) handler: async (req, res) => { const event = parseWebhookPayload(req); // Votre gestionnaire entrant transmet le message à OpenClaw. // Le raccordement exact dépend du SDK de votre plateforme — // consultez un exemple réel dans le package du plugin Microsoft Teams ou Google Chat inclus. await handleAcmeChatInbound(api, event); res.statusCode = 200; res.end("ok"); return true; }, });}Tester
Écrivez des tests colocalisés dans src/channel.test.ts :
import { describe, it, expect } from "vitest";import { acmeChatPlugin } from "./channel.js"; describe("plugin acme-chat", () => { it("résout le compte depuis la configuration", () => { const cfg = { channels: { "acme-chat": { token: "test-token", allowFrom: ["user1"] }, }, } as any; const account = acmeChatPlugin.config.resolveAccount(cfg, undefined); expect(account.token).toBe("test-token"); }); it("inspecte le compte sans matérialiser les secrets", () => { const cfg = { channels: { "acme-chat": { token: "test-token" } }, } as any; const result = acmeChatPlugin.config.inspectAccount!(cfg, undefined); expect(result.configured).toBe(true); expect(result.tokenStatus).toBe("available"); }); it("signale une configuration manquante", () => { const cfg = { channels: {} } as any; const result = acmeChatPlugin.config.inspectAccount!(cfg, undefined); expect(result.configured).toBe(false); });});pnpm test <bundled-plugin-root>/acme-chat/Pour les utilitaires de test partagés, consultez Tests.
Structure des fichiers
<bundled-plugin-root>/acme-chat/├── package.json # métadonnées openclaw.channel├── openclaw.plugin.json # manifeste avec le schéma de configuration├── index.ts # defineChannelPluginEntry├── setup-entry.ts # defineSetupPluginEntry├── api.ts # exportations publiques (facultatif)├── runtime-api.ts # exportations internes de l'environnement d'exécution (facultatif)└── src/ ├── channel.ts # ChannelPlugin via createChatChannelPlugin ├── channel.test.ts # tests ├── client.ts # client de l'API de la plateforme └── runtime.ts # stockage de l'environnement d'exécution (si nécessaire)Sujets avancés
Modes de réponse fixes, propres au compte ou personnalisés
describeMessageTool et découverte des actions
inferTargetChatType, looksLikeId, reservedLiterals, resolveTarget
TTS, STT, médias et sous-agent via api.runtime
Cycle de vie partagé des événements entrants : réception, résolution, enregistrement, distribution et finalisation
Étapes suivantes
- Plugins de fournisseur - si votre plugin fournit également des modèles
- Présentation du SDK - référence complète des importations par sous-chemin
- Tests du SDK - utilitaires de test et tests de contrat
- Manifeste de plugin - schéma complet du manifeste