Plugins
Membangun Plugin saluran
Panduan ini menjelaskan cara membangun Plugin saluran yang menghubungkan OpenClaw ke platform perpesanan. Pada akhirnya, Anda akan memiliki saluran yang berfungsi dengan keamanan DM, pairing, utas balasan, dan perpesanan keluar.
Cara kerja Plugin saluran
Plugin saluran tidak memerlukan alat kirim/edit/reaksi sendiri. OpenClaw mempertahankan satu
alat message bersama di inti. Plugin Anda menangani:
- Konfigurasi - resolusi akun dan wizard penyiapan
- Keamanan - kebijakan DM dan daftar izin
- Pairing - alur persetujuan DM
- Tata bahasa sesi - cara id percakapan spesifik penyedia dipetakan ke chat dasar, id utas, dan fallback induk
- Keluar - mengirim teks, media, dan polling ke platform
- Pengutasan - cara balasan diutas
- Pengetikan Heartbeat - sinyal mengetik/sibuk opsional untuk target pengiriman Heartbeat
Inti menangani alat pesan bersama, penyambungan prompt, bentuk luar kunci sesi,
pencatatan umum :thread:, dan dispatch.
Plugin saluran baru juga harus mengekspos adaptor message dengan
defineChannelMessageAdapter dari openclaw/plugin-sdk/channel-message. Adaptor
mendeklarasikan kemampuan final-send yang tahan lama yang benar-benar didukung transport native
dan mengarahkan pengiriman teks/media ke fungsi transport yang sama seperti adaptor
outbound lama. Deklarasikan kemampuan hanya ketika tes kontrak
membuktikan efek samping native dan receipt yang dikembalikan.
Untuk kontrak API lengkap, contoh, matriks kemampuan, aturan receipt, finalisasi
pratinjau langsung, kebijakan ack penerimaan, tes, dan tabel migrasi, lihat
API pesan saluran.
Jika adaptor outbound yang ada sudah memiliki metode pengiriman dan
metadata kemampuan yang tepat, gunakan createChannelMessageAdapterFromOutbound(...) untuk
menurunkan adaptor message alih-alih menulis bridge lain secara manual.
Pengiriman adaptor harus mengembalikan nilai MessageReceipt. Ketika kode kompatibilitas
masih membutuhkan id lama, turunkan id tersebut dengan listMessageReceiptPlatformIds(...)
atau resolveMessageReceiptPrimaryId(...) alih-alih mempertahankan field
messageIds paralel dalam kode siklus hidup baru.
Saluran yang mendukung pratinjau juga harus mendeklarasikan message.live.capabilities dengan
siklus hidup live persis yang mereka miliki, seperti draftPreview,
previewFinalization, progressUpdates, nativeStreaming, atau
quietFinalization. Saluran 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(...). Pastikan kemampuan tersebut didukung
oleh tes verifyChannelMessageLiveCapabilityAdapterProofs(...) dan
verifyChannelMessageLiveFinalizerProofs(...) agar perilaku pratinjau native,
progres, edit, fallback/retensi, pembersihan, dan receipt tidak bergeser
diam-diam.
Receiver inbound yang menunda acknowledgement platform harus mendeklarasikan
message.receive.defaultAckPolicy dan supportedAckPolicies alih-alih menyembunyikan
waktu ack di state lokal monitor. Cakup setiap kebijakan yang dideklarasikan dengan
verifyChannelMessageReceiveAckPolicyAdapterProofs(...).
Helper balasan/turn lama seperti createChannelTurnReplyPipeline,
dispatchInboundReplyWithBase, dan recordInboundSessionAndDispatchReply
tetap tersedia untuk dispatcher kompatibilitas. Jangan gunakan nama tersebut untuk kode
saluran baru; Plugin baru harus memulai dengan adaptor message, receipt, dan
helper siklus hidup penerimaan/pengiriman di openclaw/plugin-sdk/channel-message.
Jika saluran Anda mendukung indikator mengetik di luar balasan inbound, ekspos
heartbeat.sendTyping(...) pada Plugin saluran. Inti memanggilnya dengan
target pengiriman Heartbeat yang telah diresolusi sebelum model Heartbeat dijalankan dan
menggunakan siklus hidup keepalive/pembersihan pengetikan bersama. Tambahkan heartbeat.clearTyping(...)
ketika platform membutuhkan sinyal berhenti eksplisit.
Jika saluran Anda menambahkan parameter alat pesan yang membawa sumber media, ekspos nama
parameter tersebut melalui describeMessageTool(...).mediaSourceParams. Inti menggunakan
daftar eksplisit tersebut untuk normalisasi jalur sandbox dan kebijakan akses media
keluar, sehingga Plugin tidak memerlukan kasus khusus inti bersama untuk parameter
avatar, lampiran, atau gambar sampul spesifik penyedia.
Lebih disarankan mengembalikan peta berbasis kunci aksi seperti
{ "set-profile": ["avatarUrl", "avatarPath"] } agar aksi yang tidak terkait tidak
mewarisi argumen media milik aksi lain. Array datar tetap berfungsi untuk parameter yang
memang sengaja dibagikan di semua aksi yang diekspos.
Jika saluran Anda membutuhkan pembentukan spesifik penyedia untuk message(action="send"),
lebih disarankan menggunakan actions.prepareSendPayload(...). Letakkan kartu native, blok, embed, atau
data tahan lama lainnya di bawah payload.channelData.<channel> dan biarkan inti melakukan
pengiriman sebenarnya melalui adaptor outbound/message. Gunakan
actions.handleAction(...) untuk pengiriman hanya sebagai fallback kompatibilitas untuk
payload yang tidak dapat diserialisasi dan dicoba ulang.
Jika platform Anda menyimpan cakupan ekstra 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 paling sempit ke percakapan paling luas/dasar.
Gunakan openclaw/plugin-sdk/channel-route ketika kode Plugin perlu menormalkan
field seperti 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 inti, sehingga Plugin sebaiknya
menggunakannya alih-alih perbandingan ad hoc String(threadId).
Plugin dengan tata bahasa target spesifik penyedia dapat menginjeksikan parser mereka ke
resolveChannelRouteTargetWithParser(...) dan tetap mendapatkan bentuk target rute yang sama
serta semantik fallback utas yang digunakan inti.
Plugin bawaan yang membutuhkan parsing yang sama sebelum registry saluran melakukan boot
juga dapat mengekspos file tingkat atas session-key-api.ts dengan ekspor
resolveSessionConversation(...) yang sesuai. Inti menggunakan permukaan aman-bootstrap tersebut
hanya ketika registry Plugin runtime belum tersedia.
messaging.resolveParentConversationCandidates(...) tetap tersedia sebagai
fallback kompatibilitas lama ketika Plugin hanya membutuhkan fallback induk di atas
id umum/raw. Jika kedua hook ada, inti menggunakan
resolveSessionConversation(...).parentConversationCandidates terlebih dahulu dan hanya
fallback ke resolveParentConversationCandidates(...) ketika hook kanonis
menghilangkannya.
Persetujuan dan kemampuan saluran
Sebagian besar Plugin saluran tidak memerlukan kode khusus persetujuan.
- Inti memiliki
/approvedalam chat yang sama, payload tombol persetujuan bersama, dan pengiriman fallback generik. - Lebih baik gunakan satu objek
approvalCapabilitypada Plugin kanal ketika kanal memerlukan perilaku khusus persetujuan. ChannelPlugin.approvalsdihapus. Letakkan fakta pengiriman/native/render/auth persetujuan padaapprovalCapability.plugin.authhanya untuk login/logout; inti tidak lagi membaca hook auth persetujuan dari objek tersebut.approvalCapability.authorizeActorActiondanapprovalCapability.getActionAvailabilityStateadalah titik sambungan auth persetujuan kanonis.- Gunakan
approvalCapability.getActionAvailabilityStateuntuk ketersediaan auth persetujuan dalam chat yang sama. - Jika kanal Anda mengekspos persetujuan exec native, gunakan
approvalCapability.getExecInitiatingSurfaceStateuntuk status permukaan pemulai/klien native ketika berbeda dari auth persetujuan dalam chat yang sama. Inti menggunakan hook khusus exec itu untuk membedakanenabledvsdisabled, menentukan apakah kanal pemulai mendukung persetujuan exec native, dan menyertakan kanal dalam panduan fallback klien native.createApproverRestrictedNativeApprovalCapability(...)mengisi ini untuk kasus umum. - Gunakan
outbound.shouldSuppressLocalPayloadPromptatauoutbound.beforeDeliverPayloaduntuk perilaku siklus hidup payload khusus kanal seperti menyembunyikan prompt persetujuan lokal duplikat atau mengirim indikator mengetik sebelum pengiriman. - Gunakan
approvalCapability.deliveryhanya untuk routing persetujuan native atau penekanan fallback. - Gunakan
approvalCapability.nativeRuntimeuntuk fakta persetujuan native milik kanal. Pertahankan agar tetap lazy pada entrypoint kanal panas dengancreateLazyChannelApprovalNativeRuntimeAdapter(...), yang dapat mengimpor modul runtime Anda sesuai kebutuhan sambil tetap memungkinkan inti menyusun siklus hidup persetujuan. - Gunakan
approvalCapability.renderhanya ketika kanal benar-benar memerlukan payload persetujuan kustom, bukan renderer bersama. - Gunakan
approvalCapability.describeExecApprovalSetupketika kanal ingin balasan jalur nonaktif menjelaskan knob konfigurasi persis yang diperlukan untuk mengaktifkan persetujuan exec native. Hook menerima{ channel, channelLabel, accountId }; kanal akun bernama harus merender path berlingkup akun sepertichannels.<channel>.accounts.<id>.execApprovals.*, bukan default level teratas. - Jika kanal dapat menyimpulkan identitas DM yang stabil dan mirip pemilik dari konfigurasi yang ada, gunakan
createResolvedApproverActionAuthAdapterdariopenclaw/plugin-sdk/approval-runtimeuntuk membatasi/approvedalam chat yang sama tanpa menambahkan logika inti khusus persetujuan. - Jika kanal memerlukan pengiriman persetujuan native, jaga agar kode kanal berfokus pada normalisasi target plus fakta transport/presentasi. Gunakan
createChannelExecApprovalProfile,createChannelNativeOriginTargetResolver,createChannelApproverDmTargetResolver, dancreateApproverRestrictedNativeApprovalCapabilitydariopenclaw/plugin-sdk/approval-runtime. Letakkan fakta khusus kanal di balikapprovalCapability.nativeRuntime, idealnya melaluicreateChannelApprovalNativeRuntimeAdapter(...)ataucreateLazyChannelApprovalNativeRuntimeAdapter(...), sehingga inti dapat menyusun handler dan memiliki pemfilteran permintaan, routing, dedupe, kedaluwarsa, langganan Gateway, dan pemberitahuan dirutekan-ke-tempat-lain.nativeRuntimedipecah menjadi beberapa titik sambungan yang lebih kecil: createChannelNativeOriginTargetResolvermenggunakan pencocok rute kanal bersama secara default untuk target{ to, accountId, threadId }. TeruskantargetsMatchhanya ketika kanal memiliki aturan ekuivalensi khusus penyedia, seperti pencocokan prefiks timestamp Slack.- Teruskan
normalizeTargetForMatchkecreateChannelNativeOriginTargetResolverketika kanal perlu mengkanoniskan id penyedia sebelum pencocok rute default atau callbacktargetsMatchkustom berjalan, sambil mempertahankan target asli untuk pengiriman. GunakannormalizeTargethanya ketika target pengiriman yang terselesaikan itu sendiri harus dikanoniskan. availability- apakah akun dikonfigurasi dan apakah permintaan harus ditanganipresentation- petakan model tampilan persetujuan bersama menjadi payload native tertunda/terselesaikan/kedaluwarsa atau tindakan finaltransport- siapkan target plus kirim/perbarui/hapus pesan persetujuan nativeinteractions- hook opsional bind/unbind/clear-action untuk tombol atau reaksi nativeobserve- hook diagnostik pengiriman opsional- Jika kanal memerlukan objek milik runtime seperti klien, token, aplikasi Bolt, atau penerima webhook, daftarkan melalui
openclaw/plugin-sdk/channel-runtime-context. Registry runtime-context generik memungkinkan inti mem-bootstrap handler berbasis kapabilitas dari status startup kanal tanpa menambahkan perekat wrapper khusus persetujuan. - Gunakan
createChannelApprovalHandlerataucreateChannelNativeApprovalRuntimelevel lebih rendah hanya ketika titik sambungan berbasis kapabilitas belum cukup ekspresif. - Kanal persetujuan native harus merutekan
accountIddanapprovalKindmelalui helper tersebut.accountIdmenjaga kebijakan persetujuan multi-akun tetap berlingkup ke akun bot yang benar, danapprovalKindmenjaga perilaku persetujuan exec vs Plugin tetap tersedia bagi kanal tanpa cabang hardcoded di inti. - Inti sekarang juga memiliki pemberitahuan reroute persetujuan. Plugin kanal tidak boleh mengirim pesan tindak lanjut mereka sendiri seperti "persetujuan dikirim ke DM / kanal lain" dari
createChannelNativeApprovalRuntime; sebagai gantinya, ekspos routing asal + DM pemberi persetujuan yang akurat melalui helper kapabilitas persetujuan bersama dan biarkan inti mengagregasi pengiriman aktual sebelum memposting pemberitahuan apa pun kembali ke chat pemulai. - Pertahankan jenis id persetujuan yang dikirimkan dari awal sampai akhir. Klien native tidak boleh menebak atau menulis ulang routing persetujuan exec vs Plugin dari status lokal kanal.
- Jenis persetujuan berbeda dapat secara sengaja mengekspos permukaan native berbeda.
Contoh bundled saat ini:
- Slack mempertahankan routing persetujuan native tersedia untuk id exec dan Plugin.
- Matrix mempertahankan routing DM/kanal native dan UX reaksi yang sama untuk persetujuan exec dan Plugin, sambil tetap memungkinkan auth berbeda menurut jenis persetujuan.
createApproverRestrictedNativeApprovalAdaptermasih ada sebagai wrapper kompatibilitas, tetapi kode baru sebaiknya memilih builder kapabilitas dan mengeksposapprovalCapabilitypada Plugin.
Untuk entrypoint kanal panas, pilih subpath runtime yang lebih sempit ketika Anda hanya memerlukan 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 juga, pilih 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, dan
openclaw/plugin-sdk/reply-chunking ketika Anda tidak memerlukan permukaan payung yang lebih luas.
Khusus untuk setup:
openclaw/plugin-sdk/setup-runtimemencakup helper setup yang aman untuk runtime: adapter patch setup yang aman diimpor (createPatchedAccountSetupAdapter,createEnvPatchedAccountSetupAdapter,createSetupInputPresenceValidator), output catatan lookup,promptResolvedAllowFrom,splitSetupEntries, dan builder setup-proxy terdelegasiopenclaw/plugin-sdk/setup-adapter-runtimeadalah titik sambungan adapter sempit yang sadar env untukcreateEnvPatchedAccountSetupAdapteropenclaw/plugin-sdk/channel-setupmencakup builder setup optional-install plus beberapa primitif yang aman untuk setup:createOptionalChannelSetupSurface,createOptionalChannelSetupAdapter,
Jika kanal Anda mendukung setup atau auth berbasis env dan alur startup/config
generik harus mengetahui nama env tersebut sebelum runtime dimuat, deklarasikan di
manifest Plugin dengan channelEnvVars. Simpan envVars runtime kanal atau konstanta lokal
hanya untuk salinan yang ditujukan bagi operator.
Jika kanal Anda dapat muncul dalam status, channels list, channels status, atau
pemindaian SecretRef sebelum runtime Plugin dimulai, tambahkan openclaw.setupEntry di
package.json. Entrypoint tersebut harus aman diimpor pada path perintah read-only
dan harus mengembalikan metadata kanal, adapter config yang aman untuk setup, adapter status,
dan metadata target secret kanal yang diperlukan untuk ringkasan tersebut. Jangan
memulai klien, listener, atau runtime transport dari entry setup.
Pertahankan path impor entri kanal utama tetap sempit juga. Discovery dapat mengevaluasi
entri dan modul Plugin kanal untuk mendaftarkan kapabilitas tanpa mengaktifkan
kanal. File seperti channel-plugin-api.ts harus mengekspor objek Plugin kanal
tanpa mengimpor wizard setup, klien transport, listener socket,
peluncur subprocess, atau modul startup layanan. Letakkan bagian runtime tersebut
dalam modul yang dimuat dari registerFull(...), setter runtime, atau adapter
kapabilitas lazy.
createOptionalChannelSetupWizard, DEFAULT_ACCOUNT_ID,
createTopLevelChannelDmPolicy, setSetupChannelEnabled, dan
splitSetupEntries
- gunakan titik sambungan
openclaw/plugin-sdk/setupyang lebih luas hanya ketika Anda juga memerlukan helper setup/config bersama yang lebih berat sepertimoveSingleAccountChannelSectionToDefaultAccount(...)
Jika kanal Anda hanya ingin mengiklankan "instal Plugin ini terlebih dahulu" di permukaan
setup, pilih createOptionalChannelSetupSurface(...). Adapter/wizard yang dihasilkan
gagal tertutup pada penulisan config dan finalisasi, serta menggunakan kembali
pesan install-required yang sama di seluruh validasi, finalisasi, dan salinan tautan docs.
Untuk path kanal panas lainnya, pilih helper sempit dibandingkan permukaan legacy yang lebih luas:
openclaw/plugin-sdk/account-core,openclaw/plugin-sdk/account-id,openclaw/plugin-sdk/account-resolution, danopenclaw/plugin-sdk/account-helpersuntuk config multi-akun dan fallback akun defaultopenclaw/plugin-sdk/inbound-envelopedanopenclaw/plugin-sdk/inbound-reply-dispatchuntuk rute/envelope masuk dan wiring catat-dan-dispatchopenclaw/plugin-sdk/messaging-targetsuntuk parsing/pencocokan targetopenclaw/plugin-sdk/outbound-mediadanopenclaw/plugin-sdk/outbound-runtimeuntuk pemuatan media plus delegasi identitas/kirim outbound dan perencanaan payloadbuildThreadAwareOutboundSessionRoute(...)dariopenclaw/plugin-sdk/channel-coreketika rute outbound 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 ketika platform mereka memiliki semantik pengiriman thread native.openclaw/plugin-sdk/thread-bindings-runtimeuntuk siklus hidup thread-binding dan pendaftaran adapteropenclaw/plugin-sdk/agent-media-payloadhanya ketika tata letak field payload agent/media legacy masih diperlukanopenclaw/plugin-sdk/telegram-command-configuntuk normalisasi custom-command Telegram, validasi duplikat/konflik, dan kontrak config perintah yang stabil untuk fallback
Kanal auth-only biasanya dapat berhenti di path default: inti menangani persetujuan dan Plugin hanya mengekspos kapabilitas outbound/auth. Kanal persetujuan native seperti Matrix, Slack, Telegram, dan transport chat kustom harus menggunakan helper native bersama, bukan membuat siklus hidup persetujuan sendiri.
Kebijakan mention masuk
Pertahankan penanganan mention masuk terbagi 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 ketika Anda memerlukan barrel helper inbound
yang lebih luas.
Cocok untuk logika lokal Plugin:
- deteksi reply-to-bot
- deteksi quoted-bot
- pemeriksaan partisipasi thread
- pengecualian pesan layanan/sistem
- cache native platform yang diperlukan untuk membuktikan partisipasi bot
Cocok untuk helper bersama:
requireMention- hasil penyebutan eksplisit
- allowlist penyebutan implisit
- bypass perintah
- keputusan lewati final
Alur yang disarankan:
- Hitung fakta penyebutan lokal.
- Teruskan fakta tersebut ke
resolveInboundMentionDecision({ facts, policy }). - Gunakan
decision.effectiveWasMentioned,decision.shouldBypassMention, dandecision.shouldSkipdi gate inbound 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 penyebutan bersama yang sama untuk
Plugin kanal bawaan yang sudah bergantung pada injeksi runtime:
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
Jika Anda hanya membutuhkan implicitMentionKindWhen dan
resolveInboundMentionDecision, impor dari
openclaw/plugin-sdk/channel-mention-gating untuk menghindari pemuatan helper
runtime inbound yang tidak terkait.
Helper resolveMentionGating* yang lebih lama tetap ada di
openclaw/plugin-sdk/channel-inbound hanya sebagai ekspor kompatibilitas. Kode baru
sebaiknya menggunakan resolveInboundMentionDecision({ facts, policy }).
Panduan langkah demi langkah
Paket dan manifes
Buat file Plugin standar. Bidang channel di package.json adalah
yang membuat ini menjadi Plugin kanal. Untuk surface 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 kanal. channelConfigs
memvalidasi channels.acme-chat dan merupakan sumber cold-path yang digunakan oleh skema
konfigurasi, penyiapan, dan surface UI sebelum runtime Plugin dimuat.
Bangun objek Plugin kanal
Antarmuka ChannelPlugin memiliki banyak surface 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 kanal yang menerima kunci DM tingkat atas kanonis dan kunci bersarang legacy, gunakan helper dari plugin-sdk/channel-config-helpers: resolveChannelDmAccess, resolveChannelDmPolicy, resolveChannelDmAllowFrom, dan normalizeChannelDmPolicy mempertahankan nilai lokal akun di depan nilai root yang diwariskan. Pasangkan resolver yang sama dengan perbaikan doctor melalui normalizeLegacyDmAliases agar runtime dan migrasi membaca kontrak yang sama.
Yang dilakukan createChatChannelPlugin untuk Anda
Alih-alih mengimplementasikan antarmuka adapter tingkat rendah secara manual, Anda meneruskan opsi deklaratif dan builder menyusunnya:
| Opsi | Yang dihubungkannya |
|---|---|
security.dm |
Resolver keamanan DM tercakup dari bidang konfigurasi |
pairing.text |
Alur pairing DM berbasis teks dengan pertukaran kode |
threading |
Resolver mode reply-to (tetap, tercakup 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 membutuhkan kendali penuh.
Adapter outbound 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 outbound bersama.
Konteks kirim juga menyertakan replyToIdSource (implicit atau explicit)
ketika target balasan native telah diselesaikan, sehingga helper payload dapat mempertahankan
tag balasan eksplisit tanpa memakai 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 kanal di registerCliMetadata(...) agar OpenClaw
dapat menampilkannya di bantuan root tanpa mengaktifkan runtime kanal penuh,
sementara pemuatan penuh normal tetap mengambil deskriptor yang sama untuk pendaftaran
perintah nyata. Simpan 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
diselesaikan ke operator.admin.
defineChannelPluginEntry menangani pemisahan mode pendaftaran secara otomatis. Lihat
Entry Point untuk semua
opsi.
Tambahkan entry penyiapan
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 entry penuh saat kanal dinonaktifkan atau belum dikonfigurasi. Ini menghindari penarikan kode runtime berat selama alur penyiapan. Lihat Penyiapan dan Konfigurasi untuk detail.
Kanal workspace bawaan yang memisahkan ekspor aman-penyiapan ke modul sidecar
dapat menggunakan defineBundledChannelSetupEntry(...) dari
openclaw/plugin-sdk/channel-entry-contract ketika juga membutuhkan
setter runtime waktu-penyiapan eksplisit.
Tangani pesan inbound
Plugin Anda perlu menerima pesan dari platform dan meneruskannya ke OpenClaw. Pola umumnya adalah Webhook yang memverifikasi permintaan dan mendispatchnya melalui handler inbound kanal 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;
},
});
}
Test
Tulis pengujian yang ditempatkan bersama 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, bercakupan akun, atau kustom
describeMessageTool dan penemuan aksi
inferTargetChatType, looksLikeId, resolveTarget
TTS, STT, media, subagen melalui api.runtime
Siklus hidup giliran 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
- Manifes Plugin - skema manifes lengkap