Building plugins
Membangun Plugin kanal
Panduan ini menjelaskan cara membangun Plugin kanal yang menghubungkan OpenClaw ke platform perpesanan. Pada akhirnya, Anda akan memiliki kanal yang berfungsi dengan keamanan DM, penyandingan, penguliran balasan, dan perpesanan keluar.
Cara kerja Plugin kanal
Plugin kanal tidak memerlukan alat kirim/edit/reaksi miliknya sendiri. OpenClaw mempertahankan satu
alat message bersama di core. Plugin Anda memiliki:
- Konfigurasi - resolusi akun dan wizard penyiapan
- Keamanan - kebijakan DM dan daftar izinkan
- Penyandingan - alur persetujuan DM
- Tata bahasa sesi - cara id percakapan khusus penyedia dipetakan ke obrolan dasar, id utas, dan fallback induk
- Keluar - mengirim teks, media, dan polling ke platform
- Penguliran - cara balasan diutas
- Pengetikan Heartbeat - sinyal mengetik/sibuk opsional untuk target pengiriman Heartbeat
Core memiliki alat pesan bersama, pengawatan prompt, bentuk kunci sesi luar,
pembukuan :thread: generik, dan dispatch.
Plugin kanal baru juga harus mengekspos adapter message dengan
defineChannelMessageAdapter dari openclaw/plugin-sdk/channel-outbound. Adapter
mendeklarasikan kemampuan pengiriman final tahan lama yang benar-benar didukung oleh transport native
dan mengarahkan pengiriman teks/media ke fungsi transport yang sama seperti
adapter outbound lama. Deklarasikan kemampuan hanya ketika uji kontrak
membuktikan efek samping native dan tanda terima yang dikembalikan.
Untuk kontrak API lengkap, contoh, matriks kemampuan, aturan tanda terima, finalisasi
pratinjau langsung, kebijakan ack penerimaan, pengujian, dan tabel migrasi, lihat
API outbound kanal.
Jika adapter outbound yang ada sudah memiliki metode kirim dan
metadata kemampuan yang tepat, gunakan createChannelMessageAdapterFromOutbound(...) untuk
menurunkan adapter message alih-alih menulis bridge lain secara manual.
Pengiriman adapter harus mengembalikan nilai MessageReceipt. Ketika kode kompatibilitas
masih memerlukan id lama, turunkan id tersebut dengan listMessageReceiptPlatformIds(...)
atau resolveMessageReceiptPrimaryId(...) alih-alih mempertahankan field
messageIds paralel dalam kode siklus hidup baru.
Kanal yang mampu melakukan pratinjau juga harus mendeklarasikan message.live.capabilities dengan
siklus hidup langsung persis yang mereka miliki, seperti draftPreview,
previewFinalization, progressUpdates, nativeStreaming, atau
quietFinalization. Kanal yang memfinalisasi pratinjau draf di tempat juga harus
mendeklarasikan message.live.finalizer.capabilities, seperti finalEdit,
normalFallback, discardPending, previewReceipt, dan
retainOnAmbiguousFailure, serta merutekan logika runtime melalui
defineFinalizableLivePreviewAdapter(...) plus
deliverWithFinalizableLivePreviewAdapter(...). Jaga agar kemampuan tersebut didukung
oleh pengujian verifyChannelMessageLiveCapabilityAdapterProofs(...) dan
verifyChannelMessageLiveFinalizerProofs(...) sehingga perilaku pratinjau native,
progres, edit, fallback/retensi, pembersihan, dan tanda terima tidak dapat bergeser
diam-diam.
Penerima inbound yang menunda pengakuan platform harus mendeklarasikan
message.receive.defaultAckPolicy dan supportedAckPolicies alih-alih menyembunyikan
waktu ack dalam state lokal monitor. Cakup setiap kebijakan yang dideklarasikan dengan
verifyChannelMessageReceiveAckPolicyAdapterProofs(...).
Helper balasan lama seperti createChannelTurnReplyPipeline,
dispatchInboundReplyWithBase, dan recordInboundSessionAndDispatchReply
tetap tersedia untuk dispatcher kompatibilitas. Jangan gunakan nama-nama tersebut untuk kode
kanal baru; Plugin baru harus mulai dengan adapter message, tanda terima, dan
helper siklus hidup terima/kirim di openclaw/plugin-sdk/channel-outbound.
Kanal yang memigrasikan otorisasi inbound dapat menggunakan subpath eksperimental
openclaw/plugin-sdk/channel-ingress-runtime dari path penerimaan runtime.
Subpath mempertahankan lookup platform dan efek samping di dalam Plugin, sambil
berbagi resolusi state daftar izinkan, keputusan rute/pengirim/perintah/peristiwa/aktivasi,
diagnostik yang direda, dan pemetaan penerimaan turn. Pertahankan normalisasi
identitas Plugin dalam descriptor yang Anda berikan ke resolver; jangan
menserialisasi nilai match mentah dari state atau keputusan yang di-resolve. Lihat
API ingress kanal untuk desain API,
batas kepemilikan, dan ekspektasi pengujian.
Jika kanal Anda mendukung indikator mengetik di luar balasan inbound, ekspos
heartbeat.sendTyping(...) pada Plugin kanal. Core memanggilnya dengan
target pengiriman Heartbeat yang di-resolve sebelum run model Heartbeat dimulai dan
menggunakan siklus hidup keepalive/pembersihan pengetikan bersama. Tambahkan heartbeat.clearTyping(...)
ketika platform memerlukan sinyal berhenti eksplisit.
Jika kanal Anda menambahkan parameter alat pesan yang membawa sumber media, ekspos nama
parameter tersebut melalui describeMessageTool(...).mediaSourceParams. Core menggunakan
daftar eksplisit tersebut untuk normalisasi path sandbox dan kebijakan akses media keluar,
sehingga Plugin tidak memerlukan kasus khusus shared-core untuk parameter avatar, lampiran,
atau gambar sampul khusus penyedia.
Lebih baik mengembalikan peta berbasis kunci aksi seperti
{ "set-profile": ["avatarUrl", "avatarPath"] } sehingga aksi yang tidak terkait tidak
mewarisi argumen media aksi lain. Array datar tetap berfungsi untuk parameter yang
memang sengaja dibagikan di setiap aksi yang diekspos.
Kanal yang harus mengekspos URL publik sementara untuk pengambilan media sisi platform
dapat menggunakan createHostedOutboundMediaStore(...) dari
openclaw/plugin-sdk/outbound-media dengan penyimpanan state Plugin. Pertahankan
parsing rute platform dan penegakan token di dalam Plugin kanal; helper bersama
hanya memiliki pemuatan media, metadata kedaluwarsa, baris chunk, dan pembersihan.
Jika kanal Anda memerlukan pembentukan khusus penyedia untuk message(action="send"),
lebih baik gunakan actions.prepareSendPayload(...). Letakkan kartu native, blok, embed, atau
data tahan lama lain di bawah payload.channelData.<channel> dan biarkan core melakukan
pengiriman aktual melalui adapter outbound/message. Gunakan
actions.handleAction(...) untuk kirim hanya sebagai fallback kompatibilitas untuk
payload yang tidak dapat diserialisasi dan dicoba ulang.
Jika platform Anda menyimpan scope tambahan di dalam id percakapan, pertahankan parsing tersebut
di Plugin dengan messaging.resolveSessionConversation(...). Itu adalah hook
kanonis untuk memetakan rawId ke id percakapan dasar, id utas opsional,
baseConversationId eksplisit, dan parentConversationCandidates apa pun.
Ketika Anda mengembalikan parentConversationCandidates, pertahankan urutannya dari
induk yang paling sempit hingga percakapan paling luas/dasar.
Gunakan openclaw/plugin-sdk/channel-route ketika kode Plugin perlu menormalkan
field mirip rute, membandingkan utas anak dengan rute induknya, atau membangun
kunci dedupe stabil dari { channel, to, accountId, threadId }. Helper tersebut
menormalkan id utas numerik dengan cara yang sama seperti core, sehingga Plugin sebaiknya
menggunakannya alih-alih perbandingan ad hoc String(threadId).
Plugin dengan tata bahasa target khusus penyedia harus mengekspos
messaging.resolveOutboundSessionRoute(...) sehingga core mendapatkan identitas
sesi dan utas native penyedia tanpa menggunakan shim parser.
Plugin terbundel yang memerlukan parsing yang sama sebelum registry kanal berjalan
juga dapat mengekspos file tingkat atas session-key-api.ts dengan ekspor
resolveSessionConversation(...) yang cocok. Core menggunakan permukaan yang aman untuk bootstrap ini
hanya ketika registry Plugin runtime belum tersedia.
messaging.resolveParentConversationCandidates(...) tetap tersedia sebagai
fallback kompatibilitas lama ketika Plugin hanya memerlukan fallback induk di atas
id generik/mentah. Jika kedua hook ada, core menggunakan
resolveSessionConversation(...).parentConversationCandidates terlebih dahulu dan hanya
fallback ke resolveParentConversationCandidates(...) ketika hook kanonis
menghilangkannya.
Persetujuan dan kemampuan kanal
Sebagian besar Plugin kanal tidak memerlukan kode khusus persetujuan.
- Core memiliki
/approvedi chat yang sama, payload tombol persetujuan bersama, dan pengiriman fallback generik. - Lebih baik gunakan satu objek
approvalCapabilitypada Plugin channel saat channel membutuhkan perilaku khusus persetujuan. ChannelPlugin.approvalsdihapus. Letakkan fakta pengiriman/native/render/auth persetujuan padaapprovalCapability.plugin.authhanya untuk login/logout; core tidak lagi membaca hook auth persetujuan dari objek tersebut.approvalCapability.authorizeActorActiondanapprovalCapability.getActionAvailabilityStateadalah seam auth persetujuan kanonis.- Gunakan
approvalCapability.getActionAvailabilityStateuntuk ketersediaan auth persetujuan di chat yang sama. Biarkan approver yang sudah dikonfigurasi tetap tersedia untuk/approvebahkan saat pengiriman native dinonaktifkan; gunakan state initiating-surface native untuk panduan pengiriman/setup sebagai gantinya. - Jika channel Anda mengekspos persetujuan exec native, gunakan
approvalCapability.getExecInitiatingSurfaceStateuntuk state initiating-surface/klien-native saat berbeda dari auth persetujuan di chat yang sama. Core menggunakan hook khusus exec tersebut untuk membedakanenabledvsdisabled, menentukan apakah channel pemicu mendukung persetujuan exec native, dan menyertakan channel dalam panduan fallback klien-native.createApproverRestrictedNativeApprovalCapability(...)mengisi ini untuk kasus umum. - Gunakan
outbound.shouldSuppressLocalPayloadPromptatauoutbound.beforeDeliverPayloaduntuk perilaku siklus hidup payload khusus channel seperti menyembunyikan prompt persetujuan lokal duplikat atau mengirim indikator mengetik sebelum pengiriman. - Gunakan
approvalCapability.deliveryhanya untuk perutean persetujuan native atau penekanan fallback. - Gunakan
approvalCapability.nativeRuntimeuntuk fakta persetujuan native yang dimiliki channel. Pertahankan agar lazy pada entrypoint channel panas dengancreateLazyChannelApprovalNativeRuntimeAdapter(...), yang dapat mengimpor modul runtime Anda sesuai kebutuhan sambil tetap memungkinkan core merakit siklus hidup persetujuan. - Gunakan
approvalCapability.renderhanya saat channel benar-benar membutuhkan payload persetujuan kustom alih-alih renderer bersama. - Gunakan
approvalCapability.describeExecApprovalSetupsaat channel ingin balasan jalur nonaktif menjelaskan knob konfigurasi persis yang diperlukan untuk mengaktifkan persetujuan exec native. Hook menerima{ channel, channelLabel, accountId }; channel akun bernama harus merender path yang tercakup akun sepertichannels.<channel>.accounts.<id>.execApprovals.*alih-alih default tingkat atas. - Gunakan
approvalCapability.describePluginApprovalSetupsaat panduan kegagalan persetujuan Plugin aman ditampilkan untuk kegagalan no-route dan timeout persetujuan Plugin.createApproverRestrictedNativeApprovalCapability(...)tidak menyimpulkan ini daridescribeExecApprovalSetup; teruskan helper yang sama secara eksplisit hanya saat persetujuan Plugin dan exec benar-benar menggunakan setup native yang sama. - Jika channel dapat menyimpulkan identitas DM yang stabil seperti pemilik dari konfigurasi yang ada, gunakan
createResolvedApproverActionAuthAdapterdariopenclaw/plugin-sdk/approval-runtimeuntuk membatasi/approvedi chat yang sama tanpa menambahkan logika core khusus persetujuan. - Jika auth persetujuan kustom sengaja hanya mengizinkan fallback chat yang sama, kembalikan
markImplicitSameChatApprovalAuthorization({ authorized: true })dariopenclaw/plugin-sdk/approval-auth-runtime; jika tidak, core memperlakukan hasilnya sebagai otorisasi approver eksplisit. - Jika callback native milik channel menyelesaikan persetujuan secara langsung, gunakan
isImplicitSameChatApprovalAuthorization(...)sebelum menyelesaikan agar fallback implisit tetap melalui otorisasi aktor normal channel. - Jika channel membutuhkan pengiriman persetujuan native, jaga kode channel tetap berfokus pada normalisasi target plus fakta transport/presentasi. Gunakan
createChannelExecApprovalProfile,createChannelNativeOriginTargetResolver,createChannelApproverDmTargetResolver, dancreateApproverRestrictedNativeApprovalCapabilitydariopenclaw/plugin-sdk/approval-runtime. Letakkan fakta khusus channel di balikapprovalCapability.nativeRuntime, idealnya melaluicreateChannelApprovalNativeRuntimeAdapter(...)ataucreateLazyChannelApprovalNativeRuntimeAdapter(...), agar core dapat merakit handler dan memiliki pemfilteran permintaan, perutean, dedupe, kedaluwarsa, langganan gateway, dan pemberitahuan dirutekan-ke-tempat-lain.nativeRuntimedibagi menjadi beberapa seam yang lebih kecil: - Gunakan
createNativeApprovalChannelRouteGatesdariopenclaw/plugin-sdk/approval-native-runtimesaat channel mendukung pengiriman native asal sesi dan target penerusan persetujuan eksplisit. Helper ini memusatkan pemilihan konfigurasi persetujuan, penangananmode, filter agen/sesi, pengikatan akun, pencocokan target sesi, dan pencocokan daftar target sementara pemanggil tetap memiliki id channel, mode penerusan default, lookup akun, pemeriksaan transport aktif, normalisasi target, dan resolusi target sumber turn. Jangan gunakan ini untuk membuat default kebijakan channel milik core; teruskan mode default terdokumentasi channel secara eksplisit. createChannelNativeOriginTargetResolvermenggunakan pencocok rute channel bersama secara default untuk target{ to, accountId, threadId }. TeruskantargetsMatchhanya saat channel memiliki aturan ekuivalensi khusus provider, seperti pencocokan prefiks timestamp Slack.- Teruskan
normalizeTargetForMatchkecreateChannelNativeOriginTargetResolversaat channel perlu mengkanoniskan id provider sebelum pencocok rute default atau callbacktargetsMatchkustom berjalan, sambil mempertahankan target asli untuk pengiriman. GunakannormalizeTargethanya saat target pengiriman yang terselesaikan itu sendiri harus dikanoniskan. availability- apakah akun sudah dikonfigurasi dan apakah permintaan harus ditanganipresentation- memetakan model tampilan persetujuan bersama menjadi payload native pending/terselesaikan/kedaluwarsa atau aksi akhirtransport- menyiapkan target plus mengirim/memperbarui/menghapus pesan persetujuan nativeinteractions- hook bind/unbind/clear-action opsional untuk tombol atau reaksi native, plus hookcancelDeliveredopsional. ImplementasikancancelDeliveredsaatdeliverPendingmendaftarkan state dalam proses atau persisten (seperti penyimpanan target reaksi) agar state tersebut dapat dilepas jika penghentian handler membatalkan pengiriman sebelumbindPendingberjalan atau saatbindPendingtidak mengembalikan handleobserve- hook diagnostik pengiriman opsional- Jika channel membutuhkan objek milik runtime seperti klien, token, aplikasi Bolt, atau penerima webhook, daftarkan melalui
openclaw/plugin-sdk/channel-runtime-context. Registry runtime-context generik memungkinkan core mem-bootstrap handler berbasis capability dari state startup channel tanpa menambahkan glue wrapper khusus persetujuan. - Gunakan
createChannelApprovalHandlerataucreateChannelNativeApprovalRuntimetingkat lebih rendah hanya saat seam berbasis capability belum cukup ekspresif. - Channel persetujuan native harus merutekan
accountIddanapprovalKindmelalui helper tersebut.accountIdmenjaga kebijakan persetujuan multi-akun tetap tercakup pada akun bot yang tepat, danapprovalKindmenjaga perilaku persetujuan exec vs Plugin tersedia untuk channel tanpa cabang hardcoded di core. - Core kini juga memiliki pemberitahuan reroute persetujuan. Plugin channel tidak boleh mengirim pesan tindak lanjut "persetujuan dikirim ke DM / channel lain" sendiri dari
createChannelNativeApprovalRuntime; sebagai gantinya, ekspos perutean asal + DM approver yang akurat melalui helper capability persetujuan bersama dan biarkan core mengagregasi pengiriman aktual sebelum memposting pemberitahuan apa pun kembali ke chat pemicu. - Pertahankan jenis id persetujuan yang dikirimkan dari ujung ke ujung. Klien native tidak boleh menebak atau menulis ulang perutean persetujuan exec vs Plugin dari state lokal channel.
- Jenis persetujuan yang berbeda dapat sengaja mengekspos surface native yang berbeda.
Contoh bundel saat ini:
- Slack menjaga perutean persetujuan native tersedia untuk id exec dan Plugin.
- Matrix menjaga perutean DM/channel native dan UX reaksi yang sama untuk persetujuan exec dan Plugin, sambil tetap membiarkan auth berbeda berdasarkan jenis persetujuan.
createApproverRestrictedNativeApprovalAdaptermasih ada sebagai wrapper kompatibilitas, tetapi kode baru sebaiknya menggunakan builder capability dan mengeksposapprovalCapabilitypada Plugin.
Untuk entrypoint channel panas, lebih baik gunakan subpath runtime yang lebih sempit saat Anda hanya membutuhkan satu bagian dari keluarga tersebut:
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
Demikian pula, lebih baik gunakan 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, dan
openclaw/plugin-sdk/reply-chunking saat Anda tidak membutuhkan surface payung
yang lebih luas.
Khusus untuk setup:
openclaw/plugin-sdk/setup-runtimemencakup helper setup yang aman untuk runtime:createSetupTranslator, adapter patch setup yang aman diimpor (createPatchedAccountSetupAdapter,createEnvPatchedAccountSetupAdapter,createSetupInputPresenceValidator), output lookup-note,promptResolvedAllowFrom,splitSetupEntries, dan builder proxy setup yang didelegasikanopenclaw/plugin-sdk/setup-runtimemenyertakan seam adapter sadar env untukcreateEnvPatchedAccountSetupAdapteropenclaw/plugin-sdk/channel-setupmencakup builder setup optional-install plus beberapa primitif aman-setup:createOptionalChannelSetupSurface,createOptionalChannelSetupAdapter,
Jika channel Anda mendukung setup atau auth berbasis env dan alur startup/config
generik harus mengetahui nama env tersebut sebelum runtime dimuat, deklarasikan
di manifes Plugin dengan channelEnvVars. Pertahankan envVars runtime channel atau konstanta lokal
hanya untuk salinan yang menghadap operator.
Jika channel Anda dapat muncul di status, channels list, channels status, atau
pemindaian SecretRef sebelum runtime Plugin dimulai, tambahkan openclaw.setupEntry di
package.json. Entrypoint tersebut harus aman diimpor dalam path perintah read-only
dan harus mengembalikan metadata channel, adapter konfigurasi aman-setup, adapter status,
dan metadata target secret channel yang diperlukan untuk ringkasan tersebut. Jangan
memulai klien, listener, atau runtime transport dari entry setup.
Jaga path impor entry channel utama tetap sempit juga. Discovery dapat mengevaluasi
entry dan modul Plugin channel untuk mendaftarkan capability tanpa mengaktifkan
channel. File seperti channel-plugin-api.ts harus mengekspor objek Plugin channel
tanpa mengimpor wizard setup, klien transport, listener socket,
peluncur subprocess, atau modul startup layanan. Letakkan bagian runtime tersebut
di modul yang dimuat dari registerFull(...), setter runtime, atau adapter
capability lazy.
createOptionalChannelSetupWizard, DEFAULT_ACCOUNT_ID,
createTopLevelChannelDmPolicy, setSetupChannelEnabled, dan
splitSetupEntries
- gunakan seam
openclaw/plugin-sdk/setupyang lebih luas hanya saat Anda juga membutuhkan helper setup/config bersama yang lebih berat sepertimoveSingleAccountChannelSectionToDefaultAccount(...)
Jika channel Anda hanya ingin mengiklankan "instal Plugin ini terlebih dahulu" di surface
setup, lebih baik gunakan createOptionalChannelSetupSurface(...). Adapter/wizard
yang dihasilkan fail closed pada penulisan konfigurasi dan finalisasi, dan menggunakan ulang
pesan wajib-instal yang sama di seluruh validasi, finalisasi, dan salinan tautan-docs.
Untuk path channel panas lainnya, lebih baik gunakan helper sempit daripada surface legacy yang lebih luas:
openclaw/plugin-sdk/account-core,openclaw/plugin-sdk/account-id,openclaw/plugin-sdk/account-resolution, danopenclaw/plugin-sdk/account-helpersuntuk konfigurasi multi-akun dan fallback akun defaultopenclaw/plugin-sdk/inbound-envelopedanopenclaw/plugin-sdk/channel-inbounduntuk rute/envelope masuk dan pengkabelan rekam-dan-dispatchopenclaw/plugin-sdk/channel-targetsuntuk helper parsing targetopenclaw/plugin-sdk/outbound-mediauntuk pemuatan media danopenclaw/plugin-sdk/channel-outbounduntuk identitas keluar/delegasi kirim dan perencanaan payloadbuildThreadAwareOutboundSessionRoute(...)dariopenclaw/plugin-sdk/channel-coresaat rute keluar harus mempertahankanreplyToId/threadIdeksplisit atau memulihkan sesi:thread:saat ini setelah kunci sesi dasar masih cocok. Plugin penyedia dapat menimpa presedensi, perilaku sufiks, dan normalisasi id thread saat platform mereka memiliki semantik pengiriman thread native.openclaw/plugin-sdk/thread-bindings-runtimeuntuk siklus hidup pengikatan thread dan registrasi adapteropenclaw/plugin-sdk/agent-media-payloadhanya saat tata letak field payload agen/media legacy masih diperlukanopenclaw/plugin-sdk/telegram-command-configuntuk normalisasi perintah kustom Telegram, validasi duplikat/konflik, dan kontrak konfigurasi perintah yang stabil terhadap fallback
Saluran khusus auth biasanya dapat berhenti di jalur default: core menangani persetujuan dan Plugin hanya mengekspos kemampuan keluar/auth. Saluran persetujuan native seperti Matrix, Slack, Telegram, dan transport chat kustom sebaiknya menggunakan helper native bersama alih-alih membuat sendiri siklus hidup persetujuannya.
Kebijakan mention masuk
Pisahkan penanganan mention masuk dalam dua lapisan:
- pengumpulan bukti milik Plugin
- evaluasi kebijakan bersama
Gunakan openclaw/plugin-sdk/channel-mention-gating untuk keputusan kebijakan mention.
Gunakan openclaw/plugin-sdk/channel-inbound hanya saat Anda memerlukan barrel helper masuk
yang lebih luas.
Cocok untuk logika lokal Plugin:
- deteksi balasan-ke-bot
- deteksi bot-yang-dikutip
- pemeriksaan partisipasi thread
- pengecualian pesan layanan/sistem
- cache native platform yang diperlukan untuk membuktikan partisipasi bot
Cocok untuk helper bersama:
requireMention- hasil mention eksplisit
- allowlist mention implisit
- bypass perintah
- keputusan skip akhir
Alur yang disarankan:
- Hitung fakta mention lokal.
- Teruskan fakta tersebut ke
resolveInboundMentionDecision({ facts, policy }). - Gunakan
decision.effectiveWasMentioned,decision.shouldBypassMention, dandecision.shouldSkipdi gate masuk Anda.
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 mengekspos helper mention bersama yang sama untuk
Plugin saluran bawaan yang sudah bergantung pada injeksi runtime:
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
Jika Anda hanya memerlukan implicitMentionKindWhen dan
resolveInboundMentionDecision, impor dari
openclaw/plugin-sdk/channel-mention-gating untuk menghindari pemuatan helper
runtime masuk yang tidak terkait.
Gunakan resolveInboundMentionDecision({ facts, policy }) untuk gating mention.
Panduan langkah demi langkah
Paket dan manifest
Buat file Plugin standar. Field channel di package.json adalah yang
menjadikan ini Plugin saluran. Untuk permukaan metadata paket lengkap,
lihat Penyiapan dan Konfigurasi Plugin:
{"name": "@myorg/openclaw-acme-chat","version": "1.0.0","type": "module","openclaw": { "extensions": ["./index.ts"], "setupEntry": "./setup-entry.ts", "channel": { "id": "acme-chat", "label": "Acme Chat", "blurb": "Connect OpenClaw to Acme Chat." }}}{"id": "acme-chat","kind": "channel","channels": ["acme-chat"],"name": "Acme Chat","description": "Acme Chat channel plugin","configSchema": { "type": "object", "additionalProperties": false, "properties": {}},"channelConfigs": { "acme-chat": { "schema": { "type": "object", "additionalProperties": false, "properties": { "token": { "type": "string" }, "allowFrom": { "type": "array", "items": { "type": "string" } } } }, "uiHints": { "token": { "label": "Bot token", "sensitive": true } } }}}configSchema memvalidasi plugins.entries.acme-chat.config. Gunakan ini untuk
pengaturan milik Plugin yang bukan konfigurasi akun saluran. channelConfigs
memvalidasi channels.acme-chat dan merupakan sumber cold-path yang digunakan oleh skema
konfigurasi, penyiapan, dan permukaan UI sebelum runtime Plugin dimuat.
Bangun objek Plugin saluran
Antarmuka ChannelPlugin memiliki banyak permukaan adapter opsional. Mulailah dengan
minimum - id dan setup - lalu tambahkan adapter sesuai kebutuhan.
Buat 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); }, }, },});Untuk saluran yang menerima kunci DM level teratas kanonis dan kunci bertingkat legacy, gunakan helper dari plugin-sdk/channel-config-helpers: resolveChannelDmAccess, resolveChannelDmPolicy, resolveChannelDmAllowFrom, dan normalizeChannelDmPolicy menjaga nilai lokal akun tetap lebih dulu daripada nilai root yang diwariskan. Pasangkan resolver yang sama dengan perbaikan doctor melalui normalizeLegacyDmAliases agar runtime dan migrasi membaca kontrak yang sama.
Apa yang dilakukan createChatChannelPlugin untuk Anda
Alih-alih mengimplementasikan antarmuka adapter level rendah secara manual, Anda meneruskan opsi deklaratif dan builder menyusunnya:
| Opsi | Yang dikabelkan |
|---|---|
security.dm |
Resolver keamanan DM berskala dari field konfigurasi |
pairing.text |
Alur pairing DM berbasis teks dengan pertukaran kode |
threading |
Resolver mode reply-to (tetap, berskala akun, atau kustom) |
outbound.attachedResults |
Fungsi kirim yang mengembalikan metadata hasil (ID pesan) |
Anda juga dapat meneruskan objek adapter mentah alih-alih opsi deklaratif jika memerlukan kontrol penuh.
Adapter keluar mentah dapat mendefinisikan fungsi chunker(text, limit, ctx).
ctx.formatting opsional membawa keputusan pemformatan saat pengiriman
seperti maxLinesPerMessage; terapkan sebelum mengirim agar threading balasan
dan batas chunk diselesaikan sekali oleh pengiriman keluar bersama.
Konteks kirim juga menyertakan replyToIdSource (implicit atau explicit)
saat target balasan native telah diselesaikan, sehingga helper payload dapat mempertahankan
tag balasan eksplisit tanpa mengonsumsi slot balasan sekali pakai implisit.
Hubungkan entry point
Buat 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(/* ... */); },});Letakkan deskriptor CLI milik saluran di registerCliMetadata(...) agar OpenClaw
dapat menampilkannya di bantuan root tanpa mengaktifkan runtime saluran penuh,
sementara pemuatan penuh normal tetap mengambil deskriptor yang sama untuk pendaftaran
perintah sebenarnya. Pertahankan registerFull(...) untuk pekerjaan khusus runtime.
Jika registerFull(...) mendaftarkan metode RPC Gateway, gunakan prefiks
khusus plugin. Namespace admin inti (config.*,
exec.approvals.*, wizard.*, update.*) tetap dicadangkan dan selalu
di-resolve ke operator.admin.
defineChannelPluginEntry menangani pemisahan mode pendaftaran secara otomatis. Lihat
Titik Masuk untuk semua
opsi.
Tambahkan entri setup
Buat setup-entry.ts untuk pemuatan ringan selama onboarding:
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";import { acmeChatPlugin } from "./src/channel.js"; export default defineSetupPluginEntry(acmeChatPlugin);OpenClaw memuat ini alih-alih entri penuh saat saluran dinonaktifkan atau belum dikonfigurasi. Ini menghindari penarikan kode runtime yang berat selama alur setup. Lihat Setup dan Konfigurasi untuk detail.
Saluran workspace bawaan yang memisahkan ekspor yang aman untuk setup ke dalam modul
sidecar dapat menggunakan defineBundledChannelSetupEntry(...) dari
openclaw/plugin-sdk/channel-entry-contract saat mereka juga memerlukan
setter runtime eksplisit pada waktu setup.
Tangani pesan masuk
Plugin Anda perlu menerima pesan dari platform dan meneruskannya ke OpenClaw. Pola umumnya adalah Webhook yang memverifikasi permintaan dan mengirimkannya melalui handler masuk saluran Anda:
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; }, });}Uji
Tulis pengujian yang dikolokasikan di 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/Untuk helper pengujian bersama, lihat Pengujian.
Struktur file
<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)Topik lanjutan
Mode balasan tetap, berbasis akun, atau kustom
describeMessageTool dan penemuan tindakan
inferTargetChatType, looksLikeId, reservedLiterals, resolveTarget
TTS, STT, media, subagent melalui api.runtime
Siklus hidup event masuk bersama: ingest, resolve, record, dispatch, finalize
Langkah berikutnya
- Plugin Penyedia - jika plugin Anda juga menyediakan model
- Ikhtisar SDK - referensi impor subpath lengkap
- Pengujian SDK - utilitas pengujian dan pengujian kontrak
- Manifest Plugin - skema manifest lengkap