Plugins
Kanaalplugins bouwen
Deze gids laat zien hoe je een kanaalplugin bouwt die OpenClaw met een berichtenplatform verbindt. Aan het einde heb je een werkend kanaal met DM-beveiliging, koppeling, antwoordthreads en uitgaande berichten.
Hoe kanaalplugins werken
Kanaalplugins hebben geen eigen verzend-, bewerk- of reactietools nodig. OpenClaw houdt één
gedeelde message-tool in de kern. Je plugin is verantwoordelijk voor:
- Configuratie - accountresolutie en configuratiewizard
- Beveiliging - DM-beleid en toestemmingslijsten
- Koppeling - DM-goedkeuringsflow
- Sessiesyntaxis - hoe providerspecifieke gespreks-id's worden gekoppeld aan basischats, thread-id's en bovenliggende fallbacks
- Uitgaand - tekst, media en polls naar het platform sturen
- Threads - hoe antwoorden in threads worden geplaatst
- Heartbeat-typen - optionele typ-/bezig-signalen voor Heartbeat-afleverdoelen
De kern beheert de gedeelde berichtentool, promptkoppeling, de buitenste sessiesleutelvorm,
generieke :thread:-boekhouding en dispatch.
Nieuwe kanaalplugins moeten ook een message-adapter aanbieden met
defineChannelMessageAdapter uit openclaw/plugin-sdk/channel-message. De
adapter declareert welke duurzame mogelijkheden voor definitief verzenden het native transport
daadwerkelijk ondersteunt en verwijst tekst-/mediaverzendingen naar dezelfde transportfuncties als
de verouderde outbound-adapter. Declareer een mogelijkheid alleen wanneer een contracttest
het native neveneffect en het geretourneerde ontvangstbewijs bewijst.
Zie voor het volledige API-contract, voorbeelden, mogelijkhedenmatrix, ontvangstbewijsregels, definitieve livevoorbeeldafronding, ontvangstbevestigingsbeleid, tests en migratietabel
Kanaalbericht-API.
Als de bestaande outbound-adapter al de juiste verzendmethoden en
mogelijkheidsmetadata heeft, gebruik dan createChannelMessageAdapterFromOutbound(...) om
de message-adapter af te leiden in plaats van handmatig nog een brug te schrijven.
Adapterverzendingen moeten MessageReceipt-waarden retourneren. Wanneer compatibiliteitscode
nog verouderde id's nodig heeft, leid die dan af met listMessageReceiptPlatformIds(...)
of resolveMessageReceiptPrimaryId(...) in plaats van parallelle
messageIds-velden in nieuwe levenscycluscode te behouden.
Kanalen met voorbeeldondersteuning moeten ook message.live.capabilities declareren met
de exacte live-levenscyclus die ze beheren, zoals draftPreview,
previewFinalization, progressUpdates, nativeStreaming of
quietFinalization. Kanalen die een conceptvoorbeeld ter plekke afronden, moeten
ook message.live.finalizer.capabilities declareren, zoals finalEdit,
normalFallback, discardPending, previewReceipt en
retainOnAmbiguousFailure, en de runtimelogica routeren via
defineFinalizableLivePreviewAdapter(...) plus
deliverWithFinalizableLivePreviewAdapter(...). Houd die mogelijkheden onderbouwd
door tests met verifyChannelMessageLiveCapabilityAdapterProofs(...) en
verifyChannelMessageLiveFinalizerProofs(...), zodat native voorbeeld-,
voortgangs-, bewerkings-, fallback-/behoud-, opschoon- en ontvangstbewijsgedrag niet stilzwijgend kan afwijken.
Inkomende ontvangers die platformbevestigingen uitstellen, moeten
message.receive.defaultAckPolicy en supportedAckPolicies declareren in plaats van
bevestigingstiming in monitorlokale state te verbergen. Dek elk gedeclareerd beleid af met
verifyChannelMessageReceiveAckPolicyAdapterProofs(...).
Verouderde helpers voor antwoorden/beurten zoals createChannelTurnReplyPipeline,
dispatchInboundReplyWithBase en recordInboundSessionAndDispatchReply
blijven beschikbaar voor compatibiliteitsdispatchers. Gebruik die namen niet voor nieuwe
kanaalcode; nieuwe plugins moeten beginnen met de message-adapter, ontvangstbewijzen en
helpers voor de ontvangst-/verzendlevenscyclus op openclaw/plugin-sdk/channel-message.
Als je kanaal typindicatoren buiten inkomende antwoorden ondersteunt, bied dan
heartbeat.sendTyping(...) aan op de kanaalplugin. De kern roept dit aan met het
opgeloste Heartbeat-afleverdoel voordat de Heartbeat-modelrun start en
gebruikt de gedeelde levenscyclus voor typ-keepalive en opschoning. Voeg heartbeat.clearTyping(...)
toe wanneer het platform een expliciet stopsignaal nodig heeft.
Als je kanaal berichtentoolparameters toevoegt die mediabronnen bevatten, bied die
parameternamen dan aan via describeMessageTool(...).mediaSourceParams. De kern gebruikt
die expliciete lijst voor normalisatie van sandboxpaden en beleid voor uitgaande mediatoegang,
zodat plugins geen gedeelde kernspecialisaties nodig hebben voor providerspecifieke
avatar-, bijlage- of omslagafbeeldingsparameters.
Geef bij voorkeur een actiegesleutelde map terug, zoals
{ "set-profile": ["avatarUrl", "avatarPath"] }, zodat niet-gerelateerde acties niet
de media-argumenten van een andere actie overerven. Een platte array werkt nog steeds voor parameters die
bewust tussen elke aangeboden actie worden gedeeld.
Als je kanaal providerspecifieke vormgeving nodig heeft voor message(action="send"),
gebruik dan bij voorkeur actions.prepareSendPayload(...). Plaats native kaarten, blokken, embeds of
andere duurzame gegevens onder payload.channelData.<channel> en laat de kern
de daadwerkelijke verzending uitvoeren via de outbound-/message-adapter. Gebruik
actions.handleAction(...) voor verzenden alleen als compatibiliteitsfallback voor
payloads die niet kunnen worden geserialiseerd en opnieuw geprobeerd.
Als je platform extra scope opslaat in gespreks-id's, houd die parsing dan
in de plugin met messaging.resolveSessionConversation(...). Dat is de
canonieke hook voor het koppelen van rawId aan de basisgespreks-id, optionele thread-id,
expliciete baseConversationId en eventuele parentConversationCandidates.
Wanneer je parentConversationCandidates retourneert, houd ze dan geordend van de
smalste parent tot het breedste/basisgesprek.
Gebruik openclaw/plugin-sdk/channel-route wanneer plugincode route-achtige velden moet normaliseren,
een child-thread met zijn parent-route moet vergelijken, of een
stabiele deduplicatiesleutel moet bouwen uit { channel, to, accountId, threadId }. De helper
normaliseert numerieke thread-id's op dezelfde manier als de kern, dus plugins moeten hier de voorkeur aan geven
boven ad-hocvergelijkingen met String(threadId).
Plugins met providerspecifieke doelsyntaxis kunnen hun parser injecteren in
resolveChannelRouteTargetWithParser(...) en toch dezelfde routedoelevorm en
thread-fallbacksemantiek krijgen die de kern gebruikt.
Gebundelde plugins die dezelfde parsing nodig hebben voordat het kanaalregister opstart,
kunnen ook een session-key-api.ts-bestand op topniveau aanbieden met een bijbehorende
resolveSessionConversation(...)-export. De kern gebruikt dat opstartveilige oppervlak
alleen wanneer het runtimepluginregister nog niet beschikbaar is.
messaging.resolveParentConversationCandidates(...) blijft beschikbaar als
verouderde compatibiliteitsfallback wanneer een plugin alleen parent-fallbacks nodig heeft boven op
de generieke/ruwe id. Als beide hooks bestaan, gebruikt de kern eerst
resolveSessionConversation(...).parentConversationCandidates en valt alleen
terug op resolveParentConversationCandidates(...) wanneer de canonieke hook
ze weglaat.
Goedkeuringen en kanaalmogelijkheden
De meeste kanaalplugins hebben geen goedkeuringsspecifieke code nodig.
- Core beheert
/approvein dezelfde chat, gedeelde payloads voor goedkeuringsknoppen en generieke fallback-bezorging. - Geef de voorkeur aan één
approvalCapability-object op de channel-Plugin wanneer het kanaal goedkeuringsspecifiek gedrag nodig heeft. ChannelPlugin.approvalsis verwijderd. Plaats feiten over goedkeuringsbezorging, native gedrag, rendering en auth opapprovalCapability.plugin.authis alleen voor login/logout; core leest geen goedkeurings-auth-hooks meer uit dat object.approvalCapability.authorizeActorActionenapprovalCapability.getActionAvailabilityStatezijn de canonieke seam voor goedkeurings-auth.- Gebruik
approvalCapability.getActionAvailabilityStatevoor beschikbaarheid van goedkeurings-auth in dezelfde chat. - Als je kanaal native exec-goedkeuringen exposeert, gebruik dan
approvalCapability.getExecInitiatingSurfaceStatevoor de initiating-surface/native-client-status wanneer die afwijkt van goedkeurings-auth in dezelfde chat. Core gebruikt die exec-specifieke hook om onderscheid te maken tussenenabledendisabled, te bepalen of het initiating channel native exec-goedkeuringen ondersteunt, en het kanaal op te nemen in fallback-begeleiding voor native clients.createApproverRestrictedNativeApprovalCapability(...)vult dit in voor het gangbare geval. - Gebruik
outbound.shouldSuppressLocalPayloadPromptofoutbound.beforeDeliverPayloadvoor kanaalspecifiek payload-lifecycle-gedrag, zoals het verbergen van dubbele lokale goedkeuringsprompts of het verzenden van typing indicators vóór bezorging. - Gebruik
approvalCapability.deliveryalleen voor native goedkeuringsrouting of fallback-onderdrukking. - Gebruik
approvalCapability.nativeRuntimevoor native goedkeuringsfeiten die eigendom zijn van het kanaal. Houd het lazy op hot channel entrypoints metcreateLazyChannelApprovalNativeRuntimeAdapter(...), dat je runtime-module on demand kan importeren terwijl core nog steeds de goedkeuringslifecycle kan samenstellen. - Gebruik
approvalCapability.renderalleen wanneer een kanaal echt aangepaste goedkeuringspayloads nodig heeft in plaats van de gedeelde renderer. - Gebruik
approvalCapability.describeExecApprovalSetupwanneer het kanaal wil dat het antwoord voor het disabled-pad uitlegt welke exacte config-knoppen nodig zijn om native exec-goedkeuringen in te schakelen. De hook ontvangt{ channel, channelLabel, accountId }; named-account-kanalen moeten account-scoped paden renderen, zoalschannels.<channel>.accounts.<id>.execApprovals.*, in plaats van top-level defaults. - Als een kanaal stabiele eigenaarachtige DM-identiteiten uit bestaande config kan afleiden, gebruik dan
createResolvedApproverActionAuthAdapteruitopenclaw/plugin-sdk/approval-runtimeom/approvein dezelfde chat te beperken zonder goedkeuringsspecifieke core-logica toe te voegen. - Als een kanaal native goedkeuringsbezorging nodig heeft, houd kanaalcode dan gericht op target-normalisatie plus transport-/presentatiefeiten. Gebruik
createChannelExecApprovalProfile,createChannelNativeOriginTargetResolver,createChannelApproverDmTargetResolverencreateApproverRestrictedNativeApprovalCapabilityuitopenclaw/plugin-sdk/approval-runtime. Plaats de kanaalspecifieke feiten achterapprovalCapability.nativeRuntime, idealiter viacreateChannelApprovalNativeRuntimeAdapter(...)ofcreateLazyChannelApprovalNativeRuntimeAdapter(...), zodat core de handler kan samenstellen en request filtering, routing, dedupe, expiry, Gateway-subscriptie en routed-elsewhere-notificaties kan beheren.nativeRuntimeis opgesplitst in enkele kleinere seams: createChannelNativeOriginTargetResolvergebruikt standaard de gedeelde channel-route-matcher voor{ to, accountId, threadId }-targets. GeeftargetsMatchalleen door wanneer een kanaal provider-specifieke equivalentieregels heeft, zoals Slack timestamp-prefix-matching.- Geef
normalizeTargetForMatchdoor aancreateChannelNativeOriginTargetResolverwanneer het kanaal provider-id's moet canonicaliseren voordat de standaard route-matcher of een aangepastetargetsMatch-callback draait, terwijl het oorspronkelijke target voor bezorging behouden blijft. GebruiknormalizeTargetalleen wanneer het opgeloste bezorgtarget zelf gecanonicaliseerd moet worden. availability- of het account geconfigureerd is en of een request moet worden afgehandeldpresentation- map het gedeelde goedkeurings-viewmodel naar native payloads of eindacties voor pending/resolved/expiredtransport- bereid targets voor en verzend/update/verwijder native goedkeuringsberichteninteractions- optionele bind/unbind/clear-action-hooks voor native knoppen of reactiesobserve- optionele diagnostische hooks voor bezorging- Als het kanaal runtime-owned objecten nodig heeft, zoals een client, token, Bolt-app of webhook receiver, registreer die dan via
openclaw/plugin-sdk/channel-runtime-context. De generieke runtime-context-registry laat core capability-driven handlers bootstrappen vanuit de opstartstatus van het kanaal zonder goedkeuringsspecifieke wrapper-glue toe te voegen. - Grijp alleen naar het lagere niveau
createChannelApprovalHandlerofcreateChannelNativeApprovalRuntimewanneer de capability-driven seam nog niet expressief genoeg is. - Native goedkeuringskanalen moeten zowel
accountIdalsapprovalKindvia die helpers routeren.accountIdhoudt multi-account-goedkeuringsbeleid scoped naar het juiste bot-account, enapprovalKindhoudt exec- versus Plugin-goedkeuringsgedrag beschikbaar voor het kanaal zonder hardcoded branches in core. - Core beheert nu ook notices voor het omleiden van goedkeuringen. Channel-Plugins moeten geen eigen follow-upberichten "approval went to DMs / another channel" verzenden vanuit
createChannelNativeApprovalRuntime; exposeer in plaats daarvan accurate origin- en approver-DM-routing via de gedeelde helpers voor goedkeuringscapabilities en laat core daadwerkelijke bezorgingen aggregeren voordat er een notice terug naar de initiating chat wordt gepost. - Behoud het type van de bezorgde goedkeurings-id end-to-end. Native clients mogen exec- versus Plugin-goedkeuringsrouting niet raden of herschrijven vanuit kanaal-lokale status.
- Verschillende goedkeuringstypen kunnen bewust verschillende native surfaces exposen.
Huidige gebundelde voorbeelden:
- Slack houdt native goedkeuringsrouting beschikbaar voor zowel exec- als Plugin-id's.
- Matrix houdt dezelfde native DM-/kanaalrouting en reactie-UX voor exec- en Plugin-goedkeuringen, terwijl auth nog steeds per goedkeuringstype kan verschillen.
createApproverRestrictedNativeApprovalAdapterbestaat nog steeds als compatibiliteitswrapper, maar nieuwe code moet de voorkeur geven aan de capability builder enapprovalCapabilityop de Plugin exposen.
Voor hot channel entrypoints geef je de voorkeur aan de smallere runtime-subpaden wanneer je maar één deel van die familie nodig hebt:
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
Geef eveneens de voorkeur aan openclaw/plugin-sdk/setup-runtime,
openclaw/plugin-sdk/setup-adapter-runtime,
openclaw/plugin-sdk/reply-runtime,
openclaw/plugin-sdk/reply-dispatch-runtime,
openclaw/plugin-sdk/reply-reference en
openclaw/plugin-sdk/reply-chunking wanneer je de bredere overkoepelende
surface niet nodig hebt.
Specifiek voor setup:
openclaw/plugin-sdk/setup-runtimedekt de runtime-veilige setup-helpers: import-veilige setup patch adapters (createPatchedAccountSetupAdapter,createEnvPatchedAccountSetupAdapter,createSetupInputPresenceValidator), lookup-note-output,promptResolvedAllowFrom,splitSetupEntriesen de gedelegeerde setup-proxy buildersopenclaw/plugin-sdk/setup-adapter-runtimeis de smalle env-aware adapter- seam voorcreateEnvPatchedAccountSetupAdapteropenclaw/plugin-sdk/channel-setupdekt de optional-install setup builders plus enkele setup-veilige primitives:createOptionalChannelSetupSurface,createOptionalChannelSetupAdapter,
Als je kanaal env-driven setup of auth ondersteunt en generieke startup/config-
flows die env-namen vóór runtime-loads moeten kennen, declareer ze dan in het
Plugin-manifest met channelEnvVars. Houd channel runtime envVars of lokale
constanten alleen voor operator-facing copy.
Als je kanaal kan verschijnen in status, channels list, channels status of
SecretRef-scans voordat de Plugin-runtime start, voeg dan openclaw.setupEntry toe in
package.json. Dat entrypoint moet veilig te importeren zijn in read-only command-
paden en moet de kanaalmetadata, setup-veilige config adapter, status
adapter en metadata voor channel secret targets teruggeven die nodig zijn voor die samenvattingen. Start geen
clients, listeners of transport-runtimes vanuit de setup entry.
Houd ook het importpad van de main channel entry smal. Discovery kan de
entry en de channel Plugin-module evalueren om capabilities te registreren zonder
het kanaal te activeren. Bestanden zoals channel-plugin-api.ts moeten het channel
Plugin-object exporteren zonder setup wizards, transport clients, socket
listeners, subprocess launchers of service startup-modules te importeren. Plaats die runtime-
onderdelen in modules die worden geladen vanuit registerFull(...), runtime setters of lazy
capability adapters.
createOptionalChannelSetupWizard, DEFAULT_ACCOUNT_ID,
createTopLevelChannelDmPolicy, setSetupChannelEnabled en
splitSetupEntries
- gebruik de bredere
openclaw/plugin-sdk/setup-seam alleen wanneer je ook de zwaardere gedeelde setup/config-helpers nodig hebt, zoalsmoveSingleAccountChannelSectionToDefaultAccount(...)
Als je kanaal alleen "install this plugin first" wil tonen in setup-
surfaces, geef dan de voorkeur aan createOptionalChannelSetupSurface(...). De gegenereerde
adapter/wizard falen gesloten bij config writes en finalization, en ze hergebruiken
hetzelfde install-required-bericht in validatie, finalize en docs-link-
copy.
Voor andere hot channel-paden geef je de voorkeur aan de smalle helpers boven bredere legacy- surfaces:
openclaw/plugin-sdk/account-core,openclaw/plugin-sdk/account-id,openclaw/plugin-sdk/account-resolutionenopenclaw/plugin-sdk/account-helpersvoor multi-account-config en default-account-fallbackopenclaw/plugin-sdk/inbound-envelopeenopenclaw/plugin-sdk/inbound-reply-dispatchvoor inbound route/envelope en record-and-dispatch-wiringopenclaw/plugin-sdk/messaging-targetsvoor target parsing/matchingopenclaw/plugin-sdk/outbound-mediaenopenclaw/plugin-sdk/outbound-runtimevoor media loading plus outbound identity/send delegates en payload planningbuildThreadAwareOutboundSessionRoute(...)uitopenclaw/plugin-sdk/channel-corewanneer een outbound route een explicietereplyToId/threadIdmoet behouden of de huidige:thread:-sessie moet herstellen nadat de base session key nog steeds matcht. Provider-Plugins kunnen precedence, suffix-gedrag en thread-id-normalisatie overriden wanneer hun platform native thread delivery semantics heeft.openclaw/plugin-sdk/thread-bindings-runtimevoor thread-binding-lifecycle en adapterregistratieopenclaw/plugin-sdk/agent-media-payloadalleen wanneer een legacy agent/media- payloadveldindeling nog vereist isopenclaw/plugin-sdk/telegram-command-configvoor Telegram custom-command- normalisatie, validatie van duplicaten/conflicten en een fallback-stabiel command config-contract
Auth-only kanalen kunnen meestal stoppen bij het standaardpad: core handelt goedkeuringen af en de Plugin exposeert alleen outbound/auth-capabilities. Native goedkeuringskanalen zoals Matrix, Slack, Telegram en aangepaste chat-transports moeten de gedeelde native helpers gebruiken in plaats van hun eigen goedkeuringslifecycle te bouwen.
Beleid voor inbound vermeldingen
Houd verwerking van inbound vermeldingen opgesplitst in twee lagen:
- plugin-owned evidence gathering
- shared policy evaluation
Gebruik openclaw/plugin-sdk/channel-mention-gating voor mention-policy-beslissingen.
Gebruik openclaw/plugin-sdk/channel-inbound alleen wanneer je de bredere inbound
helper barrel nodig hebt.
Goede plek voor Plugin-lokale logica:
- reply-to-bot-detectie
- quoted-bot-detectie
- thread-participation-checks
- service/system-message-exclusions
- platform-native caches die nodig zijn om bot-participatie te bewijzen
Goede plek voor de gedeelde helper:
requireMention- expliciet vermeldingsresultaat
- allowlist voor impliciete vermeldingen
- opdrachtbypass
- uiteindelijke overslabeslissing
Aanbevolen flow:
- Bereken lokale vermeldingsfeiten.
- Geef die feiten door aan
resolveInboundMentionDecision({ facts, policy }). - Gebruik
decision.effectiveWasMentioned,decision.shouldBypassMentionendecision.shouldSkipin je inbound 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 stelt dezelfde gedeelde vermeldingshelpers beschikbaar voor
gebundelde kanaalplugins die al afhankelijk zijn van runtime-injectie:
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
Als je alleen implicitMentionKindWhen en
resolveInboundMentionDecision nodig hebt, importeer dan vanuit
openclaw/plugin-sdk/channel-mention-gating om te voorkomen dat niet-gerelateerde inbound
runtimehelpers worden geladen.
De oudere resolveMentionGating*-helpers blijven op
openclaw/plugin-sdk/channel-inbound alleen beschikbaar als compatibiliteitsexports. Nieuwe code
moet resolveInboundMentionDecision({ facts, policy }) gebruiken.
Doorloop
Package and manifest
Maak de standaard pluginbestanden. Het veld channel in package.json is
wat dit een kanaalplugin maakt. Zie voor het volledige oppervlak voor pakketmetadata
Plugin instellen en configureren:
{
"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 valideert plugins.entries.acme-chat.config. Gebruik dit voor
instellingen die eigendom zijn van de plugin en geen kanaalaccountconfiguratie zijn. channelConfigs
valideert channels.acme-chat en is de cold-path-bron die door het configuratieschema,
de setup en UI-oppervlakken wordt gebruikt voordat de pluginruntime laadt.
Build the channel plugin object
De interface ChannelPlugin heeft veel optionele adapteroppervlakken. Begin met
het minimum - id en setup - en voeg adapters toe wanneer je ze nodig hebt.
Maak 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);
},
},
},
});
Voor kanalen die zowel canonieke DM-sleutels op topniveau als verouderde geneste sleutels accepteren, gebruik je de helpers uit plugin-sdk/channel-config-helpers: resolveChannelDmAccess, resolveChannelDmPolicy, resolveChannelDmAllowFrom en normalizeChannelDmPolicy houden accountlokale waarden vóór overgenomen rootwaarden. Combineer dezelfde resolver met doctor-reparatie via normalizeLegacyDmAliases, zodat runtime en migratie hetzelfde contract lezen.
What createChatChannelPlugin does for you
In plaats van low-level adapterinterfaces handmatig te implementeren, geef je declaratieve opties door en stelt de builder ze samen:
| Optie | Wat het verbindt |
|---|---|
security.dm |
Scoped DM-beveiligingsresolver vanuit configuratievelden |
pairing.text |
Op tekst gebaseerde DM-koppelingsflow met code-uitwisseling |
threading |
Resolver voor antwoordmodus (vast, account-scoped of aangepast) |
outbound.attachedResults |
Verzendfuncties die resultaatmetadata retourneren (bericht-ID's) |
Je kunt ook raw adapterobjecten doorgeven in plaats van de declaratieve opties als je volledige controle nodig hebt.
Raw outbound-adapters mogen een functie chunker(text, limit, ctx) definiëren.
De optionele ctx.formatting draagt beslissingen over formattering tijdens levering
zoals maxLinesPerMessage; pas dit toe vóór verzending, zodat antwoordthreading
en chunkgrenzen één keer door gedeelde outbound-levering worden opgelost.
Verzendcontexten bevatten ook replyToIdSource (implicit of explicit)
wanneer een native antwoorddoel is opgelost, zodat payloadhelpers
expliciete antwoordtags kunnen behouden zonder een impliciete eenmalige antwoordslot te gebruiken.
Wire the entry point
Maak 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(/* ... */);
},
});
Plaats CLI-descriptors die eigendom zijn van het kanaal in registerCliMetadata(...), zodat OpenClaw
ze in roothelp kan tonen zonder de volledige kanaalruntime te activeren,
terwijl normale volledige loads nog steeds dezelfde descriptors meenemen voor echte opdrachtregistratie.
Houd registerFull(...) voor werk dat alleen tijdens runtime nodig is.
Als registerFull(...) Gateway-RPC-methoden registreert, gebruik dan een
plugin-specifiek prefix. Core-adminnamespaces (config.*,
exec.approvals.*, wizard.*, update.*) blijven gereserveerd en worden altijd
opgelost naar operator.admin.
defineChannelPluginEntry handelt de splitsing in registratiemodus automatisch af. Zie
Invoerpunten voor alle
opties.
Add a setup entry
Maak setup-entry.ts voor lichtgewicht laden tijdens onboarding:
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
import { acmeChatPlugin } from "./src/channel.js";
export default defineSetupPluginEntry(acmeChatPlugin);
OpenClaw laadt dit in plaats van de volledige entry wanneer het kanaal is uitgeschakeld of niet is geconfigureerd. Het voorkomt dat zware runtimecode wordt binnengehaald tijdens setupflows. Zie Setup en configuratie voor details.
Gebundelde workspace-kanalen die setup-veilige exports splitsen naar sidecar
modules kunnen defineBundledChannelSetupEntry(...) uit
openclaw/plugin-sdk/channel-entry-contract gebruiken wanneer ze ook een
expliciete runtime-setter tijdens setup nodig hebben.
Handle inbound messages
Je plugin moet berichten van het platform ontvangen en doorsturen naar OpenClaw. Het typische patroon is een Webhook die de aanvraag verifieert en deze via de inbound-handler van je kanaal dispatcht:
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
Schrijf 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/
Zie Testen voor gedeelde testhelpers.
Bestandsstructuur
<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)
Geavanceerde onderwerpen
Vaste, accountgebonden of aangepaste antwoordmodi
describeMessageTool en actiedetectie
inferTargetChatType, looksLikeId, resolveTarget
TTS, STT, media, subagent via api.runtime
Gedeelde levenscyclus voor inkomende turns: ingest, resolve, record, dispatch, finalize
Volgende stappen
- Providerplugins - als je plugin ook modellen levert
- SDK-overzicht - volledige importreferentie voor subpaden
- SDK-testen - testhulpmiddelen en contracttests
- Pluginmanifest - volledig manifestschema