Building plugins
Создание Plugin для каналов
Это руководство показывает, как создать канальный Plugin, который подключает OpenClaw к платформе обмена сообщениями. К концу у вас будет рабочий канал с безопасностью личных сообщений, сопряжением, цепочками ответов и исходящими сообщениями.
Как работают канальные Plugins
Канальным Plugins не нужны собственные инструменты отправки, редактирования или реакций. OpenClaw хранит один
общий инструмент message в core. Ваш Plugin отвечает за:
- Конфигурация - разрешение аккаунта и мастер настройки
- Безопасность - политика личных сообщений и списки разрешений
- Сопряжение - поток подтверждения в личных сообщениях
- Грамматика сессий - как идентификаторы бесед, специфичные для провайдера, сопоставляются с базовыми чатами, идентификаторами цепочек и родительскими fallback-вариантами
- Исходящие сообщения - отправка текста, медиа и опросов на платформу
- Цепочки - как ответы объединяются в цепочки
- Heartbeat typing - необязательные индикаторы набора/занятости для целей доставки Heartbeat
Core отвечает за общий инструмент сообщений, подключение prompt, внешнюю форму ключа сессии,
общий учет :thread: и диспетчеризацию.
Новые канальные Plugins также должны предоставлять адаптер message с помощью
defineChannelMessageAdapter из openclaw/plugin-sdk/channel-outbound. Адаптер
объявляет, какие устойчивые возможности финальной отправки фактически поддерживает нативный транспорт,
и направляет отправку текста/медиа в те же транспортные функции, что и устаревший адаптер
outbound. Объявляйте возможность только тогда, когда contract test
доказывает нативный побочный эффект и возвращенную квитанцию.
Полный контракт API, примеры, матрицу возможностей, правила квитанций, финализацию live preview,
политику подтверждений получения, тесты и таблицу миграции см. в
API исходящих сообщений канала.
Если существующий адаптер outbound уже имеет нужные методы отправки и
метаданные возможностей, используйте createChannelMessageAdapterFromOutbound(...), чтобы
получить адаптер message вместо ручного написания еще одного моста.
Отправки адаптера должны возвращать значения MessageReceipt. Когда коду совместимости
по-прежнему нужны устаревшие идентификаторы, получайте их через listMessageReceiptPlatformIds(...)
или resolveMessageReceiptPrimaryId(...), а не храните параллельные
поля messageIds в новом коде жизненного цикла.
Каналы с поддержкой preview также должны объявлять message.live.capabilities с
точным live-жизненным циклом, которым они владеют, например draftPreview,
previewFinalization, progressUpdates, nativeStreaming или
quietFinalization. Каналы, которые финализируют черновой preview на месте, также должны
объявлять message.live.finalizer.capabilities, например finalEdit,
normalFallback, discardPending, previewReceipt и
retainOnAmbiguousFailure, и направлять логику runtime через
defineFinalizableLivePreviewAdapter(...) плюс
deliverWithFinalizableLivePreviewAdapter(...). Подкрепляйте эти возможности
тестами verifyChannelMessageLiveCapabilityAdapterProofs(...) и
verifyChannelMessageLiveFinalizerProofs(...), чтобы поведение нативного preview,
progress, edit, fallback/retention, cleanup и receipt не могло незаметно разойтись.
Входящие приемники, которые откладывают подтверждения платформы, должны объявлять
message.receive.defaultAckPolicy и supportedAckPolicies, а не скрывать
тайминг ack в локальном состоянии монитора. Покройте каждую объявленную политику
с помощью verifyChannelMessageReceiveAckPolicyAdapterProofs(...).
Устаревшие помощники ответов, такие как createChannelTurnReplyPipeline,
dispatchInboundReplyWithBase и recordInboundSessionAndDispatchReply,
остаются доступными для диспетчеров совместимости. Не используйте эти имена в новом
коде каналов; новые Plugins должны начинать с адаптера message, квитанций и
помощников жизненного цикла receive/send из openclaw/plugin-sdk/channel-outbound.
Каналы, мигрирующие входящую авторизацию, могут использовать экспериментальный
подпуть openclaw/plugin-sdk/channel-ingress-runtime из runtime-путей получения.
Подпуть оставляет поиск платформы и побочные эффекты в Plugin, при этом
разделяя разрешение состояния списков разрешений, решения route/sender/command/event/activation,
редактированную диагностику и сопоставление допуска turn. Держите нормализацию
идентичности Plugin в дескрипторе, который вы передаете resolver; не
сериализуйте сырые значения совпадений из разрешенного состояния или решения. См.
API входящих сообщений канала для дизайна API,
границы владения и ожиданий к тестам.
Если ваш канал поддерживает индикаторы набора вне входящих ответов, предоставьте
heartbeat.sendTyping(...) в канальном Plugin. Core вызывает его с
разрешенной целью доставки Heartbeat до начала модельного запуска Heartbeat и
использует общий жизненный цикл keepalive/cleanup для индикатора набора. Добавьте heartbeat.clearTyping(...),
когда платформе нужен явный сигнал остановки.
Если ваш канал добавляет параметры инструмента сообщений, которые несут источники медиа, предоставьте эти
имена параметров через describeMessageTool(...).mediaSourceParams. Core использует
этот явный список для нормализации путей sandbox и политики доступа к исходящим медиа,
поэтому Plugins не нужны специальные случаи в shared-core для специфичных для провайдера
параметров avatar, attachment или cover-image.
Предпочитайте возвращать map по ключам действий, например
{ "set-profile": ["avatarUrl", "avatarPath"] }, чтобы несвязанные действия не
наследовали медиа-аргументы другого действия. Плоский массив по-прежнему работает для параметров, которые
намеренно общие для каждого предоставленного действия.
Каналы, которым нужно предоставить временный публичный URL для получения медиа на стороне платформы,
могут использовать createHostedOutboundMediaStore(...) из
openclaw/plugin-sdk/outbound-media с хранилищами состояния Plugin. Держите
разбор маршрутов платформы и проверку токенов в канальном Plugin; общий помощник
отвечает только за загрузку медиа, метаданные истечения срока действия, строки фрагментов и очистку.
Если вашему каналу нужно специфичное для провайдера формирование для message(action="send"),
предпочитайте actions.prepareSendPayload(...). Помещайте нативные cards, blocks, embeds или
другие устойчивые данные в payload.channelData.<channel> и позвольте core выполнить
фактическую отправку через адаптер outbound/message. Используйте
actions.handleAction(...) для отправки только как fallback совместимости для
payloads, которые нельзя сериализовать и повторить.
Если ваша платформа хранит дополнительную область внутри идентификаторов бесед, оставьте этот разбор
в Plugin с messaging.resolveSessionConversation(...). Это
канонический hook для сопоставления rawId с базовым идентификатором беседы, необязательным идентификатором цепочки,
явным baseConversationId и любыми parentConversationCandidates.
Когда вы возвращаете parentConversationCandidates, держите их упорядоченными от
самого узкого родителя к самой широкой/базовой беседе.
Используйте openclaw/plugin-sdk/channel-route, когда коду Plugin нужно нормализовать
поля, похожие на маршруты, сравнить дочернюю цепочку с ее родительским маршрутом или построить
стабильный ключ дедупликации из { channel, to, accountId, threadId }. Помощник
нормализует числовые идентификаторы цепочек так же, как это делает core, поэтому Plugins должны предпочитать
его ad hoc-сравнениям String(threadId).
Plugins с целевой грамматикой, специфичной для провайдера, должны предоставлять
messaging.resolveOutboundSessionRoute(...), чтобы core получал нативную для провайдера
идентичность сессии и цепочки без parser shims.
Bundled Plugins, которым нужен тот же разбор до запуска реестра каналов,
также могут предоставлять файл верхнего уровня session-key-api.ts с соответствующим
экспортом resolveSessionConversation(...). Core использует эту bootstrap-safe поверхность
только когда runtime-реестр Plugins еще недоступен.
messaging.resolveParentConversationCandidates(...) остается доступным как
устаревший fallback совместимости, когда Plugin нужны только родительские fallback-варианты поверх
общего/сырого идентификатора. Если существуют оба hook, core сначала использует
resolveSessionConversation(...).parentConversationCandidates и переходит к
resolveParentConversationCandidates(...) только когда канонический hook
их опускает.
Подтверждения и возможности каналов
Большинству канальных Plugins не нужен код, специфичный для подтверждений.
- Ядро владеет
/approveв том же чате, общими payload утверждений для кнопок и универсальной fallback-доставкой. - Предпочитайте один объект
approvalCapabilityв канальном Plugin, когда каналу требуется поведение, специфичное для утверждений. ChannelPlugin.approvalsудален. Размещайте факты доставки/нативного отображения/render/auth для утверждений вapprovalCapability.plugin.authпредназначен только для входа/выхода; ядро больше не читает auth-хуки утверждений из этого объекта.approvalCapability.authorizeActorActionиapprovalCapability.getActionAvailabilityStateявляются каноничным seam для auth утверждений.- Используйте
approvalCapability.getActionAvailabilityStateдля доступности auth утверждений в том же чате. - Если ваш канал предоставляет нативные утверждения exec, используйте
approvalCapability.getExecInitiatingSurfaceStateдля состояния инициирующей поверхности/нативного клиента, когда оно отличается от auth утверждений в том же чате. Ядро использует этот exec-специфичный хук, чтобы различатьenabledиdisabled, решать, поддерживает ли инициирующий канал нативные утверждения exec, и включать канал в подсказки fallback для нативного клиента.createApproverRestrictedNativeApprovalCapability(...)заполняет это для обычного случая. - Используйте
outbound.shouldSuppressLocalPayloadPromptилиoutbound.beforeDeliverPayloadдля поведения жизненного цикла payload, специфичного для канала, например скрытия дублирующихся локальных запросов утверждения или отправки индикаторов набора перед доставкой. - Используйте
approvalCapability.deliveryтолько для нативной маршрутизации утверждений или подавления fallback. - Используйте
approvalCapability.nativeRuntimeдля принадлежащих каналу нативных фактов утверждений. Оставляйте его ленивым на горячих entrypoint канала с помощьюcreateLazyChannelApprovalNativeRuntimeAdapter(...), который может импортировать ваш runtime-модуль по требованию, при этом позволяя ядру собирать жизненный цикл утверждения. - Используйте
approvalCapability.renderтолько когда каналу действительно нужны пользовательские payload утверждений вместо общего renderer. - Используйте
approvalCapability.describeExecApprovalSetup, когда канал хочет, чтобы ответ для отключенного пути объяснял точные параметры конфигурации, необходимые для включения нативных утверждений exec. Хук получает{ channel, channelLabel, accountId }; каналы с именованными аккаунтами должны отображать пути в области аккаунта, напримерchannels.<channel>.accounts.<id>.execApprovals.*, вместо верхнеуровневых значений по умолчанию. - Если канал может вывести стабильные DM-идентичности, похожие на владельцев, из существующей конфигурации, используйте
createResolvedApproverActionAuthAdapterизopenclaw/plugin-sdk/approval-runtime, чтобы ограничить/approveв том же чате без добавления логики ядра, специфичной для утверждений. - Если пользовательский auth утверждений намеренно разрешает только same-chat fallback, возвращайте
markImplicitSameChatApprovalAuthorization({ authorized: true })изopenclaw/plugin-sdk/approval-auth-runtime; иначе ядро трактует результат как явную авторизацию утверждающего. - Если принадлежащий каналу нативный callback напрямую разрешает утверждения, используйте
isImplicitSameChatApprovalAuthorization(...)перед разрешением, чтобы неявный fallback все равно проходил через обычную авторизацию actor канала. - Если каналу нужна нативная доставка утверждений, держите код канала сфокусированным на нормализации target и фактах транспорта/представления. Используйте
createChannelExecApprovalProfile,createChannelNativeOriginTargetResolver,createChannelApproverDmTargetResolverиcreateApproverRestrictedNativeApprovalCapabilityизopenclaw/plugin-sdk/approval-runtime. Помещайте факты, специфичные для канала, заapprovalCapability.nativeRuntime, в идеале черезcreateChannelApprovalNativeRuntimeAdapter(...)илиcreateLazyChannelApprovalNativeRuntimeAdapter(...), чтобы ядро могло собрать handler и владеть фильтрацией запросов, маршрутизацией, дедупликацией, истечением срока, подпиской Gateway и уведомлениями о маршрутизации в другое место.nativeRuntimeразделен на несколько меньших seams: - Используйте
createNativeApprovalChannelRouteGatesизopenclaw/plugin-sdk/approval-native-runtime, когда канал поддерживает и нативную доставку из session-origin, и явные цели пересылки утверждений. Helper централизует выбор конфигурации утверждений, обработкуmode, фильтры agent/session, привязку аккаунта, сопоставление session-target и сопоставление списка targets, при этом вызывающие стороны по-прежнему владеют id канала, режимом пересылки по умолчанию, lookup аккаунта, проверкой включенного транспорта, нормализацией target и разрешением turn-source target. Не используйте его для создания принадлежащих ядру политик канала по умолчанию; передавайте документированный режим по умолчанию канала явно. createChannelNativeOriginTargetResolverпо умолчанию использует общий matcher маршрутов канала для targets{ to, accountId, threadId }. ПередавайтеtargetsMatchтолько когда у канала есть правила эквивалентности, специфичные для provider, например сопоставление префикса timestamp в Slack.- Передавайте
normalizeTargetForMatchвcreateChannelNativeOriginTargetResolver, когда каналу нужно канонизировать provider ids перед запуском стандартного matcher маршрутов или пользовательского callbacktargetsMatch, сохраняя исходный target для доставки. ИспользуйтеnormalizeTargetтолько когда сам разрешенный target доставки должен быть канонизирован. availability- настроен ли аккаунт и должен ли запрос обрабатыватьсяpresentation- отображение общей view model утверждения в ожидающие/разрешенные/истекшие нативные payload или финальные действияtransport- подготовка targets и отправка/обновление/удаление нативных сообщений утвержденияinteractions- необязательные хуки bind/unbind/clear-action для нативных кнопок или реакций, плюс необязательный хукcancelDelivered. РеализуйтеcancelDelivered, когдаdeliverPendingрегистрирует внутрипроцессное или постоянное состояние (например, хранилище target реакций), чтобы это состояние можно было освободить, если остановка handler отменяет доставку до запускаbindPendingили когдаbindPendingне возвращает handleobserve- необязательные хуки диагностики доставки- Если каналу нужны runtime-объекты, такие как client, token, Bolt app или webhook receiver, регистрируйте их через
openclaw/plugin-sdk/channel-runtime-context. Универсальный registry runtime-context позволяет ядру bootstrap-ить handlers на основе capabilities из startup-состояния канала без добавления wrapper-glue, специфичного для утверждений. - Обращайтесь к более низкоуровневым
createChannelApprovalHandlerилиcreateChannelNativeApprovalRuntimeтолько когда seam на основе capability пока недостаточно выразителен. - Нативные каналы утверждений должны маршрутизировать и
accountId, иapprovalKindчерез эти helpers.accountIdудерживает политику утверждений для нескольких аккаунтов в области правильного bot account, аapprovalKindсохраняет доступность поведения утверждений exec и Plugin для канала без захардкоженных ветвлений в ядре. - Теперь ядро также владеет уведомлениями о перемаршрутизации утверждений. Канальные Plugins не должны отправлять собственные follow-up сообщения "утверждение ушло в DM / другой канал" из
createChannelNativeApprovalRuntime; вместо этого раскрывайте точную маршрутизацию origin + approver-DM через общие helpers capability утверждений и позволяйте ядру агрегировать фактические доставки перед публикацией любого уведомления обратно в инициирующий чат. - Сохраняйте kind доставленного approval id сквозным образом. Нативные клиенты не должны угадывать или переписывать маршрутизацию утверждений exec и Plugin из локального состояния канала.
- Разные kinds утверждений могут намеренно предоставлять разные нативные поверхности.
Текущие встроенные примеры:
- Slack сохраняет нативную маршрутизацию утверждений доступной и для exec, и для Plugin ids.
- Matrix сохраняет одинаковую нативную маршрутизацию DM/канала и UX реакций для утверждений exec и Plugin, при этом все еще позволяя auth отличаться по kind утверждения.
createApproverRestrictedNativeApprovalAdapterвсе еще существует как wrapper совместимости, но новый код должен предпочитать builder capability и раскрыватьapprovalCapabilityв Plugin.
Для горячих entrypoint канала предпочитайте более узкие runtime subpaths, когда вам нужна только одна часть этого семейства:
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
Аналогично, предпочитайте 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 и
openclaw/plugin-sdk/reply-chunking, когда вам не нужна более широкая umbrella
surface.
Конкретно для setup:
openclaw/plugin-sdk/setup-runtimeпокрывает runtime-safe helpers setup:createSetupTranslator, import-safe setup patch adapters (createPatchedAccountSetupAdapter,createEnvPatchedAccountSetupAdapter,createSetupInputPresenceValidator), вывод lookup-note,promptResolvedAllowFrom,splitSetupEntriesи делегированные builders setup-proxyopenclaw/plugin-sdk/setup-runtimeвключает env-aware adapter seam дляcreateEnvPatchedAccountSetupAdapteropenclaw/plugin-sdk/channel-setupпокрывает builders setup для optional-install плюс несколько setup-safe primitives:createOptionalChannelSetupSurface,createOptionalChannelSetupAdapter,
Если ваш канал поддерживает setup или auth на основе env, а универсальные startup/config
flows должны знать эти имена env до загрузки runtime, объявите их в
манифесте Plugin через channelEnvVars. Оставляйте runtime envVars канала или локальные
константы только для copy, обращенной к операторам.
Если ваш канал может появляться в status, channels list, channels status или
сканированиях SecretRef до запуска runtime Plugin, добавьте openclaw.setupEntry в
package.json. Этот entrypoint должен быть безопасен для импорта в command
paths только для чтения и должен возвращать метаданные канала, setup-safe config adapter, status
adapter и метаданные secret target канала, необходимые для этих сводок. Не
запускайте clients, listeners или transport runtimes из setup entry.
Сохраняйте основной import path entry канала тоже узким. Discovery может оценить
entry и модуль канального Plugin, чтобы зарегистрировать capabilities без активации
канала. Файлы вроде channel-plugin-api.ts должны экспортировать объект канального
Plugin без импорта setup wizards, transport clients, socket
listeners, subprocess launchers или service startup modules. Помещайте эти runtime
части в модули, загружаемые из registerFull(...), runtime setters или lazy
capability adapters.
createOptionalChannelSetupWizard, DEFAULT_ACCOUNT_ID,
createTopLevelChannelDmPolicy, setSetupChannelEnabled и
splitSetupEntries
- используйте более широкий seam
openclaw/plugin-sdk/setupтолько когда вам также нужны более тяжелые общие helpers setup/config, такие какmoveSingleAccountChannelSectionToDefaultAccount(...)
Если ваш канал хочет только рекламировать "сначала установите этот Plugin" в setup
surfaces, предпочитайте createOptionalChannelSetupSurface(...). Сгенерированные
adapter/wizard fail closed при config writes и finalization, и они повторно используют
одно и то же сообщение install-required в validation, finalize и copy docs-link.
Для других горячих путей канала предпочитайте узкие helpers более широким legacy surfaces:
openclaw/plugin-sdk/account-core,openclaw/plugin-sdk/account-id,openclaw/plugin-sdk/account-resolutionиopenclaw/plugin-sdk/account-helpersдля конфигурации нескольких аккаунтов и отката к аккаунту по умолчаниюopenclaw/plugin-sdk/inbound-envelopeиopenclaw/plugin-sdk/channel-inboundдля входящего маршрута/конверта и связки записи и диспетчеризацииopenclaw/plugin-sdk/channel-targetsдля вспомогательных функций разбора целейopenclaw/plugin-sdk/outbound-mediaдля загрузки медиа иopenclaw/plugin-sdk/channel-outboundдля делегатов исходящей идентификации/отправки и планирования полезной нагрузкиbuildThreadAwareOutboundSessionRoute(...)изopenclaw/plugin-sdk/channel-core, когда исходящий маршрут должен сохранить явныйreplyToId/threadIdили восстановить текущую сессию:thread:после того, как базовый ключ сессии все еще совпадает. Provider plugins могут переопределять приоритет, поведение суффиксов и нормализацию идентификатора треда, когда у их платформы есть нативная семантика доставки в треды.openclaw/plugin-sdk/thread-bindings-runtimeдля жизненного цикла привязок тредов и регистрации адаптераopenclaw/plugin-sdk/agent-media-payloadтолько когда все еще требуется устаревшая структура полей полезной нагрузки агент/медиаopenclaw/plugin-sdk/telegram-command-configдля нормализации пользовательских команд Telegram, проверки дубликатов/конфликтов и контракта конфигурации команд со стабильным откатом
Каналы только для аутентификации обычно могут остановиться на пути по умолчанию: ядро обрабатывает подтверждения, а Plugin лишь предоставляет исходящие возможности и возможности аутентификации. Нативные каналы подтверждений, такие как Matrix, Slack, Telegram и пользовательские чат-транспорты, должны использовать общие нативные вспомогательные функции вместо реализации собственного жизненного цикла подтверждений.
Политика входящих упоминаний
Держите обработку входящих упоминаний разделенной на два слоя:
- сбор доказательств, принадлежащий Plugin
- оценка общей политики
Используйте openclaw/plugin-sdk/channel-mention-gating для решений политики упоминаний.
Используйте openclaw/plugin-sdk/channel-inbound только когда нужен более широкий barrel входящих
вспомогательных функций.
Хорошо подходит для локальной логики Plugin:
- обнаружение ответа боту
- обнаружение цитирования бота
- проверки участия в треде
- исключения служебных/системных сообщений
- платформенно-нативные кэши, нужные для подтверждения участия бота
Хорошо подходит для общего вспомогательного модуля:
requireMention- результат явного упоминания
- allowlist неявных упоминаний
- обход команды
- окончательное решение о пропуске
Предпочтительный поток:
- Вычислите локальные факты упоминания.
- Передайте эти факты в
resolveInboundMentionDecision({ facts, policy }). - Используйте
decision.effectiveWasMentioned,decision.shouldBypassMentionиdecision.shouldSkipво входящем шлюзе.
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 предоставляет те же общие вспомогательные функции упоминаний для
встроенных канальных plugins, которые уже зависят от инъекции runtime:
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
Если вам нужны только implicitMentionKindWhen и
resolveInboundMentionDecision, импортируйте из
openclaw/plugin-sdk/channel-mention-gating, чтобы не загружать несвязанные входящие
вспомогательные функции runtime.
Используйте resolveInboundMentionDecision({ facts, policy }) для шлюза упоминаний.
Пошаговое руководство
Пакет и манифест
Создайте стандартные файлы Plugin. Поле channel в package.json —
это то, что делает его канальным Plugin. Полную поверхность метаданных пакета
см. в Настройка и конфигурация 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 проверяет plugins.entries.acme-chat.config. Используйте его для
настроек, принадлежащих Plugin, которые не являются конфигурацией аккаунта канала. channelConfigs
проверяет channels.acme-chat и является источником холодного пути, используемым поверхностями
схемы конфигурации, настройки и UI до загрузки runtime Plugin.
Соберите объект канального Plugin
Интерфейс ChannelPlugin имеет много необязательных поверхностей адаптера. Начните с
минимума — id и setup — и добавляйте адаптеры по мере необходимости.
Создайте 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); }, }, },});Для каналов, которые принимают как канонические DM-ключи верхнего уровня, так и устаревшие вложенные ключи, используйте вспомогательные функции из plugin-sdk/channel-config-helpers: resolveChannelDmAccess, resolveChannelDmPolicy, resolveChannelDmAllowFrom и normalizeChannelDmPolicy сохраняют локальные для аккаунта значения перед унаследованными корневыми значениями. Свяжите тот же resolver с исправлением doctor через normalizeLegacyDmAliases, чтобы runtime и миграция читали один и тот же контракт.
Что createChatChannelPlugin делает за вас
Вместо ручной реализации низкоуровневых интерфейсов адаптеров вы передаете декларативные параметры, а builder компонует их:
| Параметр | Что подключает |
|---|---|
security.dm |
Scoped resolver безопасности DM из полей конфигурации |
pairing.text |
Текстовый поток DM-сопряжения с обменом кодом |
threading |
Resolver режима ответа (фиксированный, привязанный к аккаунту или пользовательский) |
outbound.attachedResults |
Функции отправки, возвращающие метаданные результата (идентификаторы сообщений) |
Вы также можете передать необработанные объекты адаптеров вместо декларативных параметров, если вам нужен полный контроль.
Необработанные исходящие адаптеры могут определять функцию chunker(text, limit, ctx).
Необязательный ctx.formatting несет решения форматирования на момент доставки,
такие как maxLinesPerMessage; применяйте их перед отправкой, чтобы трединг ответов
и границы фрагментов были один раз разрешены общей исходящей доставкой.
Контексты отправки также включают replyToIdSource (implicit или explicit),
когда нативная цель ответа была разрешена, чтобы вспомогательные функции полезной нагрузки могли сохранять
явные теги ответа, не потребляя неявный одноразовый слот ответа.
Подключите точку входа
Создайте 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(/* ... */); },});Помещайте принадлежащие каналу дескрипторы CLI в registerCliMetadata(...), чтобы OpenClaw
мог показывать их в корневой справке без активации полного runtime канала,
при этом обычные полные загрузки по-прежнему будут получать те же дескрипторы для реальной
регистрации команд. Оставьте registerFull(...) для работы, относящейся только к runtime.
Если registerFull(...) регистрирует методы RPC Gateway, используйте
префикс, специфичный для Plugin. Пространства имен администрирования ядра (config.*,
exec.approvals.*, wizard.*, update.*) остаются зарезервированными и всегда
разрешаются в operator.admin.
defineChannelPluginEntry автоматически обрабатывает разделение режимов регистрации. См.
Точки входа для всех
вариантов.
Добавьте точку входа настройки
Создайте setup-entry.ts для облегченной загрузки во время онбординга:
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";import { acmeChatPlugin } from "./src/channel.js"; export default defineSetupPluginEntry(acmeChatPlugin);OpenClaw загружает ее вместо полной точки входа, когда канал отключен или не настроен. Это позволяет не подтягивать тяжелый код runtime во время потоков настройки. Подробности см. в Настройка и конфигурация.
Каналы из комплектного рабочего пространства, которые выносят безопасные для настройки экспорты в боковые
модули, могут использовать defineBundledChannelSetupEntry(...) из
openclaw/plugin-sdk/channel-entry-contract, когда им также нужен
явный setter runtime на этапе настройки.
Обработайте входящие сообщения
Ваш Plugin должен получать сообщения с платформы и пересылать их в OpenClaw. Типичный шаблон — Webhook, который проверяет запрос и передает его через входящий обработчик вашего канала:
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; }, });}Протестируйте
Пишите colocated-тесты в 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/Общие вспомогательные средства для тестов см. в Тестирование.
Структура файлов
<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)Продвинутые темы
Фиксированные, привязанные к учетной записи или пользовательские режимы ответа
describeMessageTool и обнаружение действий
inferTargetChatType, looksLikeId, reservedLiterals, resolveTarget
TTS, STT, медиа, subagent через api.runtime
Общий жизненный цикл входящего события: ingest, resolve, record, dispatch, finalize
Следующие шаги
- Provider Plugins - если ваш Plugin также предоставляет модели
- Обзор SDK - полный справочник импортов подпутей
- Тестирование SDK - утилиты тестирования и контрактные тесты
- Манифест Plugin - полная схема манифеста