Création de plugins de channel
Ce guide explique la création d’un plugin de channel qui connecte OpenClaw à une plateforme de messagerie. À la fin, vous disposerez d’un channel fonctionnel avec une sécurité DM, le jumelage, le threading des réponses et la messagerie sortante.
Fonctionnement des plugins de channel
Section intitulée « Fonctionnement des plugins de channel »Les plugins de canal n’ont pas besoin de leurs propres outils d’envoi/de modification/de réaction. OpenClaw conserve un outil message partagé dans le cœur. Votre plugin possède :
- Config - résolution de compte et assistant de configuration
- Sécurité - stratégie de DM et listes d’autorisation
- Appariement - flux d’approbation DM
- Grammaire de session - comment les identifiants de conversation spécifiques au fournisseur sont mappés aux chats de base, aux identifiants de fil de discussion et aux replis parent
- Sortant - envoi de texte, de médias et de sondages vers la plateforme
- Fils de discussion - comment les réponses sont organisées en fils
- Frappe de l’état de santé - signaux facultatifs de frappe/occupation pour les cibles de livraison de l’état de santé
Le cœur possède l’outil de message partagé, le câblage des invites, la forme de la clé de session externe,
la tenue de livres générique :thread: et la répartition.
Les nouveaux plugins de canal doivent également exposer un adaptateur message avec
defineChannelMessageAdapter de openclaw/plugin-sdk/channel-outbound. L’adaptateur déclare quelles capacités d’envoi final durables le transport natif
prend réellement en charge et dirige les envois de texte/médias vers les mêmes fonctions de transport que
l’adaptateur outbound hérité. Ne déclarez une capacité que lorsqu’un test de contrat
prouve l’effet secondaire natif et le reçu renvoyé.
Pour le contrat API complet, les exemples, la matrice des capacités, les règles de reçu, la finalisation
de l’aperçu en direct, la stratégie d’accusé de réception de réception, les tests et la table de migration, consultez
API sortant du canal.
Si l’adaptateur outbound existant possède déjà les bonnes méthodes d’envoi et
les métadonnées de capacité, utilisez createChannelMessageAdapterFromOutbound(...) pour
dériver l’adaptateur message au lieu d’écrire manuellement un autre pont.
Les envois de l’adaptateur doivent renvoyer des valeurs MessageReceipt. Lorsque le code de compatibilité
a encore besoin d’identifiants hérités, dérivez-les avec listMessageReceiptPlatformIds(...)
ou resolveMessageReceiptPrimaryId(...) au lieu de conserver des champs messageIds parallèles
dans le nouveau code de cycle de vie.
Les canaux prenant en charge les aperçus doivent également déclarer message.live.capabilities avec
le cycle de vie en direct exact qu’ils possèdent, tel que draftPreview,
previewFinalization, progressUpdates, nativeStreaming, ou
quietFinalization. Les canaux qui finalisent un aperçu de brouillon en place doivent
également déclarer message.live.finalizer.capabilities, tel que finalEdit,
normalFallback, discardPending, previewReceipt, et
retainOnAmbiguousFailure, et acheminer la logique d’exécution via
defineFinalizableLivePreviewAdapter(...) plus
deliverWithFinalizableLivePreviewAdapter(...). Maintenez ces capacités prises en charge
par des tests verifyChannelMessageLiveCapabilityAdapterProofs(...) et
verifyChannelMessageLiveFinalizerProofs(...) afin que le comportement de l’aperçu natif,
de la progression, de l’édition, de la repli/conservation, du nettoyage et du reçu ne puisse pas dériver
en silence.
Les récepteurs entrants qui diffèrent les accusés de réception de la plateforme doivent déclarer
message.receive.defaultAckPolicy et supportedAckPolicies au lieu de masquer
le timing des accusés de réception dans l’état local du moniteur. Couvrez chaque stratégie déclarée avec
verifyChannelMessageReceiveAckPolicyAdapterProofs(...).
Les assistants de réponse hérités tels que createChannelTurnReplyPipeline,
dispatchInboundReplyWithBase et recordInboundSessionAndDispatchReply
restent disponibles pour les répartiteurs de compatibilité. N’utilisez pas ces noms pour le nouveau
code de canal ; les nouveaux plugins devraient commencer par l’adaptateur message, les reçus et
les assistants de cycle de vie de réception/envoi sur openclaw/plugin-sdk/channel-outbound.
Les channels migrant l’autorisation entrante peuvent utiliser le sous-chemin expérimental openclaw/plugin-sdk/channel-ingress-runtime depuis les chemins de réception d’exécution. Le sous-chemin conserve la recherche de plateforme et les effets secondaires dans le plugin, tout en partageant la résolution de l’état de la liste d’autorisation, les décisions de route/expéditeur/commande/événement/activation, les diagnostics expurgés et le mappage d’admission de tour. Conservez la normalisation de l’identité du plugin dans le descripteur que vous passez au résolveur ; ne sérialisez pas les valeurs de correspondance brutes de l’état résolu ou de la décision. Voir Channel ingress API pour la conception de l’API, la limite de responsabilité et les attentes de test.
Si votre channel prend en charge les indicateurs de frappe en dehors des réponses entrantes, exposez
heartbeat.sendTyping(...) sur le plugin de channel. Core l’appelle avec la
cible de livraison de pulsation (heartbeat) résolue avant le début de l’exécution du modèle de pulsation
et utilise le cycle de vie partagé de maintien/nettoyage de frappe. Ajoutez heartbeat.clearTyping(...)
lorsque la plateforme a besoin d’un signal d’arrêt explicite.
Si votre channel ajoute des paramètres d’outil de message (message-tool) qui transportent des sources média, exposez ces
noms de paramètres via describeMessageTool(...).mediaSourceParams. Core utilise
cette liste explicite pour la normalisation du chemin du bac à sable et la stratégie d’accès média sortant,
de sorte que les plugins n’ont pas besoin de cas particuliers dans le cœur partagé (shared-core) pour les paramètres
d’avatar, de pièce jointe ou d’image de couche spécifiques au fournisseur.
Préférez renvoyer une carte indexée par action telle que
{ "set-profile": ["avatarUrl", "avatarPath"] } afin que les actions non liées n’héritent
pas des arguments média d’une autre action. Un tableau plat fonctionne toujours pour les paramètres qui
sont intentionnellement partagés entre chaque action exposée.
Si votre channel nécessite une mise en forme spécifique au fournisseur pour message(action="send"),
préférez actions.prepareSendPayload(...). Placez les cartes natives, les blocs, les intégrations ou
autres données durables sous payload.channelData.<channel> et laissez le cœur effectuer
l’envoi réel via l’adaptateur outbound/message. Utilisez
actions.handleAction(...) pour l’envoi uniquement en tant que solution de repli de compatibilité pour
les charges utiles qui ne peuvent pas être sérialisées et réessayées.
Si votre plateforme stocke une portée supplémentaire dans les identifiants de conversation, gardez cet analyseur
dans le plugin avec messaging.resolveSessionConversation(...). C’est le
crochet canonique pour mapper rawId à l’identifiant de conversation de base, au fil de discussion optionnel,
au baseConversationId explicite, et à tout parentConversationCandidates.
Lorsque vous renvoyez parentConversationCandidates, gardez-les ordonnés du
parent le plus étroit vers la conversation la plus large/de base.
Utilisez openclaw/plugin-sdk/channel-route lorsque le code du plugin doit normaliser des champs de type route, comparer un fil de discussion enfant avec 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 fil de discussion de la même manière que le cœur, les plugins devraient donc l’utiliser de préférence aux comparaisons String(threadId) ad hoc.
Les plugins avec une syntaxe cible spécifique au fournisseur doivent exposer messaging.resolveOutboundSessionRoute(...) afin que le cœur obtienne l’identité de session et de fil native du fournisseur sans utiliser de shims d’analyse.
Les plugins groupés qui ont besoin du même analyseur avant le démarrage du registre de canaux
peuvent également exposer un fichier session-key-api.ts de niveau supérieur avec un export resolveSessionConversation(...) correspondant.
Le cœur utilise cette surface sûre pour le démarrage
uniquement lorsque le registre de plugins d’exécution n’est pas encore disponible.
messaging.resolveParentConversationCandidates(...) reste disponible en tant que
solution de repli de compatibilité héritée lorsqu’un plugin a uniquement besoin de replis parents au-dessus
de l’identifiant générique/brut. Si les deux crochets existent, le cœur utilise
resolveSessionConversation(...).parentConversationCandidates en premier et ne
revient à resolveParentConversationCandidates(...) que lorsque le crochet canonique
les omet.
Approbations et capacités du channel
Section intitulée « Approbations et capacités du channel »La plupart des plugins de channel n’ont pas besoin de code spécifique aux approbations.
- Le cœur gère les
/approvedans la même conversation, les charges utiles partagées des boutons d’approbation, et la livraison de repli générique. - Préférez un seul objet
approvalCapabilitysur le plugin de canal lorsque le canal a besoin d’un comportement spécifique aux approbations. ChannelPlugin.approvalsest supprimé. Placez les faits de livraison/natif/rendu/authentification des approbations surapprovalCapability.plugin.authest réservé à la connexion/déconnexion ; le noyau ne lit plus les hooks d’authentification d’approbation depuis cet objet.approvalCapability.authorizeActorActionetapprovalCapability.getActionAvailabilityStateconstituent la jonction canonique pour l’authentification des approbations.- Utilisez
approvalCapability.getActionAvailabilityStatepour la disponibilité de l’authentification des approbations dans le même chat. - Si votre canal expose des approbations d’exécution natives, utilisez
approvalCapability.getExecInitiatingSurfaceStatepour l’état de la surface d’initiation/client natif lorsqu’il diffère de l’authentification des approbations dans le même chat. Le noyau utilise ce hook spécifique à l’exécution pour distinguerenableddedisabled, pour décider si le canal d’initiation prend en charge les approbations d’exécution natives, et pour inclure le canal dans les directives de repli du client natif.createApproverRestrictedNativeApprovalCapability(...)remplit cela pour le cas courant. - Utilisez
outbound.shouldSuppressLocalPayloadPromptououtbound.beforeDeliverPayloadpour le comportement du cycle de vie de la charge utile spécifique au canal, tel que masquer les invites d’approbation locales en double ou envoyer des indicateurs de frappe avant la livraison. - Utilisez
approvalCapability.deliveryuniquement pour le routage des approbations natives ou la suppression du repli. - Utilisez
approvalCapability.nativeRuntimepour les faits d’approbation natifs appartenant au canal. Gardez-le paresseux sur les points d’entrée à chaud du canal aveccreateLazyChannelApprovalNativeRuntimeAdapter(...), qui peut importer votre module d’exécution à la demande tout en permettant au noyau d’assembler le cycle de vie de l’approbation. - Utilisez
approvalCapability.renderuniquement lorsqu’un canal a vraiment besoin de charges utiles d’approbation personnalisées au lieu du moteur de rendu partagé. - Utilisez
approvalCapability.describeExecApprovalSetuplorsque le canal souhaite que la réponse du chemin désactivé explique les boutons de configuration exacts nécessaires pour activer les approbations d’exécution natives. Le hook reçoit{ channel, channelLabel, accountId }; les canaux avec compte nommé doivent afficher des chemins étendus au compte tels quechannels.<channel>.accounts.<id>.execApprovals.*au lieu des valeurs par défaut de niveau supérieur. - Si un channel peut déduire des identités DM stables de type propriétaire à partir de la configuration existante, utilisez
createResolvedApproverActionAuthAdapterdepuisopenclaw/plugin-sdk/approval-runtimepour restreindre/approvede même discussion sans ajouter de logique principale spécifique aux approbations. - Si l’autorisation d’approbation personnalisée autorise intentionnellement uniquement le repli sur la même conversation, renvoyez
markImplicitSameChatApprovalAuthorization({ authorized: true })à partir deopenclaw/plugin-sdk/approval-auth-runtime; sinon, le cœur traite le résultat comme une autorisation explicite de l’approuvant. - Si un rappel natif dépossédé par le canal résout les approbations directement, utilisez
isImplicitSameChatApprovalAuthorization(...)avant la résolution afin que le repli implicite passe toujours par l’autorisation d’acteur normale du canal. - Si un canal a besoin d’une livraison d’approbation native, concentrez le code du canal sur la normalisation de la cible ainsi que sur les faits de transport/présentation. Utilisez
createChannelExecApprovalProfile,createChannelNativeOriginTargetResolver,createChannelApproverDmTargetResolveretcreateApproverRestrictedNativeApprovalCapabilityà partir deopenclaw/plugin-sdk/approval-runtime. Placez les faits spécifiques au canal derrièreapprovalCapability.nativeRuntime, idéalement viacreateChannelApprovalNativeRuntimeAdapter(...)oucreateLazyChannelApprovalNativeRuntimeAdapter(...), afin que le cœur puisse assembler le gestionnaire et gérer le filtrage des requêtes, le routage, la déduplication, l’expiration, l’abonnement à la passerelle et les avis de routage-ailleurs.nativeRuntimeest divisé en quelques coutures plus petites : - Utilisez
createNativeApprovalChannelRouteGatesdeopenclaw/plugin-sdk/approval-native-runtimelorsqu’un channel prend en charge à la fois la livraison native d’origine de session et les cibles de transfert d’approbation explicites. L’assistant centralise la sélection de la configuration d’approbation, la gestion demode, les filtres d’agent/session, la liaison de compte, la correspondance session-cible et la correspondance de liste de cibles, tandis que les appelants possèdent toujours l’identifiant du channel, le mode de transfert par défaut, la recherche de compte, la vérification de l’activation du transport, la normalisation de la cible et la résolution de la cible source de tour. Ne l’utilisez pas pour créer des valeurs par défaut de stratégie de channel appartenant au cœur ; transmettez explicitement le mode par défaut documenté du channel. createChannelNativeOriginTargetResolverutilise par défaut le matcher de route de channel partagé pour les cibles{ to, accountId, threadId }. Ne passeztargetsMatchque lorsqu’un channel a des règles d’équivalence spécifiques au fournisseur, comme la correspondance de préfixe d’horodatage Slack.- Passez
normalizeTargetForMatchàcreateChannelNativeOriginTargetResolverlorsque le channel doit canoniser les identifiants de fournisseur avant l’exécution du matcher de route par défaut ou d’un rappeltargetsMatchpersonnalisé, tout en préservant la cible d’origine pour la livraison. UtiliseznormalizeTargetuniquement lorsque la cible de livraison résolue elle-même doit être canonisée. availability- si le compte est configuré et si une requête doit être géréepresentation- mapper le modèle de vue d’approbation partagé en charges utiles natives en attente/résolues/expirées ou en actions finalestransport- préparer les cibles ainsi qu’envoyer/mettre à jour/supprimer les messages d’approbation natifsinteractions- hooks bind/unbind/clear-action facultatifs pour les boutons ou réactions natifs, ainsi qu’un hookcancelDeliveredfacultatif. ImplémentezcancelDeliveredlorsquedeliverPendingenregistre un état en cours de traitement ou persistant (tel qu’un magasin 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 debindPendingou lorsquebindPendingne renvoie aucun descripteurobserve- hooks de diagnostics de livraison facultatifs- Si le channel a besoin d’objets détenus par le runtime tels qu’un client, un jeton, une application Bolt ou un récepteur de webhook, enregistrez-les via Bolt
openclaw/plugin-sdk/channel-runtime-context. Le registre générique de contexte d’exécution permet au cœur d’amorcer les gestionnaires basés sur les capacités à partir de l’état de démarrage du channel sans ajouter de colle d’enveloppe spécifique aux approbations. - Optez pour le niveau inférieur
createChannelApprovalHandleroucreateChannelNativeApprovalRuntimeuniquement lorsque la couture basée sur les capacités n’est pas encore assez expressive. - Les channels d’approbation natifs doivent acheminer à la fois
accountIdetapprovalKindvia ces assistants.accountIdmaintient la stratégie d’approbation multi-compte délimitée au bon compte bot, etapprovalKindmaintient le comportement d’approbation exec vs plugin disponible pour le channel sans branches codées en dur dans le cœur. - Le cœur possède désormais également les avis de réacheminement d’approbation. Les plugins de channel ne doivent pas envoyer leurs propres messages de suivi “l’approbation est allée vers les DMs / un autre channel” depuis
createChannelNativeApprovalRuntime; à la place, exposez un routage exact de l’origine + DM de l’approbateur via les assistants de capacité d’approbation partagés et laissez le cœur agréger les livraisons réelles avant de poster tout avis vers le chat initiateur. - Conservez le type d’ID d’approbation livré de bout en bout. Les clients natifs ne doivent pas deviner ou réécrire le routage d’approbation exec vs plugin à partir de l’état local au channel.
- Différents types d’approbation peuvent intentionnellement exposer différentes surfaces natives.
Exemples regroupés actuels :
- Slack conserve le routage d’approbation natif disponible pour les identifiants exec et plugin.
- Matrix conserve le même routage natif DM/channel et la même UX de réaction pour les approbations exec et plugin, tout en permettant à l’auth de différer selon le type d’approbation.
createApproverRestrictedNativeApprovalAdapterexiste toujours en tant que wrapper de compatibilité, mais le nouveau code devrait préférer le constructeur de capacités et exposerapprovalCapabilitysur le plugin.
Pour les points d’entrée de channel à chaud, préférez les sous-chemins d’exécution plus étroits lorsque vous n’avez besoin que d’une seule 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-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, préférez 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, et
openclaw/plugin-sdk/reply-chunking lorsque vous n’avez pas besoin de la surface globale plus large.
Spécifiquement pour la configuration :
openclaw/plugin-sdk/setup-runtimecouvre les assistants de configuration sécurisés au runtime :createSetupTranslator, les adaptateurs de correctifs de configuration sécurisés à l’importation (createPatchedAccountSetupAdapter,createEnvPatchedAccountSetupAdapter,createSetupInputPresenceValidator), la sortie de note de recherche,promptResolvedAllowFrom,splitSetupEntries, et les constructeurs de proxy de configuration déléguésopenclaw/plugin-sdk/setup-runtimeinclut la jonction d’adaptateur consciente de l’env pourcreateEnvPatchedAccountSetupAdapteropenclaw/plugin-sdk/channel-setupcouvre les constructeurs de configuration d’installation facultative ainsi que quelques primitives de configuration sécurisées :createOptionalChannelSetupSurface,createOptionalChannelSetupAdapter,
Si votre channel prend en charge la configuration ou l’authentification basées sur l’env et que les flux de démarrage/config génériques doivent connaître ces noms d’env avant le chargement du runtime, déclarez-les dans le manifeste du plugin avec channelEnvVars. Gardez le runtime du channel envVars ou des constantes locales uniquement pour le texte destiné aux opérateurs.
Si votre channel peut apparaître dans les balayages status, channels list, channels status ou SecretRef avant le démarrage du runtime du plugin, ajoutez openclaw.setupEntry dans package.json. Ce point d’entrée doit être sûr à importer dans les chemins de commande en lecture seule et doit renvoyer les métadonnées du channel, l’adaptateur de configuration sûr pour l’installation, l’adaptateur de statut et les métadonnées de la cible secrète du channel nécessaires pour ces résumés. Ne démarrez pas de clients, d’écouteurs ou de runtimes de transport à partir du point d’entrée de configuration.
Gardez également le chemin d’importation de l’entrée principale du channel étroit. Discovery peut évaluer l’entrée et le module du plugin de channel pour enregistrer les capacités sans activer le channel. Les fichiers tels que channel-plugin-api.ts doivent exporter l’objet du plugin de channel sans importer les assistants d’installation, les clients de transport, les écouteurs de socket, les lanceurs de sous-processus ou les modules de démarrage de service. Mettez ces éléments d’exécution dans des modules chargés depuis registerFull(...), les setters d’exécution ou les adaptateurs de capacité différés.
createOptionalChannelSetupWizard, DEFAULT_ACCOUNT_ID,
createTopLevelChannelDmPolicy, setSetupChannelEnabled et
splitSetupEntries
- utilisez la couture
openclaw/plugin-sdk/setupplus large uniquement lorsque vous avez également besoin des assistants de configuration/installation partagés plus lourds tels quemoveSingleAccountChannelSectionToDefaultAccount(...)
Si votre channel souhaite uniquement annoncer “installer ce plugin d’abord” dans les surfaces de configuration, préférez createOptionalChannelSetupSurface(...). L’adaptateur/l’assistant généré échoue en mode fermé lors de l’écriture et de la finalisation de la configuration, et ils réutilisent le même message d’installation requise pour la validation, la finalisation et la copie du lien vers la documentation.
Pour d’autres chemins critiques du channel, préférez les assistants étroits aux 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 multi-compte et le repli vers le compte par défautopenclaw/plugin-sdk/inbound-envelopeetopenclaw/plugin-sdk/channel-inboundpour le câblage de la route/enveloppe entrante et de l’enregistrement et de la répartitionopenclaw/plugin-sdk/channel-targetspour les assistants d’analyse de la cibleopenclaw/plugin-sdk/outbound-mediapour le chargement des médias etopenclaw/plugin-sdk/channel-outboundpour les délégués d’identité et d’envoi sortants ainsi que pour la planification de la charge utilebuildThreadAwareOutboundSessionRoute(...)deopenclaw/plugin-sdk/channel-corelorsqu’une route sortante doit préserver unreplyToId/threadIdexplicite ou récupérer la session:thread:actuelle après concordance de la clé de session de base. Les plugins de fournisseur peuvent remplacer la priorité, le comportement de suffixe et la normalisation de l’identifiant de fil lorsque leur plateforme possède une sémantique de livraison de fil native.openclaw/plugin-sdk/thread-bindings-runtimepour le cycle de vie de liaison de fil et l’enregistrement de l’adaptateuropenclaw/plugin-sdk/agent-media-payloaduniquement lorsqu’une disposition de champ de charge utile agent/média héritée est encore requiseopenclaw/plugin-sdk/telegram-command-configpour la normalisation des commandes personnalisées Telegram, la validation des doublons/conflits et un contrat de configuration de commande stable de repli
Les canaux d’authentification uniquement peuvent généralement s’arrêter au chemin par défaut : le cœur gère les approbations et le plugin expose simplement les capacités sortantes/d’authentification. Les canaux d’approbation natifs tels que Matrix, Slack, Telegram et les transports de discussion personnalisés devraient utiliser les assistants natifs partagés au lieu de créer leur propre cycle de vie d’approbation.
Politique de mention entrante
Section intitulée « Politique de mention entrante »Conservez la gestion des mentions entrantes divisée en deux couches :
- collecte de preuves appartenant au plugin
- évaluation de la politique partagée
Utilisez openclaw/plugin-sdk/channel-mention-gating pour les décisions de politique de mention.
Utilisez openclaw/plugin-sdk/channel-inbound uniquement lorsque vous avez besoin de l’assistant entrant plus large.
Convient bien pour la logique locale au plugin :
- détection de réponse au bot
- détection de bot cité
- vérifications de participation au fil
- exclusions de messages de service/système
- caches natifs de la plateforme nécessaires pour prouver la participation du bot
Convient bien pour l’assistant partagé :
requireMention- résultat de mention explicite
- liste d’autorisation de mention implicite
- contournement de commande
- décision finale de saut
Flux préféré :
- Calculez les faits de mention locaux.
- Passez ces faits à
resolveInboundMentionDecision({ facts, policy }). - Utilisez
decision.effectiveWasMentioned,decision.shouldBypassMentionetdecision.shouldSkipdans votre porte entrante.
import { 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 expose les mêmes assistants de mention partagés pour
les plugins de channel groupés qui dépendent déjà de l’injection de runtime :
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
Si vous avez besoin uniquement de implicitMentionKindWhen et
de resolveInboundMentionDecision, importez depuis
openclaw/plugin-sdk/channel-mention-gating pour éviter le chargement d’assistants de runtime
inbound non liés.
Utilisez resolveInboundMentionDecision({ facts, policy }) pour le filtrage des mentions.
Procédure pas à pas
Section intitulée « Procédure pas à pas »Package et manifeste
Créez les fichiers de plugin standard. Le champ
channeldanspackage.jsonest ce qui fait de ce plugin un plugin de channel. Pour la surface complète des métadonnées de package, consultez Configuration et configuration des plugins :{"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}}}}}configSchemavalideplugins.entries.acme-chat.config. Utilisez-le pour les paramètres appartenant au plugin qui ne sont pas la configuration du compte du channel.channelConfigsvalidechannels.acme-chatet est la source de chemin froid utilisée par le schéma de configuration, la configuration et les surfaces de l’interface utilisateur avant le chargement du runtime du plugin.Créer l'objet du plugin de channel
L’interface
ChannelPluginpossède de nombreuses surfaces d’adaptateur optionnelles. Commencez par le minimum -idetsetup- et 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"; // your platform API clienttype 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)?.[“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({ base: createChannelPluginBase({ id: “acme-chat”, setup: { resolveAccount, inspectAccount(cfg, accountId) { const section = (cfg.channels as Record
)?.[“acme-chat”]; return { enabled: Boolean(section?.token), configured: Boolean(section?.token), tokenStatus: section?.token ? “available” : “missing”, }; }, }, }),
// DM security: who can message the botsecurity: {dm: {channelKey: "acme-chat",resolvePolicy: (account) => account.dmPolicy,resolveAllowFrom: (account) => account.allowFrom,defaultPolicy: "allowlist",},},// Pairing: approval flow for new DM contactspairing: {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 deliveredthreading: { topLevelReplyToMode: "reply" },// Outbound: send messages to the platformoutbound: {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);},},},});```Pour les channels qui acceptent à la fois les clés DM de premier niveau canoniques et les clés imbriquées héritées, utilisez les assistants de `plugin-sdk/channel-config-helpers` : `resolveChannelDmAccess`, `resolveChannelDmPolicy`, `resolveChannelDmAllowFrom`, et `normalizeChannelDmPolicy` placent les valeurs locales au compte avant les valeurs racines héritées. Associez le même résolveur à une réparation doctor via `normalizeLegacyDmAliases` afin que l'exécution et la migration lisent le même contrat.Ce que fait createChatChannelPlugin pour vous
Au lieu d’implémenter manuellement les interfaces d’adaptateur de bas niveau, vous passez des options déclaratives et le générateur les compose :
Option Ce qu’il connecte security.dmRésolveur de sécurité DM délimité à partir des champs de configuration pairing.textFlux d’appariement DM basé sur du texte avec échange de codes threadingRésolveur de mode de réponse (fixe, délimité au compte, ou personnalisé) outbound.attachedResultsFonctions d’envoi qui renvoient des métadonnées de résultat (ID de message) Vous pouvez également passer des objets d’adaptateur bruts au lieu 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). L’optionnelctx.formattingporte les décisions de formatage au moment de la livraison telles quemaxLinesPerMessage; appliquez-le avant l’envoi afin que le threading de réponse et les limites des blocs soient résolus une seule fois par la livraison sortante partagée. Les contextes d’envoi incluent égalementreplyToIdSource(implicitouexplicit) lorsqu’une cible de réponse native a été résolue, afin que les assistants de payload puissent préserver les balises de réponse explicites sans consommer un créneau 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: "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(/* ... */);},});```CLIPlacez les descripteurs CLI appartenant au channel dans `registerCliMetadata(...)`OpenClaw afin qu'OpenClawpuisse les afficher dans l'aide racine sans activer le runtime complet du channel,tandis que les chargements complets normaux récupèrent toujours les mêmes descripteurs pour l'enregistrementréel des commandes. Conservez `registerFull(...)` pour le travail exclusif au runtime.Si `registerFull(...)`RPC enregistre des méthodes RPC de passerelle, utilisez unpréfixe spécifique au plugin. Les espaces de noms d'administration principaux (`config.*`,`exec.approvals.*`, `wizard.*`, `update.*`) restent réservés et résolventtoujours vers `operator.admin`.`defineChannelPluginEntry` gère automatiquement la répartition du mode d'enregistrement. Consultez[Points d'entrée](/en/plugins/sdk-entrypoints#definechannelpluginentry) pour toutesles options.Ajouter une entrée de configuration
Créez
setup-entry.tspour un chargement léger lors de l’onboarding :import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";import { acmeChatPlugin } from "./src/channel.js";export default defineSetupPluginEntry(acmeChatPlugin);```OpenClawOpenClaw charge ceci au lieu de l'entrée complète lorsque le channel est désactivéou non configuré. Cela évite d'intégrer du code de runtime lourd lors des flux de configuration.Consultez [Configuration et paramétrage](/en/plugins/sdk-setup#setup-entry) pour plus de détails.Les channels d'espace de travail regroupés qui divisent les exportations sécurisées pour la configuration enmodules sidecar peuvent utiliser `defineBundledChannelSetupEntry(...)` depuis`openclaw/plugin-sdk/channel-entry-contract` lorsqu'ils ont également besoind'un définitseur de runtime explicite pour la configuration.Handle inbound messages
Votre plugin doit recevoir des messages de la plateforme et les transmettre à OpenClaw. Le modèle typique est un webhook qui vérifie la requête et la distribue via le gestionnaire inbound de votre channel :
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;},});}```Microsoft TeamsGoogle Chat
Test
Écrivez des tests colocalisés dans
src/channel.test.ts
:
```typescript src/channel.test.tsimport { 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); });});```
```bashpnpm test -- <bundled-plugin-root>/acme-chat/```
Pour les helpers de test partagés, voir [Testing](/fr/plugins/sdk-testing).Structure des fichiers
Section intitulée « Structure des fichiers »<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)Sujets avancés
Section intitulée « Sujets avancés »Modes de réponse fixes, au niveau du compte ou personnalisés
describeMessageTool et découverte d’actions
inferTargetChatType, looksLikeId, resolveTarget
TTS, STT, média, subagent via api.runtime
Cycle de vie partagé des événements entrants : ingest, resolve, record, dispatch, finalize
Étapes suivantes
Section intitulée « Étapes suivantes »- Plugins de fournisseur - si votre plugin fournit également des modèles
- Présentation du SDK - référence complète des imports de sous-chemins
- Tests du SDK - utilitaires de test et tests de contrat
- Manifeste de plugin - schéma complet du manifeste