---
read_when:
    - Ви створюєте новий плагін каналу обміну повідомленнями
    - Ви хочете підключити OpenClaw до платформи обміну повідомленнями
    - Вам потрібно розуміти інтерфейс адаптера ChannelPlugin
sidebarTitle: Channel Plugins
summary: Покроковий посібник зі створення плагіна каналу обміну повідомленнями для OpenClaw
title: Створення плагінів каналів
x-i18n:
    generated_at: "2026-07-16T18:22:57Z"
    model: gpt-5.6
    postprocess_version: locale-links-v1
    prompt_version: 32
    provider: openai
    source_hash: 2c6398dd0b4789b9f4aaf7ad2d1786a7e6388cb8fbb74e8ecaecae7ac0a5eb90
    source_path: plugins/sdk-channel-plugins.md
    workflow: 16
---

Цей посібник описує створення плагіна каналу, який підключає OpenClaw до платформи
обміну повідомленнями: безпека особистих повідомлень, сполучення, ланцюжки відповідей і вихідні повідомлення.

<Info>
  Уперше працюєте з плагінами OpenClaw? Спочатку прочитайте [Початок роботи](/uk/plugins/building-plugins)
  про структуру пакета й налаштування маніфесту.
</Info>

## За що відповідає ваш плагін

Плагіни каналів не реалізують інструменти надсилання, редагування чи реакцій; ядро надає один
спільний інструмент `message`. Ваш плагін відповідає за:

- **Конфігурацію** — визначення облікового запису й майстер налаштування
- **Безпеку** — політику особистих повідомлень і списки дозволених користувачів
- **Сполучення** — процес схвалення особистих повідомлень
- **Граматику сеансів** — відображення ідентифікаторів розмов постачальника на базові
  чати, ідентифікатори гілок і резервні батьківські варіанти
- **Вихідні повідомлення** — надсилання тексту, медіафайлів і опитувань на платформу
- **Гілки** — спосіб упорядкування відповідей у гілки
- **Індикатор введення Heartbeat** — необов’язкові сигнали введення/зайнятості для цілей
  доставки Heartbeat

Ядро відповідає за спільний інструмент повідомлень, підключення промптів, зовнішню форму ключа сеансу,
загальний облік `:thread:` і диспетчеризацію.

## Адаптер повідомлень

Надайте адаптер `message` з `defineChannelMessageAdapter` із
`openclaw/plugin-sdk/channel-outbound`. Оголошуйте лише довготривалі можливості
остаточного надсилання, які фактично підтримує ваш нативний транспорт, і підкріпіть їх контрактним
тестом, що підтверджує нативний побічний ефект і повернену квитанцію. Спрямуйте надсилання тексту й медіафайлів
до тих самих транспортних функцій, які використовує застарілий адаптер `outbound`. Повний
контракт API, матрицю можливостей, правила квитанцій, фіналізацію попереднього перегляду наживо,
політику підтвердження отримання, тести й таблицю міграції див. у
[API вихідних повідомлень каналу](/uk/plugins/sdk-channel-outbound).

Якщо ваш наявний адаптер `outbound` уже має належні методи надсилання й
метадані можливостей, створіть адаптер `message` за допомогою
`createChannelMessageAdapterFromOutbound(...)`, а не пишіть ще один
міст вручну. Надсилання через адаптер повертає значення `MessageReceipt`. Для застарілих ідентифікаторів отримуйте
їх за допомогою `listMessageReceiptPlatformIds(...)` або
`resolveMessageReceiptPrimaryId(...)`, а не зберігайте паралельні поля `messageIds`.

Оголошуйте можливості передавання наживо й фіналізатора точно — ядро використовує їх, щоб визначити,
що може робити канал, а розбіжність між оголошеною та фактичною поведінкою означає
помилку контрактного тесту:

| Поверхня                              | Значення                                                                                         |
| ------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `message.live.capabilities`           | `draftPreview`, `previewFinalization`, `progressUpdates`, `nativeStreaming`, `quietFinalization` |
| `message.live.finalizer.capabilities` | `finalEdit`, `normalFallback`, `discardPending`, `previewReceipt`, `retainOnAmbiguousFailure`    |

Канали, які фіналізують чернетку попереднього перегляду на місці, мають спрямовувати логіку середовища виконання
через `defineFinalizableLivePreviewAdapter(...)` разом із
`deliverWithFinalizableLivePreviewAdapter(...)` і підкріплювати оголошені
можливості тестами `verifyChannelMessageLiveCapabilityAdapterProofs(...)`
та `verifyChannelMessageLiveFinalizerProofs(...)`, щоб нативний попередній перегляд,
перебіг, редагування, резервний варіант/збереження, очищення й поведінка квитанцій не могли непомітно
розійтися.

Обробники вхідних повідомлень, які відкладають підтвердження платформи, мають оголошувати
`message.receive.defaultAckPolicy` і `supportedAckPolicies`, а не приховувати
час підтвердження в локальному стані монітора. Охопіть кожну оголошену політику за допомогою
`verifyChannelMessageReceiveAckPolicyAdapterProofs(...)`.

Застарілі допоміжні засоби відповідей, як-от `dispatchInboundReplyWithBase` і
`recordInboundSessionAndDispatchReply`, залишаються доступними для сумісних
диспетчерів. Не використовуйте їх у новому коді каналу; починайте з адаптера `message`,
квитанцій і допоміжних засобів життєвого циклу отримання/надсилання в
`openclaw/plugin-sdk/channel-outbound`.

### Вхідне приймання (експериментальне)

Канали, що переносять авторизацію вхідних повідомлень, можуть використовувати експериментальний
підшлях `openclaw/plugin-sdk/channel-ingress-runtime` зі шляхів отримання
середовища виконання. Він приймає факти платформи, необроблені списки дозволених користувачів, дескриптори маршрутів, факти
команд і конфігурацію груп доступу, а потім повертає проєкції відправника, маршруту, команди й активації
разом із упорядкованим графом приймання, тоді як пошук на платформі й побічні
ефекти залишаються в плагіні. Зберігайте нормалізацію ідентичності плагіна в
дескрипторі, який передаєте засобу визначення; не серіалізуйте необроблені значення збігів із
визначеного стану чи рішення. Дизайн API,
межі відповідальності й вимоги до тестів див. у
[API вхідних повідомлень каналу](/uk/plugins/sdk-channel-ingress).

### Індикатори введення

Якщо ваш канал підтримує індикатори введення поза відповідями на вхідні повідомлення, надайте
`heartbeat.sendTyping(...)` у плагіні каналу. Ядро викликає його з
визначеною ціллю доставки Heartbeat перед початком виконання моделі Heartbeat і
використовує спільний життєвий цикл підтримання активності та очищення індикатора введення. Додайте
`heartbeat.clearTyping(...)`, якщо платформі потрібен явний сигнал зупинки.

### Параметри джерел медіафайлів

Якщо ваш канал додає параметри інструмента повідомлень, що містять джерела медіафайлів, надайте
назви цих параметрів через `plugin.actions.describeMessageTool(...).mediaSourceParams`.
Ядро використовує цей явний список для нормалізації шляхів пісочниці й політики доступу до
вихідних медіафайлів, тому плагінам не потрібні спільні особливі випадки в ядрі для
параметрів аватарів, вкладень або обкладинок, специфічних для постачальника.

Надавайте перевагу мапі з ключами дій, як-от `{ "set-profile": ["avatarUrl", "avatarPath"] }`,
щоб непов’язані дії не успадковували аргументи медіафайлів іншої дії. Плоский масив
також підходить для параметрів, навмисно спільних для всіх наданих дій.

Канали, які мають надавати тимчасову публічну URL-адресу для отримання медіафайлу
на боці платформи, можуть використовувати `createHostedOutboundMediaStore(...)` із
`openclaw/plugin-sdk/outbound-media` зі сховищами стану плагіна. Зберігайте розбір
маршрутів платформи й контроль токенів у плагіні каналу; спільний допоміжний засіб
відповідає лише за завантаження медіафайлів, метадані строку дії, рядки фрагментів і очищення.

### Формування нативного корисного навантаження

Якщо вашому каналу потрібне специфічне для постачальника формування `message(action="send")`,
надавайте перевагу `actions.prepareSendPayload(...)`. Розміщуйте нативні картки, блоки, вбудовані об’єкти чи
інші довготривалі дані в `payload.channelData.<channel>`, а ядро нехай надсилає
їх через адаптер вихідних повідомлень/повідомлень. Використовуйте `actions.handleAction(...)` для надсилання
лише як резервний варіант сумісності для корисних навантажень, які неможливо серіалізувати й
повторно надіслати.

### Граматика розмов сеансу

Якщо ваша платформа зберігає додаткову область видимості в ідентифікаторах розмов, залишайте такий розбір
у плагіні за допомогою `messaging.resolveSessionConversation(...)`. Це
канонічний хук для відображення `rawId` на базовий ідентифікатор розмови, необов’язковий
ідентифікатор гілки, явний `baseConversationId` і будь-які
`parentConversationCandidates`. Повертаючи `parentConversationCandidates`,
упорядковуйте їх від найвужчої батьківської розмови до найширшої/базової.

`messaging.resolveParentConversationCandidates(...)` — це застарілий
резервний варіант сумісності для плагінів, яким потрібні лише батьківські резервні варіанти поверх
загального/необробленого ідентифікатора. Якщо існують обидва хуки, ядро спочатку використовує
`resolveSessionConversation(...).parentConversationCandidates` і переходить до
`resolveParentConversationCandidates(...)`, лише якщо канонічний
хук їх не надає.

Вбудовані плагіни, яким потрібен такий самий розбір до запуску реєстру каналів,
можуть надавати файл верхнього рівня `session-key-api.ts` із відповідним
експортом `resolveSessionConversation(...)` (див. плагіни Feishu й Telegram).
Ядро використовує цю безпечну для початкового завантаження поверхню, лише коли реєстр плагінів
середовища виконання ще недоступний.

Використовуйте `openclaw/plugin-sdk/channel-route`, коли коду плагіна потрібно нормалізувати
поля, подібні до маршрутів, порівняти дочірню гілку з її батьківським маршрутом або створити
стабільний ключ дедуплікації з `{ channel, to, accountId, threadId }`. Допоміжний засіб
нормалізує числові ідентифікатори гілок так само, як ядро, тому віддавайте йому перевагу над ситуативними
порівняннями `String(threadId)`. Плагіни зі специфічною для постачальника граматикою цілей
мають надавати `messaging.resolveOutboundSessionRoute(...)`, щоб ядро отримувало
нативну для постачальника ідентичність сеансу й гілки без обхідних засобів парсера.

### Підтримка прив’язки розмов у межах облікового запису

Установіть `conversationBindings.supportsCurrentConversationBinding`, якщо канал
підтримує загальні прив’язки поточної розмови. `createChatChannelPlugin(...)`
за замовчуванням установлює цю статичну можливість у `true`.

Якщо підтримка відрізняється залежно від налаштованого облікового запису, також реалізуйте
`conversationBindings.isCurrentConversationBindingSupported({ accountId })`.
Ядро обчислює цей синхронний хук лише після ввімкнення статичної можливості.
Повернення `false` робить загальні операції визначення можливостей поточної розмови,
прив’язування, пошуку, переліку, оновлення й відв’язування недоступними для цього облікового запису.
Якщо хук не вказано, статична можливість застосовується до кожного облікового запису.

Визначайте відповідь із уже завантаженої конфігурації облікового запису або стану середовища виконання. Цей
хук керує лише загальними прив’язками поточної розмови; він не замінює
налаштовані правила прив’язки чи маршрутизацію сеансів, за яку відповідає плагін. Контрактні тести
мають охоплювати принаймні один підтримуваний і один непідтримуваний обліковий запис за допомогою
контракту `ChannelPlugin["conversationBindings"]`, експортованого з
`openclaw/plugin-sdk/channel-core`.

## Схвалення й можливості каналу

Більшості плагінів каналів не потрібен спеціальний код для схвалень. Ядро відповідає за
`/approve` у тому самому чаті, спільні корисні навантаження кнопок схвалення й загальну резервну доставку.
`ChannelPlugin.approvals` видалено; натомість розміщуйте факти доставки, нативного представлення, візуалізації та автентифікації
схвалень в одному об’єкті `approvalCapability`. `plugin.auth` призначено лише для входу й виходу —
ядро більше не зчитує з цього об’єкта хуки автентифікації схвалень.

Використовуйте `approvalCapability.delivery` лише для нативної маршрутизації схвалень або придушення
резервного варіанта, а `approvalCapability.render` — лише коли каналу справді потрібні
власні корисні навантаження схвалень замість спільного засобу візуалізації.

### Автентифікація схвалень

- `approvalCapability.authorizeActorAction` і
  `approvalCapability.getActionAvailabilityState` є канонічним
  інтерфейсом автентифікації схвалень.
- Використовуйте `getActionAvailabilityState` для доступності автентифікації схвалень у тому самому чаті.
  Зберігайте налаштованих схвалювачів доступними для `/approve`, навіть коли нативну доставку
  вимкнено; натомість використовуйте стан нативної початкової поверхні для вказівок щодо доставки/налаштування.
- Якщо ваш канал надає нативні схвалення виконання, використовуйте
  `approvalCapability.getExecInitiatingSurfaceState` для
  стану початкової поверхні/нативного клієнта, коли він відрізняється від автентифікації
  схвалень у тому самому чаті. Ядро використовує цей хук, специфічний для виконання, щоб розрізняти `enabled` і
  `disabled`, визначати, чи підтримує початковий канал нативні схвалення
  виконання, і включати канал до резервних указівок для нативного клієнта.
  `createApproverRestrictedNativeApprovalCapability(...)` заповнює це для
  типового випадку.
- Якщо канал може визначати стабільні, подібні до власника ідентичності особистих повідомлень із наявної конфігурації,
  використовуйте `createResolvedApproverActionAuthAdapter` із
  `openclaw/plugin-sdk/approval-runtime`, щоб обмежити `/approve` у тому самому чаті
  без додавання до ядра логіки, специфічної для схвалень.
- Якщо власна автентифікація схвалень навмисно дозволяє лише резервний варіант у тому самому чаті, поверніть
  `markImplicitSameChatApprovalAuthorization({ authorized: true })` із
  `openclaw/plugin-sdk/approval-auth-runtime`; інакше ядро вважатиме
  результат явною авторизацією схвалювача.
- Якщо нативний зворотний виклик, за який відповідає канал, безпосередньо виконує схвалення, використовуйте
  `isImplicitSameChatApprovalAuthorization(...)` перед виконанням, щоб неявний
  резервний варіант усе одно проходив через звичайну авторизацію суб’єкта каналу.

### Життєвий цикл корисного навантаження й указівки з налаштування

- Використовуйте `outbound.shouldSuppressLocalPayloadPrompt` або
  `outbound.beforeDeliverPayload` для специфічної для каналу поведінки життєвого циклу корисного навантаження,
  як-от приховування дубльованих локальних запитів на схвалення або надсилання індикаторів
  введення перед доставкою.
- Використовуйте `approvalCapability.describeExecApprovalSetup`, коли канал хоче,
  щоб відповідь для вимкненого шляху пояснювала точні параметри конфігурації, потрібні для ввімкнення
  нативних схвалень виконання. Хук отримує `{ channel, channelLabel, accountId }`;
  канали з іменованими обліковими записами мають виводити шляхи в межах облікового запису, як-от
  `channels.<channel>.accounts.<id>.execApprovals.*`, замість значень
  верхнього рівня за замовчуванням.
- Використовуйте `approvalCapability.describePluginApprovalSetup`, коли вказівки плагіна
  щодо помилки схвалення можна безпечно показувати для помилок відсутності маршруту й завершення часу очікування
  схвалення плагіна. `createApproverRestrictedNativeApprovalCapability(...)` не
  визначає цього з `describeExecApprovalSetup`; передавайте той самий допоміжний засіб явно,
  лише коли схвалення плагіна й виконання справді використовують однакове нативне налаштування.

### Нативна доставка схвалень

Якщо каналу потрібна нативна доставка схвалень, зосередьте код каналу на
нормалізації цілі та фактах транспорту/представлення. Використовуйте
`createChannelExecApprovalProfile`, `createChannelNativeOriginTargetResolver`,
`createChannelApproverDmTargetResolver` і
`createApproverRestrictedNativeApprovalCapability` із
`openclaw/plugin-sdk/approval-runtime`. Розмістіть специфічні для каналу факти за
`approvalCapability.nativeRuntime`, бажано через
`createChannelApprovalNativeRuntimeAdapter(...)` або
`createLazyChannelApprovalNativeRuntimeAdapter(...)`, щоб ядро могло зібрати
обробник і відповідати за фільтрування запитів, маршрутизацію, дедуплікацію, завершення строку дії, підписку
Gateway і сповіщення про маршрутизацію в інше місце.

`nativeRuntime` розділено на кілька менших інтерфейсів:

- `availability` — чи налаштовано обліковий запис і чи слід обробляти
  запит
- `presentation` — перетворення спільної моделі подання схвалення на
  нативні корисні навантаження зі станами очікування/вирішено/прострочено або кінцеві дії
- `transport` — підготовка цілей, а також надсилання/оновлення/видалення нативних
  повідомлень про схвалення
- `interactions` — необов’язкові обробники прив’язування/відв’язування/очищення дій для нативних кнопок
  або реакцій, а також необов’язковий обробник `cancelDelivered`. Реалізуйте
  `cancelDelivered`, коли `deliverPending` реєструє внутрішньопроцесний або постійний
  стан (наприклад, сховище цілей реакцій), щоб цей стан можна було звільнити, якщо
  зупинка обробника скасовує доставлення до виконання `bindPending`, або коли
  `bindPending` не повертає дескриптор
- `observe` — необов’язкові обробники діагностики доставлення

Інші допоміжні засоби схвалення:

- Використовуйте `createNativeApprovalChannelRouteGates` з
  `openclaw/plugin-sdk/approval-native-runtime`, коли канал підтримує як нативне доставлення
  з джерела сеансу, так і явні цілі пересилання схвалень. Цей
  допоміжний засіб централізує вибір конфігурації схвалень, обробку `mode`, фільтри
  агентів/сеансів, прив’язування облікового запису, зіставлення цілі сеансу та зіставлення
  списку цілей, тоді як виклики і надалі відповідають за ідентифікатор каналу, типовий
  режим пересилання, пошук облікового запису, перевірку ввімкнення транспорту,
  нормалізацію цілі та визначення цілі джерела ходу. Не використовуйте його для
  створення належних ядру типових політик каналу; явно передавайте
  задокументований типовий режим каналу.
- `createChannelNativeOriginTargetResolver` типово використовує спільний засіб
  зіставлення маршрутів каналів для цілей `{ to, accountId, threadId }`. Передавайте
  `targetsMatch` лише тоді, коли канал має специфічні для провайдера правила еквівалентності,
  як-от зіставлення префіксів часових позначок Slack. Передавайте `normalizeTargetForMatch`, коли
  каналу потрібно канонізувати ідентифікатори провайдера до запуску типового засобу
  зіставлення маршрутів або спеціального зворотного виклику `targetsMatch`, зберігаючи
  початкову ціль для доставлення. Використовуйте `normalizeTarget` лише тоді, коли потрібно
  канонізувати саму визначену ціль доставлення.
- Якщо каналу потрібні належні середовищу виконання об’єкти, як-от клієнт, токен, застосунок Bolt
  або приймач webhook, реєструйте їх через
  `openclaw/plugin-sdk/channel-runtime-context`. Загальний реєстр контексту середовища
  виконання дає ядру змогу ініціалізувати керовані можливостями обробники зі стану
  запуску каналу без додавання спеціалізованих обгорток для схвалень.
- Звертайтеся до низькорівневих `createChannelApprovalHandler` або
  `createChannelNativeApprovalRuntime` лише тоді, коли межа, керована можливостями,
  ще недостатньо виразна.
- Нативні канали схвалення мають спрямовувати і `accountId`, і `approvalKind`
  через ці допоміжні засоби. `accountId` обмежує політику схвалення для кількох
  облікових записів відповідним обліковим записом бота, а `approvalKind` надає каналу
  поведінку схвалення для exec і plugin без жорстко закодованих розгалужень у
  ядрі.
- Ядро також відповідає за сповіщення про переспрямування схвалень. Плагіни каналів не повинні
  надсилати власні подальші повідомлення «схвалення надіслано в особисті повідомлення / інший канал» із
  `createChannelNativeApprovalRuntime`; натомість надавайте точну маршрутизацію від джерела +
  до особистих повідомлень особи, яка схвалює, через спільні допоміжні засоби можливостей схвалення й дозвольте
  ядру агрегувати фактичні доставлення, перш ніж публікувати будь-яке сповіщення назад у
  чат, що ініціював запит.
- Зберігайте вид ідентифікатора доставленого схвалення наскрізно. Нативні клієнти не повинні
  вгадувати або переписувати маршрутизацію схвалень exec і plugin на основі локального
  стану каналу.
- Передайте цей явний `approvalKind` до `resolveApprovalOverGateway`. Це використовує
  канонічний сервіс `approval.resolve` і повертає зареєстрованого переможця, коли
  інша поверхня відповідає першою. Старіший явний вхід `resolveMethod`
  залишається для елементів керування на основі команд; нові нативні дії не повинні використовувати його або
  визначати вид з ідентифікатора.
- Різні види схвалень можуть навмисно надавати різні нативні
  поверхні. Поточні вбудовані приклади: Matrix зберігає однакову нативну маршрутизацію
  особистих повідомлень/каналів і взаємодію з реакціями для схвалень exec і plugin, водночас дозволяючи
  автентифікації відрізнятися за видом схвалення; Slack зберігає доступність нативної маршрутизації схвалень
  як для ідентифікаторів exec, так і для ідентифікаторів plugin.
- `createApproverRestrictedNativeApprovalAdapter` досі існує як
  обгортка сумісності, але новий код має віддавати перевагу конструктору можливостей
  і надавати `approvalCapability` у плагіні.

### Вужчі підшляхи середовища виконання схвалень

Для гарячих точок входу каналів віддавайте перевагу цим вужчим підшляхам замість ширшого
бареля `approval-runtime`, коли потрібна лише одна частина цього сімейства:

- `openclaw/plugin-sdk/approval-auth-runtime`
- `openclaw/plugin-sdk/approval-client-runtime`
- `openclaw/plugin-sdk/approval-delivery-runtime`
- `openclaw/plugin-sdk/approval-gateway-runtime`
- `openclaw/plugin-sdk/approval-reference-runtime`
- `openclaw/plugin-sdk/approval-handler-adapter-runtime`
- `openclaw/plugin-sdk/approval-handler-runtime`
- `openclaw/plugin-sdk/approval-native-runtime`
- `openclaw/plugin-sdk/approval-reply-runtime`
- `openclaw/plugin-sdk/channel-runtime-context`

Так само віддавайте перевагу `openclaw/plugin-sdk/reply-runtime`,
`openclaw/plugin-sdk/reply-dispatch-runtime`,
`openclaw/plugin-sdk/reply-reference` і
`openclaw/plugin-sdk/reply-chunking` замість ширших узагальнювальних поверхонь, коли
вони не потрібні всі одночасно.

### Підшляхи налаштування

- `openclaw/plugin-sdk/setup-runtime` охоплює безпечні для середовища виконання допоміжні засоби налаштування:
  `createSetupTranslator`, безпечні для імпорту адаптери виправлень налаштування
  (`createPatchedAccountSetupAdapter`, `createEnvPatchedAccountSetupAdapter`,
  `createSetupInputPresenceValidator`), виведення приміток пошуку,
  `promptResolvedAllowFrom`, `splitSetupEntries` і делеговані
  конструктори проксі налаштування.
- `openclaw/plugin-sdk/channel-setup` охоплює конструктори налаштування
  необов’язкового встановлення, а також кілька безпечних для налаштування примітивів: `createOptionalChannelSetupSurface`,
  `createOptionalChannelSetupAdapter`, `createOptionalChannelSetupWizard`,
  `DEFAULT_ACCOUNT_ID`, `createTopLevelChannelDmPolicy`,
  `setSetupChannelEnabled` і `splitSetupEntries`.
- Використовуйте ширшу межу `openclaw/plugin-sdk/setup` лише тоді, коли також потрібні
  важчі спільні допоміжні засоби налаштування/конфігурації, як-от
  `moveSingleAccountChannelSectionToDefaultAccount(...)`.

Якщо ваш канал лише має повідомляти «спочатку встановіть цей плагін» у поверхнях
налаштування, віддавайте перевагу `createOptionalChannelSetupSurface(...)`. Згенеровані
адаптер/майстер безпечно припиняють роботу під час записування конфігурації та завершення, а також повторно використовують
те саме повідомлення про необхідність встановлення під час перевірки, завершення та копіювання
посилання на документацію.

Якщо ваш канал підтримує налаштування або автентифікацію через змінні середовища й загальні потоки
запуску/конфігурації мають знати назви цих змінних до завантаження середовища виконання, оголосіть їх у
маніфесті плагіна за допомогою `channelEnvVars`. Зберігайте `envVars` середовища виконання каналу або локальні
константи лише для тексту, призначеного операторам.

Якщо ваш канал може з’являтися в `status`, `channels list`, `channels status` або
скануваннях SecretRef до запуску середовища виконання плагіна, додайте `openclaw.setupEntry` у
`package.json`. Цю точку входу має бути безпечно імпортувати в шляхах команд
лише для читання, і вона має повертати метадані каналу, безпечний для налаштування адаптер
конфігурації, адаптер стану та метадані цілі секрету каналу, потрібні для цих
зведень. Не запускайте клієнти, слухачі або транспортні середовища виконання з точки
входу налаштування.

Також зберігайте вузьким шлях імпорту основної точки входу каналу. Виявлення може обчислювати
точку входу та модуль плагіна каналу для реєстрації можливостей без
активації каналу. Файли на кшталт `channel-plugin-api.ts` мають експортувати
об’єкт плагіна каналу без імпорту майстрів налаштування, транспортних
клієнтів, слухачів сокетів, засобів запуску підпроцесів або модулів запуску служб.
Розміщуйте ці частини середовища виконання в модулях, що завантажуються з `registerFull(...)`, сетерах
середовища виконання або лінивих адаптерах можливостей.

### Інші вузькі підшляхи каналів

Для інших гарячих шляхів каналів віддавайте перевагу вузьким допоміжним засобам замість ширших застарілих
поверхонь:

- `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:`
  після того, як базовий ключ сеансу все ще збігається. Плагіни провайдерів можуть
  перевизначати пріоритет, поведінку суфікса та нормалізацію ідентифікатора гілки, коли
  їхня платформа має нативну семантику доставлення в гілки.
- `openclaw/plugin-sdk/thread-bindings-runtime` для життєвого циклу прив’язування гілок
  і реєстрації адаптерів
- `openclaw/plugin-sdk/agent-media-payload` лише тоді, коли застаріле компонування полів
  корисного навантаження агента/медіафайлів усе ще потрібне
- `openclaw/plugin-sdk/telegram-command-config` (застаріле: жоден вбудований
  плагін не використовує його у робочому середовищі) для нормалізації спеціальних команд Telegram,
  перевірки дублікатів/конфліктів і контракту конфігурації команд зі стабільним
  резервним варіантом; для нового коду плагінів віддавайте перевагу локальній для плагіна обробці конфігурації команд

Каналам лише з автентифікацією зазвичай достатньо типового шляху: ядро обробляє
схвалення, а плагін лише надає можливості вихідного надсилання/автентифікації. Нативні
канали схвалення, як-от Matrix, Slack, Telegram і спеціальні транспортні засоби чатів,
мають використовувати спільні нативні допоміжні засоби замість реалізації власного життєвого
циклу схвалення.

## Політика вхідних згадок

Розділяйте обробку вхідних згадок на два рівні:

- збирання доказів, що належить плагіну
- оцінювання спільної політики

Використовуйте `openclaw/plugin-sdk/channel-mention-gating` для рішень політики згадок.
Використовуйте `openclaw/plugin-sdk/channel-inbound` лише тоді, коли потрібен ширший
барель допоміжних засобів для вхідних даних.

Доцільно для локальної логіки плагіна:

- виявлення відповіді боту
- виявлення цитування бота
- перевірки участі в гілці
- виключення службових/системних повідомлень
- нативні для платформи кеші, потрібні для підтвердження участі бота

Доцільно для спільного допоміжного засобу:

- `requireMention`
- результат явної згадки
- список дозволених неявних згадок
- обхід для команд
- остаточне рішення про пропуск

Рекомендований потік:

1. Обчисліть локальні факти про згадки.
2. Передайте ці факти до `resolveInboundMentionDecision({ facts, policy })`.
3. Використовуйте `decision.effectiveWasMentioned`, `decision.shouldBypassMention` і
   `decision.shouldSkip` у вашому вхідному шлюзі.

```typescript
import {
  implicitMentionKindWhen,
  matchesMentionWithExplicit,
  resolveInboundMentionDecision,
} from "openclaw/plugin-sdk/channel-inbound";

const wasMentioned = matchesMentionWithExplicit({
  text,
  mentionRegexes,
  explicit: {
    hasAnyMention,
    isExplicitlyMentioned,
    canResolveExplicit,
  },
});

const facts = {
  canDetectMention: true,
  wasMentioned,
  hasAnyMention,
  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;
```

`matchesMentionWithExplicit(...)` повертає логічне значення. `hasAnyMention`,
`isExplicitlyMentioned` і `canResolveExplicit` надходять із власних нативних
метаданих згадок каналу (сутностей повідомлень, ознак відповіді боту тощо);
передавайте значення `false`/`undefined`, коли ваша платформа не може їх виявити.

`api.runtime.channel.mentions` надає ті самі спільні допоміжні засоби згадок для
вбудованих плагінів каналів, які вже залежать від ін’єкції середовища виконання:
`buildMentionRegexes`, `matchesMentionPatterns`, `matchesMentionWithExplicit`,
`implicitMentionKindWhen`, `resolveInboundMentionDecision`.

Якщо потрібні лише `implicitMentionKindWhen` і `resolveInboundMentionDecision`,
імпортуйте з `openclaw/plugin-sdk/channel-mention-gating`, щоб уникнути завантаження
непов’язаних допоміжних засобів середовища виконання для вхідних даних.

## Покроковий огляд

<Steps>
  <a id="step-1-package-and-manifest"></a>
  <Step title="Пакет і маніфест">
    Створіть стандартні файли плагіна. Поле `channels` у
    `openclaw.plugin.json` (а не поле `kind`) позначає маніфест як
    власника каналу. Повний перелік метаданих пакета див. у розділі
    [Налаштування та конфігурація плагіна](/uk/plugins/sdk-setup#openclaw-channel):

    <CodeGroup>
    ```json package.json
    {
      "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",
          "blurb": "Підключіть OpenClaw до чату Acme."
        }
      }
    }
    ```

    ```json openclaw.plugin.json
    {
      "id": "acme-chat",
      "channels": ["acme-chat"],
      "name": "Чат Acme",
      "description": "Плагін каналу чату Acme",
      "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": "Токен бота",
              "sensitive": true
            }
          }
        }
      }
    }
    ```
    </CodeGroup>

    `configSchema` перевіряє `plugins.entries.acme-chat.config`. Використовуйте його для
    налаштувань, що належать плагіну й не є конфігурацією облікового запису каналу.
    `channelConfigs.acme-chat.schema` перевіряє `channels.acme-chat` і є
    джерелом холодного шляху, яке використовується схемою конфігурації, налаштуванням та інтерфейсом до
    завантаження середовища виконання плагіна. Повний опис полів верхнього рівня
    див. у розділі [Маніфест плагіна](/uk/plugins/manifest).

  </Step>

  <Step title="Створення об’єкта плагіна каналу">
    Інтерфейс `ChannelPlugin` має багато необов’язкових поверхонь адаптера. Почніть із
    мінімального набору — `id`, `config` і `setup` — та додавайте адаптери за
    потреби.

    Створіть `src/channel.ts`:

    ```typescript 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"; // клієнт API вашої платформи

    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: токен обов’язковий");
      return {
        accountId: accountId ?? null,
        token,
        allowFrom: section?.allowFrom ?? [],
        dmPolicy: section?.dmSecurity,
      };
    }

    export const acmeChatPlugin = createChatChannelPlugin<ResolvedAccount>({
      base: createChannelPluginBase({
        id: "acme-chat",
        // Визначення та перевірка облікового запису належать до `config`, а не до `setup`.
        // `setup` охоплює записи під час початкового налаштування (applyAccountConfig, validateInput).
        config: {
          listAccountIds: () => ["default"],
          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",
            };
          },
        },
        setup: {
          applyAccountConfig: ({ cfg, input }) => ({
            ...cfg,
            channels: {
              ...cfg.channels,
              "acme-chat": { ...(cfg.channels as any)?.["acme-chat"], ...input },
            },
          }),
        },
      }),

      // Безпека особистих повідомлень: хто може надсилати повідомлення боту
      security: {
        dm: {
          channelKey: "acme-chat",
          resolvePolicy: (account) => account.dmPolicy,
          resolveAllowFrom: (account) => account.allowFrom,
          defaultPolicy: "allowlist",
        },
      },

      // Сполучення: процес схвалення нових контактів для особистих повідомлень
      pairing: {
        text: {
          idLabel: "Ім’я користувача чату Acme",
          message: "Надішліть цей код, щоб підтвердити свою особу:",
          notify: async ({ target, code }) => {
            await acmeChatApi.sendDm(target, `Код сполучення: ${code}`);
          },
        },
      },

      // Гілки: спосіб доставлення відповідей
      threading: { topLevelReplyToMode: "reply" },

      // Вихідні повідомлення: надсилання повідомлень на платформу
      outbound: {
        attachedResults: {
          channel: "acme-chat",
          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);
          },
        },
      },
    });
    ```

    Для каналів, які приймають як канонічні ключі особистих повідомлень верхнього рівня, так і застарілі вкладені ключі, використовуйте допоміжні функції з `plugin-sdk/channel-config-helpers`: `resolveChannelDmAccess`, `resolveChannelDmPolicy`, `resolveChannelDmAllowFrom` і `normalizeChannelDmPolicy` надають локальним значенням облікового запису пріоритет над успадкованими кореневими значеннями. Поєднайте той самий засіб визначення з виправленням через doctor за допомогою `normalizeLegacyDmAliases`, щоб середовище виконання та міграція читали той самий контракт.

    <Accordion title="Що createChatChannelPlugin робить за вас">
      Замість реалізації низькорівневих інтерфейсів адаптерів вручну ви передаєте
      декларативні параметри, а побудовник їх компонуватиме:

      | Параметр | Що він підключає |
      | --- | --- |
      | `security.dm` | Обмежений областю засіб визначення безпеки особистих повідомлень із полів конфігурації |
      | `pairing.text` | Текстовий процес сполучення через особисті повідомлення з обміном кодом |
      | `threading` | Засіб визначення режиму відповіді (фіксований, обмежений обліковим записом або власний) |
      | `outbound.attachedResults` | Функції надсилання, що повертають метадані результату (ідентифікатори повідомлень); потребує сусіднього ідентифікатора `channel`, щоб ядро могло позначити повернений результат доставлення |

      Якщо потрібен повний контроль, замість декларативних параметрів також можна
      передавати необроблені об’єкти адаптерів.

      Необроблені вихідні адаптери можуть визначати функцію `chunker(text, limit, ctx)`.
      Необов’язковий `ctx.formatting` містить рішення щодо форматування під час доставлення,
      як-от `maxLinesPerMessage`; застосуйте його перед надсиланням, щоб гілкування відповідей
      і межі фрагментів одноразово визначалися спільним механізмом вихідного доставлення.
      Контексти надсилання також містять `replyToIdSource` (`implicit` або `explicit`),
      коли визначено нативну ціль відповіді, щоб допоміжні функції корисного навантаження могли зберігати
      явні теги відповіді, не використовуючи неявний одноразовий слот відповіді.
    </Accordion>

  </Step>

  <Step title="Підключення точки входу">
    Створіть `index.ts`:

    ```typescript index.ts
    import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
    import { acmeChatPlugin } from "./src/channel.js";

    export default defineChannelPluginEntry({
      id: "acme-chat",
      name: "Чат Acme",
      description: "Плагін каналу чату Acme",
      plugin: acmeChatPlugin,
      registerCliMetadata(api) {
        api.registerCli(
          ({ program }) => {
            program
              .command("acme-chat")
              .description("Керування чатом Acme");
          },
          {
            descriptors: [
              {
                name: "acme-chat",
                description: "Керування чатом Acme",
                hasSubcommands: false,
              },
            ],
          },
        );
      },
      registerFull(api) {
        api.registerGatewayMethod(/* ... */);
      },
    });
    ```

    Розміщуйте дескриптори CLI, що належать каналу, у `registerCliMetadata(...)`, щоб OpenClaw
    міг показувати їх у кореневій довідці без активації повного середовища виконання каналу,
    тоді як звичайне повне завантаження й надалі використовуватиме ті самі дескриптори для фактичної
    реєстрації команд. Залиште `registerFull(...)` для роботи лише під час виконання.
    `defineChannelPluginEntry` автоматично обробляє поділ режимів реєстрації.
    Якщо `registerFull(...)` реєструє методи RPC Gateway, використовуйте
    префікс, специфічний для плагіна. Простори імен адміністрування ядра (`config.*`,
    `exec.approvals.*`, `wizard.*`, `update.*`) залишаються зарезервованими та завжди
    визначаються як `operator.admin`. Усі параметри див. у розділі
    [Точки входу](/uk/plugins/sdk-entrypoints#definechannelpluginentry).

  </Step>

  <Step title="Додавання точки входу налаштування">
    Створіть `setup-entry.ts` для полегшеного завантаження під час початкового налаштування:

    ```typescript setup-entry.ts
    import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
    import { acmeChatPlugin } from "./src/channel.js";

    export default defineSetupPluginEntry(acmeChatPlugin);
    ```

    OpenClaw завантажує її замість повної точки входу, коли канал вимкнений
    або не налаштований. Це дає змогу не завантажувати важкий код середовища виконання під час процесів налаштування.
    Докладніше див. у розділі [Налаштування та конфігурація](/uk/plugins/sdk-setup#setup-entry).

    Вбудовані канали робочого простору, які відокремлюють безпечні для налаштування експорти в допоміжні
    модулі, можуть використовувати `defineBundledChannelSetupEntry(...)` з
    `openclaw/plugin-sdk/channel-entry-contract`, коли їм також потрібен
    явний засіб установлення середовища виконання під час налаштування.

  </Step>

  <Step title="Оброблення вхідних повідомлень">
    Плагін має отримувати повідомлення з платформи й переспрямовувати їх до
    OpenClaw. Типовий шаблон — Webhook, який перевіряє запит і
    передає його обробнику вхідних повідомлень каналу:

    ```typescript
    registerFull(api) {
      api.registerHttpRoute({
        path: "/acme-chat/webhook",
        auth: "plugin", // автентифікація, керована плагіном (перевіряйте підписи самостійно)
        handler: async (req, res) => {
          const event = parseWebhookPayload(req);

          // Ваш обробник вхідних повідомлень передає повідомлення до OpenClaw.
          // Точне підключення залежить від SDK вашої платформи —
          // див. реальний приклад у пакеті вбудованого плагіна Microsoft Teams або Google Chat.
          await handleAcmeChatInbound(api, event);

          res.statusCode = 200;
          res.end("ok");
          return true;
        },
      });
    }
    ```

    <Note>
      Оброблення вхідних повідомлень залежить від каналу. Кожен плагін каналу володіє
      власним конвеєром вхідних повідомлень. Перегляньте вбудовані плагіни каналів
      (наприклад, пакет плагіна Microsoft Teams або Google Chat), щоб ознайомитися з реальними шаблонами.
    </Note>

  </Step>

<a id="step-6-test"></a>
<Step title="Тестування">
Пишіть тести поруч із кодом у `src/channel.test.ts`:

    ```typescript src/channel.test.ts
    import { describe, it, expect } from "vitest";
    import { acmeChatPlugin } from "./channel.js";

    describe("плагін acme-chat", () => {
      it("визначає обліковий запис із конфігурації", () => {
        const cfg = {
          channels: {
            "acme-chat": { token: "test-token", allowFrom: ["user1"] },
          },
        } as any;
        const account = acmeChatPlugin.config.resolveAccount(cfg, undefined);
        expect(account.token).toBe("test-token");
      });

      it("перевіряє обліковий запис без матеріалізації секретів", () => {
        const cfg = {
          channels: { "acme-chat": { token: "test-token" } },
        } as any;
        const result = acmeChatPlugin.config.inspectAccount!(cfg, undefined);
        expect(result.configured).toBe(true);
        expect(result.tokenStatus).toBe("available");
      });

      it("повідомляє про відсутню конфігурацію", () => {
        const cfg = { channels: {} } as any;
        const result = acmeChatPlugin.config.inspectAccount!(cfg, undefined);
        expect(result.configured).toBe(false);
      });
    });
    ```

    ```bash
    pnpm test <bundled-plugin-root>/acme-chat/
    ```

    Спільні допоміжні засоби для тестування описано в розділі [Тестування](/uk/plugins/sdk-testing).

</Step>
</Steps>

## Структура файлів

```text
<bundled-plugin-root>/acme-chat/
├── package.json              # метадані openclaw.channel
├── openclaw.plugin.json      # Маніфест зі схемою конфігурації
├── index.ts                  # defineChannelPluginEntry
├── setup-entry.ts            # defineSetupPluginEntry
├── api.ts                    # Загальнодоступні експорти (необов’язково)
├── runtime-api.ts            # Внутрішні експорти середовища виконання (необов’язково)
└── src/
    ├── channel.ts            # ChannelPlugin через createChatChannelPlugin
    ├── channel.test.ts       # Тести
    ├── client.ts             # Клієнт API платформи
    └── runtime.ts            # Сховище середовища виконання (за потреби)
```

## Розширені теми

<CardGroup cols={2}>
  <Card title="Параметри гілок обговорення" icon="git-branch" href="/uk/plugins/sdk-entrypoints#registration-mode">
    Фіксовані, прив’язані до облікового запису або власні режими відповідей
  </Card>
  <Card title="Інтеграція інструмента повідомлень" icon="puzzle" href="/uk/plugins/architecture#channel-plugins-and-the-shared-message-tool">
    describeMessageTool і виявлення дій
  </Card>
  <Card title="Визначення цілі" icon="crosshair" href="/uk/plugins/architecture-internals#channel-target-resolution">
    inferTargetChatType, looksLikeId, reservedLiterals, resolveTarget
  </Card>
  <Card title="Допоміжні засоби середовища виконання" icon="settings" href="/uk/plugins/sdk-runtime">
    TTS, STT, медіа, підагент через api.runtime
  </Card>
  <Card title="API вхідних даних каналу" icon="bolt" href="/uk/plugins/sdk-channel-inbound">
    Спільний життєвий цикл вхідної події: отримання, визначення, запис, передавання, завершення
  </Card>
</CardGroup>

<Note>
Деякі вбудовані допоміжні точки інтеграції досі існують для супроводу
вбудованих плагінів і сумісності. Вони не є рекомендованим підходом для нових
плагінів каналів; віддавайте перевагу загальним підшляхам каналу, налаштування,
відповіді та середовища виконання зі спільної поверхні SDK, якщо ви безпосередньо
не супроводжуєте відповідне сімейство вбудованих плагінів.
</Note>

## Наступні кроки

- [Плагіни постачальників](/uk/plugins/sdk-provider-plugins) — якщо ваш плагін також надає моделі
- [Огляд SDK](/uk/plugins/sdk-overview) — повний довідник імпорту підшляхів
- [Тестування SDK](/uk/plugins/sdk-testing) — утиліти тестування та контрактні тести
- [Маніфест плагіна](/uk/plugins/manifest) — повна схема маніфесту

## Пов’язані матеріали

- [Налаштування SDK плагінів](/uk/plugins/sdk-setup)
- [Створення плагінів](/uk/plugins/building-plugins)
- [Плагіни середовища агентів](/uk/plugins/sdk-agent-harness)
