---
read_when:
    - Створення або налагодження власних Plugin для OpenClaw
    - Розуміння моделі можливостей Plugin або меж володіння
    - Робота над конвеєром завантаження Plugin або реєстром
    - Реалізація хуків часу виконання провайдера або Plugin каналів
sidebarTitle: Internals
summary: 'Внутрішні механізми Plugin: модель можливостей, володіння, контракти, конвеєр завантаження та допоміжні засоби часу виконання'
title: Внутрішні механізми Plugin
x-i18n:
    generated_at: "2026-04-14T16:24:10Z"
    model: gpt-5.4
    provider: openai
    source_hash: f86798b5d2b0ad82d2397a52a6c21ed37fe6eee1dd3d124a9e4150c4f630b841
    source_path: plugins/architecture.md
    workflow: 15
---

# Внутрішні механізми Plugin

<Info>
  Це **поглиблений довідник з архітектури**. Практичні посібники дивіться тут:
  - [Установлення та використання плагінів](/uk/tools/plugin) — посібник користувача
  - [Початок роботи](/uk/plugins/building-plugins) — перший навчальний посібник зі створення плагіна
  - [Plugin каналів](/uk/plugins/sdk-channel-plugins) — створення каналу обміну повідомленнями
  - [Plugin провайдерів](/uk/plugins/sdk-provider-plugins) — створення провайдера моделей
  - [Огляд SDK](/uk/plugins/sdk-overview) — карта імпортів і API реєстрації
</Info>

Ця сторінка описує внутрішню архітектуру системи Plugin в OpenClaw.

## Публічна модель можливостей

Можливості — це публічна модель **власних Plugin** у межах OpenClaw. Кожен
власний Plugin OpenClaw реєструється для одного або кількох типів можливостей:

| Можливість            | Метод реєстрації                              | Приклади Plugin                     |
| --------------------- | --------------------------------------------- | ----------------------------------- |
| Текстовий інференс    | `api.registerProvider(...)`                   | `openai`, `anthropic`               |
| Backend інференсу CLI | `api.registerCliBackend(...)`                 | `openai`, `anthropic`               |
| Мовлення              | `api.registerSpeechProvider(...)`             | `elevenlabs`, `microsoft`           |
| Транскрибування в реальному часі | `api.registerRealtimeTranscriptionProvider(...)` | `openai`                |
| Голос у реальному часі | `api.registerRealtimeVoiceProvider(...)`     | `openai`                            |
| Розуміння медіа       | `api.registerMediaUnderstandingProvider(...)` | `openai`, `google`                  |
| Генерація зображень   | `api.registerImageGenerationProvider(...)`    | `openai`, `google`, `fal`, `minimax` |
| Генерація музики      | `api.registerMusicGenerationProvider(...)`    | `google`, `minimax`                 |
| Генерація відео       | `api.registerVideoGenerationProvider(...)`    | `qwen`                              |
| Отримання вебданих    | `api.registerWebFetchProvider(...)`           | `firecrawl`                         |
| Вебпошук              | `api.registerWebSearchProvider(...)`          | `google`                            |
| Канал / обмін повідомленнями | `api.registerChannel(...)`              | `msteams`, `matrix`                 |

Plugin, який не реєструє жодної можливості, але надає хуки, інструменти або
сервіси, є **застарілим plugin лише з хуками**. Цей шаблон і далі повністю підтримується.

### Позиція щодо зовнішньої сумісності

Модель можливостей уже реалізована в core і сьогодні використовується
вбудованими/власними Plugin, але сумісність із зовнішніми Plugin усе ще
потребує вищої планки, ніж «це експортується, отже це незмінний контракт».

Поточні рекомендації:

- **наявні зовнішні Plugin:** зберігайте працездатність інтеграцій на основі
  хуків; вважайте це базовим рівнем сумісності
- **нові вбудовані/власні Plugin:** надавайте перевагу явній реєстрації
  можливостей замість vendor-specific звернень углиб системи або нових
  реалізацій лише з хуками
- **зовнішні Plugin, що переходять на реєстрацію можливостей:** це дозволено,
  але вважайте допоміжні поверхні для конкретних можливостей такими, що
  еволюціонують, якщо в документації контракт явно не позначено як стабільний

Практичне правило:

- API реєстрації можливостей — це цільовий напрямок
- застарілі хуки залишаються найбезпечнішим шляхом без ризику поломок для
  зовнішніх Plugin під час переходу
- не всі експортовані допоміжні підшляхи однакові; надавайте перевагу
  вузькому задокументованому контракту, а не випадковим допоміжним експортам

### Форми Plugin

OpenClaw класифікує кожен завантажений Plugin за формою на основі його
фактичної поведінки реєстрації, а не лише статичних метаданих:

- **plain-capability** -- реєструє рівно один тип можливості (наприклад,
  Plugin лише провайдера, як-от `mistral`)
- **hybrid-capability** -- реєструє кілька типів можливостей (наприклад,
  `openai` володіє текстовим інференсом, мовленням, розумінням медіа та
  генерацією зображень)
- **hook-only** -- реєструє лише хуки (типізовані або власні), без
  можливостей, інструментів, команд чи сервісів
- **non-capability** -- реєструє інструменти, команди, сервіси або маршрути,
  але не можливості

Використовуйте `openclaw plugins inspect <id>`, щоб побачити форму Plugin і
розподіл можливостей. Докладніше дивіться в [довіднику CLI](/cli/plugins#inspect).

### Застарілі хуки

Хук `before_agent_start` залишається підтримуваним шляхом сумісності для
Plugin лише з хуками. Від нього все ще залежать реальні застарілі Plugin.

Напрямок:

- зберігати його працездатність
- документувати його як застарілий
- для роботи з перевизначенням моделі/провайдера надавати перевагу `before_model_resolve`
- для модифікації prompt надавати перевагу `before_prompt_build`
- вилучати лише після зменшення реального використання і підтвердження
  безпечності міграції покриттям фікстур

### Сигнали сумісності

Коли ви запускаєте `openclaw doctor` або `openclaw plugins inspect <id>`, ви
можете побачити одну з таких міток:

| Сигнал                   | Значення                                                     |
| ------------------------ | ------------------------------------------------------------ |
| **config valid**         | Конфігурація коректно розбирається, а Plugin успішно визначаються |
| **compatibility advisory** | Plugin використовує підтримуваний, але старіший шаблон (наприклад, `hook-only`) |
| **legacy warning**       | Plugin використовує `before_agent_start`, який застарів      |
| **hard error**           | Конфігурація недійсна або Plugin не вдалося завантажити      |

Ні `hook-only`, ні `before_agent_start` сьогодні не зламають ваш Plugin --
`hook-only` є рекомендаційним сигналом, а `before_agent_start` лише спричиняє
попередження. Ці сигнали також відображаються в `openclaw status --all` і
`openclaw plugins doctor`.

## Огляд архітектури

Система Plugin в OpenClaw має чотири шари:

1. **Маніфест + виявлення**
   OpenClaw знаходить кандидатів у Plugin у налаштованих шляхах, коренях
   робочих просторів, глобальних коренях розширень і вбудованих розширеннях.
   Під час виявлення спочатку зчитуються власні маніфести `openclaw.plugin.json`
   разом із підтримуваними маніфестами пакетів.
2. **Увімкнення + валідація**
   Core вирішує, чи є знайдений Plugin увімкненим, вимкненим, заблокованим або
   вибраним для ексклюзивного слота, наприклад пам’яті.
3. **Завантаження часу виконання**
   Власні Plugin OpenClaw завантажуються в межах процесу через jiti і
   реєструють можливості в центральному реєстрі. Сумісні пакети
   нормалізуються в записи реєстру без імпорту коду часу виконання.
4. **Використання поверхонь**
   Решта OpenClaw читає реєстр, щоб надавати інструменти, канали, налаштування
   провайдерів, хуки, HTTP-маршрути, команди CLI та сервіси.

Зокрема для CLI Plugin, виявлення кореневих команд поділено на дві фази:

- метадані під час розбору надходять із `registerCli(..., { descriptors: [...] })`
- фактичний модуль CLI Plugin може залишатися лінивим і реєструватися лише під час першого виклику

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

Важлива межа проєктування:

- виявлення + валідація конфігурації мають працювати на основі **метаданих маніфесту/схеми**
  без виконання коду Plugin
- власна поведінка часу виконання надходить із шляху `register(api)` модуля Plugin

Такий поділ дозволяє OpenClaw валідувати конфігурацію, пояснювати відсутні або
вимкнені Plugin і будувати підказки для UI/схеми ще до повної активації часу виконання.

### Plugin каналів і спільний інструмент повідомлень

Plugin каналів не потрібно реєструвати окремий інструмент send/edit/react для
звичайних дій чату. OpenClaw зберігає один спільний інструмент `message` у core,
а Plugin каналів володіють специфічним для каналу виявленням і виконанням за ним.

Поточна межа така:

- core володіє хостом спільного інструмента `message`, підключенням до prompt,
  веденням сесій/гілок і диспетчеризацією виконання
- Plugin каналів володіють виявленням дій у межах області, виявленням
  можливостей і будь-якими фрагментами схеми, специфічними для каналу
- Plugin каналів володіють граматикою розмови сесії, специфічною для провайдера,
  наприклад тим, як ідентифікатори розмов кодують ідентифікатори гілок або
  успадковуються від батьківських розмов
- Plugin каналів виконують фінальну дію через свій адаптер дій

Для Plugin каналів поверхня SDK — це
`ChannelMessageActionAdapter.describeMessageTool(...)`. Цей уніфікований виклик
виявлення дозволяє Plugin повертати видимі дії, можливості та внески в схему
разом, щоб ці частини не розходилися.

Коли параметр інструмента повідомлень, специфічний для каналу, містить джерело
медіа, наприклад локальний шлях або віддалений URL медіа, Plugin також має
повертати `mediaSourceParams` з `describeMessageTool(...)`. Core використовує
цей явний список, щоб застосовувати нормалізацію шляхів sandbox і підказки
щодо вихідного доступу до медіа без жорсткого кодування імен параметрів,
які належать Plugin.
Там слід надавати перевагу maps з областю дії на рівні дій, а не одному
плоскому списку для всього каналу, щоб параметр медіа лише для профілю не
нормалізувався для не пов’язаних дій, таких як `send`.

Core передає область часу виконання в цей крок виявлення. Важливі поля містять:

- `accountId`
- `currentChannelId`
- `currentThreadTs`
- `currentMessageId`
- `sessionKey`
- `sessionId`
- `agentId`
- довірений вхідний `requesterSenderId`

Це важливо для Plugin, чутливих до контексту. Канал може приховувати або
відкривати дії повідомлень залежно від активного облікового запису, поточної
кімнати/гілки/повідомлення або довіреної ідентичності запитувача без
жорсткого кодування специфічних для каналу гілок у core-інструменті `message`.

Саме тому зміни маршрутизації embedded-runner усе ще належать до роботи Plugin:
runner відповідає за передавання поточної ідентичності чату/сесії в межу
виявлення Plugin, щоб спільний інструмент `message` відкривав правильну
поверхню, яка належить каналу, для поточного ходу.

Для допоміжних засобів виконання, які належать каналу, вбудовані Plugin мають
зберігати runtime виконання у власних модулях розширень. Core більше не
володіє runtime дій повідомлень Discord, Slack, Telegram або WhatsApp у
`src/agents/tools`. Ми не публікуємо окремі підшляхи `plugin-sdk/*-action-runtime`,
а вбудовані Plugin мають імпортувати свій локальний runtime-код напряму з
модулів, що належать їхнім розширенням.

Така сама межа застосовується і до SDK-швів, названих на честь провайдерів, загалом:
core не має імпортувати зручні barrels, специфічні для каналів Slack, Discord,
Signal, WhatsApp або подібних розширень. Якщо core потрібна певна поведінка,
слід або використати власний barrel `api.ts` / `runtime-api.ts` вбудованого Plugin,
або підняти цю потребу до вузької узагальненої можливості в спільному SDK.

Зокрема для опитувань є два шляхи виконання:

- `outbound.sendPoll` — спільна базова реалізація для каналів, що відповідають
  загальній моделі опитувань
- `actions.handleAction("poll")` — бажаний шлях для специфічної для каналу
  семантики опитувань або додаткових параметрів опитування

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

Повну послідовність запуску дивіться в розділі [Конвеєр завантаження](#load-pipeline).

## Модель володіння можливостями

OpenClaw розглядає власний Plugin як межу володіння для **компанії** або
**функції**, а не як набір не пов’язаних інтеграцій.

Це означає:

- Plugin компанії зазвичай має володіти всіма поверхнями OpenClaw, що
  належать цій компанії
- Plugin функції зазвичай має володіти повною поверхнею функції, яку він додає
- канали мають споживати спільні можливості core замість того, щоб довільно
  повторно реалізовувати поведінку провайдерів

Приклади:

- вбудований Plugin `openai` володіє поведінкою провайдера моделей OpenAI і
  поведінкою OpenAI для мовлення + голосу в реальному часі + розуміння медіа + генерації зображень
- вбудований Plugin `elevenlabs` володіє поведінкою мовлення ElevenLabs
- вбудований Plugin `microsoft` володіє поведінкою мовлення Microsoft
- вбудований Plugin `google` володіє поведінкою провайдера моделей Google, а також
  поведінкою Google для розуміння медіа + генерації зображень + вебпошуку
- вбудований Plugin `firecrawl` володіє поведінкою Firecrawl для отримання вебданих
- вбудовані Plugin `minimax`, `mistral`, `moonshot` і `zai` володіють своїми
  backends розуміння медіа
- вбудований Plugin `qwen` володіє поведінкою текстового провайдера Qwen, а також
  поведінкою для розуміння медіа і генерації відео
- Plugin `voice-call` є Plugin функції: він володіє транспортом викликів, інструментами,
  CLI, маршрутами та мостом медіапотоку Twilio, але споживає спільні можливості мовлення,
  а також транскрибування в реальному часі й голосу в реальному часі замість прямого
  імпорту vendor Plugin

Бажаний кінцевий стан:

- OpenAI живе в одному Plugin, навіть якщо він охоплює текстові моделі, мовлення, зображення та
  майбутнє відео
- інший vendor може зробити те саме для власної області поверхонь
- канали не повинні зважати на те, який vendor Plugin володіє провайдером; вони споживають
  спільний контракт можливостей, який надає core

Ось ключова відмінність:

- **plugin** = межа володіння
- **capability** = контракт core, який можуть реалізовувати або споживати кілька Plugin

Тому якщо OpenClaw додає нову область, наприклад відео, перше запитання не таке:
«який провайдер має жорстко закодувати обробку відео?» Перше запитання таке: «який
контракт можливостей відео має бути в core?» Щойно такий контракт з’являється,
vendor Plugin можуть реєструватися для нього, а Plugin каналів/функцій можуть його споживати.

Якщо можливість ще не існує, правильний крок зазвичай такий:

1. визначити відсутню можливість у core
2. відкрити її через API/runtime Plugin у типізований спосіб
3. під’єднати канали/функції до цієї можливості
4. дозволити vendor Plugin реєструвати реалізації

Це зберігає явне володіння й водночас уникає поведінки core, яка залежить від
одного vendor або одноразового шляху коду, специфічного для Plugin.

### Шарування можливостей

Користуйтеся цією ментальною моделлю, коли вирішуєте, де має бути код:

- **шар можливостей core**: спільна оркестрація, політика, fallback, правила
  злиття конфігурації, семантика доставки та типізовані контракти
- **шар vendor Plugin**: API, автентифікація, каталоги моделей, синтез мовлення,
  генерація зображень, майбутні backends відео, endpoints використання, специфічні для vendor
- **шар Plugin каналу/функції**: інтеграція Slack/Discord/voice-call тощо,
  яка споживає можливості core і подає їх на певну поверхню

Наприклад, TTS має таку форму:

- core володіє політикою TTS під час відповіді, порядком fallback, налаштуваннями та доставкою в канал
- `openai`, `elevenlabs` і `microsoft` володіють реалізаціями синтезу
- `voice-call` споживає допоміжний runtime засіб TTS для телефонії

Тому самому шаблону слід надавати перевагу й для майбутніх можливостей.

### Приклад Plugin компанії з кількома можливостями

Plugin компанії має виглядати цілісно ззовні. Якщо OpenClaw має спільні
контракти для моделей, мовлення, транскрибування в реальному часі, голосу в реальному часі,
розуміння медіа, генерації зображень, генерації відео, отримання вебданих і вебпошуку,
vendor може володіти всіма своїми поверхнями в одному місці:

```ts
import type { OpenClawPluginDefinition } from "openclaw/plugin-sdk/plugin-entry";
import {
  describeImageWithModel,
  transcribeOpenAiCompatibleAudio,
} from "openclaw/plugin-sdk/media-understanding";

const plugin: OpenClawPluginDefinition = {
  id: "exampleai",
  name: "ExampleAI",
  register(api) {
    api.registerProvider({
      id: "exampleai",
      // auth/model catalog/runtime hooks
    });

    api.registerSpeechProvider({
      id: "exampleai",
      // vendor speech config — implement the SpeechProviderPlugin interface directly
    });

    api.registerMediaUnderstandingProvider({
      id: "exampleai",
      capabilities: ["image", "audio", "video"],
      async describeImage(req) {
        return describeImageWithModel({
          provider: "exampleai",
          model: req.model,
          input: req.input,
        });
      },
      async transcribeAudio(req) {
        return transcribeOpenAiCompatibleAudio({
          provider: "exampleai",
          model: req.model,
          input: req.input,
        });
      },
    });

    api.registerWebSearchProvider(
      createPluginBackedWebSearchProvider({
        id: "exampleai-search",
        // credential + fetch logic
      }),
    );
  },
};

export default plugin;
```

Важливі не точні назви допоміжних засобів. Важлива форма:

- один Plugin володіє поверхнею vendor
- core, як і раніше, володіє контрактами можливостей
- Plugin каналів і функцій споживають допоміжні засоби `api.runtime.*`, а не код vendor
- тести контрактів можуть перевіряти, що Plugin зареєстрував можливості, якими
  він заявляє, що володіє

### Приклад можливості: розуміння відео

OpenClaw уже розглядає розуміння зображень/аудіо/відео як одну спільну
можливість. Тут застосовується та сама модель володіння:

1. core визначає контракт розуміння медіа
2. vendor Plugin реєструють `describeImage`, `transcribeAudio` і
   `describeVideo`, де це застосовно
3. Plugin каналів і функцій споживають спільну поведінку core замість
   прямого підключення до коду vendor

Це дозволяє не вшивати в core відеоприпущення одного провайдера. Plugin володіє
поверхнею vendor; core володіє контрактом можливості й поведінкою fallback.

Генерація відео вже використовує ту саму послідовність: core володіє типізованим
контрактом можливості та допоміжним runtime-засобом, а vendor Plugin реєструють
реалізації `api.registerVideoGenerationProvider(...)` для цього контракту.

Потрібен конкретний контрольний список розгортання? Дивіться
[Capability Cookbook](/uk/plugins/architecture).

## Контракти та примусове забезпечення

Поверхня API Plugin навмисно типізована й централізована в
`OpenClawPluginApi`. Цей контракт визначає підтримувані точки реєстрації та
допоміжні runtime-засоби, на які може покладатися Plugin.

Чому це важливо:

- автори Plugin отримують єдиний стабільний внутрішній стандарт
- core може відхиляти дубльоване володіння, наприклад коли два Plugin реєструють один і той самий id провайдера
- під час запуску можна показувати діагностику з конкретними порадами для неправильної реєстрації
- тести контрактів можуть забезпечувати володіння вбудованих Plugin і запобігати тихому дрейфу

Є два шари забезпечення:

1. **забезпечення під час runtime-реєстрації**
   Реєстр Plugin перевіряє реєстрації під час завантаження Plugin. Наприклад:
   дубльовані id провайдерів, дубльовані id провайдерів мовлення та
   неправильно сформовані реєстрації призводять до діагностики Plugin, а не до невизначеної поведінки.
2. **тести контрактів**
   Вбудовані Plugin фіксуються в реєстрах контрактів під час виконання тестів, щоб
   OpenClaw міг явно перевіряти володіння. Сьогодні це використовується для
   провайдерів моделей, провайдерів мовлення, провайдерів вебпошуку та
   володіння реєстрацією вбудованих Plugin.

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

### Що має входити до контракту

Хороші контракти Plugin:

- типізовані
- невеликі
- специфічні для можливості
- належать core
- придатні для повторного використання кількома Plugin
- можуть споживатися каналами/функціями без знання про vendor

Погані контракти Plugin:

- політика, специфічна для vendor, прихована в core
- одноразові обхідні шляхи Plugin, які оминають реєстр
- код каналу, що напряму звертається до реалізації vendor
- ad hoc runtime-об’єкти, які не є частиною `OpenClawPluginApi` або
  `api.runtime`

Якщо є сумніви, підніміть рівень абстракції: спочатку визначте можливість, а
потім дозвольте Plugin підключитися до неї.

## Модель виконання

Власні Plugin OpenClaw виконуються **в межах процесу** разом із Gateway. Вони не
ізольовані sandbox-середовищем. Завантажений власний Plugin має ту саму межу
довіри на рівні процесу, що й код core.

Наслідки:

- власний Plugin може реєструвати інструменти, мережеві обробники, хуки та сервіси
- помилка власного Plugin може аварійно завершити роботу gateway або дестабілізувати його
- зловмисний власний Plugin еквівалентний довільному виконанню коду всередині
  процесу OpenClaw

Сумісні пакети за замовчуванням безпечніші, оскільки OpenClaw наразі розглядає їх
як пакети метаданих/вмісту. У поточних релізах це переважно означає
вбудовані Skills.

Для невбудованих Plugin використовуйте allowlists і явні шляхи встановлення/завантаження.
Розглядайте Plugin робочого простору як код для часу розробки, а не як типові production-налаштування.

Для назв пакетів вбудованого робочого простору зберігайте прив’язку id Plugin до npm-імені:
`@openclaw/<id>` за замовчуванням або схвалений типізований суфікс, наприклад
`-provider`, `-plugin`, `-speech`, `-sandbox` або `-media-understanding`, якщо
пакет навмисно відкриває вужчу роль Plugin.

Важлива примітка щодо довіри:

- `plugins.allow` довіряє **id Plugin**, а не походженню джерела.
- Plugin робочого простору з тим самим id, що й у вбудованого Plugin, навмисно затіняє
  вбудовану копію, коли такий Plugin робочого простору ввімкнено/додано до allowlist.
- Це нормально й корисно для локальної розробки, тестування патчів і hotfix-ів.

## Межа експортів

OpenClaw експортує можливості, а не зручні реалізації.

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

- підшляхи допоміжних засобів, специфічні для вбудованих Plugin
- підшляхи runtime-проводки, не призначені як публічний API
- зручні допоміжні засоби, специфічні для vendor
- допоміжні засоби setup/onboarding, що є деталями реалізації

Деякі підшляхи допоміжних засобів вбудованих Plugin і далі залишаються в
згенерованій карті експортів SDK задля сумісності та супроводу вбудованих Plugin.
Поточні приклади містять `plugin-sdk/feishu`, `plugin-sdk/feishu-setup`, `plugin-sdk/zalo`,
`plugin-sdk/zalo-setup` і кілька швів `plugin-sdk/matrix*`. Розглядайте їх як
зарезервовані експорти деталей реалізації, а не як рекомендований шаблон SDK для
нових сторонніх Plugin.

## Конвеєр завантаження

Під час запуску OpenClaw приблизно виконує таке:

1. виявляє корені кандидатів для Plugin
2. зчитує власні маніфести або маніфести сумісних пакетів і метадані пакетів
3. відхиляє небезпечних кандидатів
4. нормалізує конфігурацію Plugin (`plugins.enabled`, `allow`, `deny`, `entries`,
   `slots`, `load.paths`)
5. вирішує стан увімкнення для кожного кандидата
6. завантажує увімкнені власні модулі через jiti
7. викликає власні хуки `register(api)` (або `activate(api)` — застарілий псевдонім) і збирає реєстрації в реєстр Plugin
8. відкриває реєстр для поверхонь команд/runtime

<Note>
`activate` — це застарілий псевдонім для `register` — завантажувач визначає, що саме є (`def.register ?? def.activate`), і викликає це в тій самій точці. Усі вбудовані Plugin використовують `register`; для нових Plugin надавайте перевагу `register`.
</Note>

Перевірки безпеки відбуваються **до** виконання runtime. Кандидати блокуються,
коли точка входу виходить за межі кореня Plugin, шлях є world-writable або
володіння шляхом виглядає підозріло для невбудованих Plugin.

### Поведінка manifest-first

Маніфест — це джерело істини для control plane. OpenClaw використовує його, щоб:

- ідентифікувати Plugin
- виявляти задекларовані канали/Skills/схему конфігурації або можливості пакета
- валідувати `plugins.entries.<id>.config`
- доповнювати мітки/placeholder-значення Control UI
- показувати метадані встановлення/каталогу
- зберігати дешеві дескриптори активації та setup без завантаження runtime Plugin

Для власних Plugin runtime-модуль є частиною data plane. Він реєструє фактичну
поведінку, таку як хуки, інструменти, команди або потоки провайдера.

Необов’язкові блоки маніфесту `activation` і `setup` залишаються в control plane.
Це лише дескриптори метаданих для планування активації та виявлення setup;
вони не замінюють runtime-реєстрацію, `register(...)` або `setupEntry`.
Перші активні споживачі активації тепер використовують підказки маніфесту щодо команд, каналів і провайдерів,
щоб звузити завантаження Plugin до ширшої матеріалізації реєстру:

- завантаження CLI звужується до Plugin, які володіють запитаною основною командою
- визначення setup/Plugin каналу звужується до Plugin, які володіють запитаним
  id каналу
- явне визначення setup/runtime провайдера звужується до Plugin, які володіють
  запитаним id провайдера

Виявлення setup тепер надає перевагу id, що належать дескрипторам, таким як `setup.providers` і
`setup.cliBackends`, щоб звузити коло кандидатів Plugin, перш ніж повертатися до
`setup-api` для Plugin, яким усе ще потрібні runtime-хуки на етапі setup. Якщо більше
ніж один виявлений Plugin заявляє про один і той самий нормалізований id провайдера setup
або backend CLI, пошук setup відмовляється від неоднозначного власника замість того, щоб
покладатися на порядок виявлення.

### Що кешує завантажувач

OpenClaw зберігає короткоживучі кеші в межах процесу для:

- результатів виявлення
- даних реєстру маніфестів
- завантажених реєстрів Plugin

Ці кеші зменшують пікове навантаження під час запуску та повторних викликів команд. Їх безпечно
розглядати як короткоживучі кеші продуктивності, а не як збереження стану.

Примітка щодо продуктивності:

- Установіть `OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1` або
  `OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1`, щоб вимкнути ці кеші.
- Налаштовуйте вікна кешу через `OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS` і
  `OPENCLAW_PLUGIN_MANIFEST_CACHE_MS`.

## Модель реєстру

Завантажені Plugin не змінюють безпосередньо довільні глобальні об’єкти core. Вони
реєструються в центральному реєстрі Plugin.

Реєстр відстежує:

- записи Plugin (ідентичність, джерело, походження, статус, діагностика)
- інструменти
- застарілі хуки й типізовані хуки
- канали
- провайдери
- обробники Gateway RPC
- HTTP-маршрути
- реєстратори CLI
- фонові сервіси
- команди, що належать Plugin

Потім можливості core читають з цього реєстру замість того, щоб напряму взаємодіяти з
модулями Plugin. Це зберігає односторонність завантаження:

- модуль Plugin -> реєстрація в реєстрі
- runtime core -> споживання реєстру

Це розділення важливе для підтримуваності. Воно означає, що більшості поверхонь core
потрібна лише одна точка інтеграції: «читати реєстр», а не «створювати спеціальний випадок для кожного модуля Plugin».

## Зворотні виклики прив’язування розмови

Plugin, які прив’язують розмову, можуть реагувати, коли схвалення завершено.

Використовуйте `api.onConversationBindingResolved(...)`, щоб отримати зворотний виклик після того,
як запит на прив’язування буде схвалено або відхилено:

```ts
export default {
  id: "my-plugin",
  register(api) {
    api.onConversationBindingResolved(async (event) => {
      if (event.status === "approved") {
        // A binding now exists for this plugin + conversation.
        console.log(event.binding?.conversationId);
        return;
      }

      // The request was denied; clear any local pending state.
      console.log(event.request.conversation.conversationId);
    });
  },
};
```

Поля в корисному навантаженні зворотного виклику:

- `status`: `"approved"` або `"denied"`
- `decision`: `"allow-once"`, `"allow-always"` або `"deny"`
- `binding`: узгоджене прив’язування для схвалених запитів
- `request`: початковий зведений запит, підказка від’єднання, id відправника та
  метадані розмови

Цей зворотний виклик є лише сповіщенням. Він не змінює того, кому дозволено прив’язувати
розмову, і виконується після завершення обробки схвалення в core.

## Runtime-хуки провайдера

Plugin провайдерів тепер мають два шари:

- метадані маніфесту: `providerAuthEnvVars` для дешевого пошуку env-auth провайдера
  до завантаження runtime, `providerAuthAliases` для варіантів провайдера, які спільно
  використовують auth, `channelEnvVars` для дешевого пошуку env/setup каналу до
  завантаження runtime, а також `providerAuthChoices` для дешевих міток onboarding/auth-choice і
  метаданих прапорців CLI до завантаження runtime
- хуки часу конфігурації: `catalog` / застарілий `discovery` плюс `applyConfigDefaults`
- runtime-хуки: `normalizeModelId`, `normalizeTransport`,
  `normalizeConfig`,
  `applyNativeStreamingUsageCompat`, `resolveConfigApiKey`,
  `resolveSyntheticAuth`, `resolveExternalAuthProfiles`,
  `shouldDeferSyntheticProfileAuth`,
  `resolveDynamicModel`, `prepareDynamicModel`, `normalizeResolvedModel`,
  `contributeResolvedModelCompat`, `capabilities`,
  `normalizeToolSchemas`, `inspectToolSchemas`,
  `resolveReasoningOutputMode`, `prepareExtraParams`, `createStreamFn`,
  `wrapStreamFn`, `resolveTransportTurnState`,
  `resolveWebSocketSessionPolicy`, `formatApiKey`, `refreshOAuth`,
  `buildAuthDoctorHint`, `matchesContextOverflowError`,
  `classifyFailoverReason`, `isCacheTtlEligible`,
  `buildMissingAuthMessage`, `suppressBuiltInModel`, `augmentModelCatalog`,
  `isBinaryThinking`, `supportsXHighThinking`,
  `resolveDefaultThinkingLevel`, `isModernModelRef`, `prepareRuntimeAuth`,
  `resolveUsageAuth`, `fetchUsageSnapshot`, `createEmbeddingProvider`,
  `buildReplayPolicy`,
  `sanitizeReplayHistory`, `validateReplayTurns`, `onModelSelected`

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

Використовуйте маніфест `providerAuthEnvVars`, коли провайдер має облікові дані на основі env,
які мають бачити узагальнені шляхи auth/status/model-picker без завантаження runtime Plugin.
Використовуйте маніфест `providerAuthAliases`, коли один id провайдера має повторно
використовувати env vars, auth profiles, auth на основі конфігурації та варіант onboarding choice для API-ключа
іншого id провайдера. Використовуйте маніфест `providerAuthChoices`, коли поверхні CLI для onboarding/auth-choice
мають знати choice id провайдера, мітки груп і просту auth-проводку з одним прапорцем
без завантаження runtime провайдера. Зберігайте runtime `envVars` провайдера для операторських підказок,
таких як мітки onboarding або setup vars для OAuth client-id/client-secret.

Використовуйте маніфест `channelEnvVars`, коли канал має auth або setup на основі env, які
мають бачити узагальнений shell-env fallback, перевірки config/status або підказки setup
без завантаження runtime каналу.

### Порядок хуків і використання

Для Plugin моделей/провайдерів OpenClaw викликає хуки приблизно в такому порядку.
Стовпець «Коли використовувати» — це короткий посібник для ухвалення рішень.

| #   | Хук                              | Що він робить                                                                                                  | Коли використовувати                                                                                                                        |
| --- | -------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| 1   | `catalog`                        | Публікує конфігурацію провайдера в `models.providers` під час генерації `models.json`                         | Провайдер володіє каталогом або базовими значеннями `base URL`                                                                              |
| 2   | `applyConfigDefaults`            | Застосовує глобальні значення конфігурації за замовчуванням, які належать провайдеру, під час матеріалізації конфігурації | Значення за замовчуванням залежать від режиму auth, env або семантики сімейства моделей провайдера                                         |
| --  | _(вбудований пошук моделі)_      | OpenClaw спочатку пробує звичайний шлях через реєстр/каталог                                                   | _(це не хук Plugin)_                                                                                                                        |
| 3   | `normalizeModelId`               | Нормалізує застарілі або preview-псевдоніми id моделей перед пошуком                                           | Провайдер володіє очищенням псевдонімів до канонічного визначення моделі                                                                    |
| 4   | `normalizeTransport`             | Нормалізує `api` / `baseUrl` сімейства провайдера перед узагальненим складанням моделі                        | Провайдер володіє очищенням транспорту для власних id провайдера в межах того самого сімейства транспорту                                  |
| 5   | `normalizeConfig`                | Нормалізує `models.providers.<id>` до визначення runtime/провайдера                                            | Провайдеру потрібне очищення конфігурації, яке має жити разом із Plugin; допоміжні засоби вбудованого сімейства Google також страхують підтримувані записи конфігурації Google |
| 6   | `applyNativeStreamingUsageCompat` | Застосовує compat-переписування native streaming usage до конфігурації провайдерів                            | Провайдеру потрібні виправлення метаданих native streaming usage, що залежать від endpoint                                                 |
| 7   | `resolveConfigApiKey`            | Визначає auth на основі env-marker для конфігураційних провайдерів до завантаження runtime auth               | Провайдер має власне визначення API-ключа через env-marker; `amazon-bedrock` також має тут вбудований resolver AWS env-marker              |
| 8   | `resolveSyntheticAuth`           | Виводить локальний/self-hosted або auth на основі конфігурації без збереження відкритого тексту               | Провайдер може працювати із synthetic/local маркером облікових даних                                                                        |
| 9   | `resolveExternalAuthProfiles`    | Накладає зовнішні профілі auth, що належать провайдеру; типове значення `persistence` — `runtime-only` для облікових даних, що належать CLI/app | Провайдер повторно використовує зовнішні облікові дані auth без збереження скопійованих refresh token                                      |
| 10  | `shouldDeferSyntheticProfileAuth` | Понижує пріоритет збережених synthetic placeholder-профілів порівняно з auth на основі env/config            | Провайдер зберігає synthetic placeholder-профілі, які не повинні мати вищий пріоритет                                                      |
| 11  | `resolveDynamicModel`            | Синхронний fallback для id моделей, що належать провайдеру, але ще відсутні в локальному реєстрі              | Провайдер приймає довільні id моделей від upstream                                                                                          |
| 12  | `prepareDynamicModel`            | Асинхронний прогрів, після чого `resolveDynamicModel` запускається знову                                       | Провайдеру потрібні мережеві метадані до визначення невідомих id                                                                            |
| 13  | `normalizeResolvedModel`         | Остаточне переписування перед тим, як embedded runner використає визначену модель                              | Провайдеру потрібні переписування транспорту, але при цьому він усе ще використовує транспорт core                                         |
| 14  | `contributeResolvedModelCompat`  | Додає compat-прапорці для моделей vendor за іншим сумісним транспортом                                         | Провайдер розпізнає власні моделі на proxy-транспортах, не перебираючи на себе провайдера                                                  |
| 15  | `capabilities`                   | Метадані транскрипту/інструментів, що належать провайдеру, і використовуються спільною логікою core          | Провайдеру потрібні особливості транскрипту/сімейства провайдера                                                                            |
| 16  | `normalizeToolSchemas`           | Нормалізує схеми інструментів до того, як їх побачить embedded runner                                          | Провайдеру потрібне очищення схем для сімейства транспорту                                                                                  |
| 17  | `inspectToolSchemas`             | Показує діагностику схем, що належить провайдеру, після нормалізації                                           | Провайдер хоче мати попередження щодо ключових слів без навчання core правилам, специфічним для провайдера                                |
| 18  | `resolveReasoningOutputMode`     | Вибирає native чи tagged-контракт виводу reasoning                                                             | Провайдеру потрібен tagged reasoning/final output замість native-полів                                                                      |
| 19  | `prepareExtraParams`             | Нормалізація параметрів запиту до застосування узагальнених обгорток параметрів потоку                         | Провайдеру потрібні параметри запиту за замовчуванням або очищення параметрів для конкретного провайдера                                   |
| 20  | `createStreamFn`                 | Повністю замінює звичайний шлях потоку власним транспортом                                                     | Провайдеру потрібен власний wire protocol, а не просто обгортка                                                                             |
| 21  | `wrapStreamFn`                   | Обгортка потоку після застосування узагальнених обгорток                                                       | Провайдеру потрібні обгортки сумісності заголовків/тіла/моделі запиту без власного транспорту                                              |
| 22  | `resolveTransportTurnState`      | Прикріплює native-заголовки транспорту або метадані для кожного ходу                                           | Провайдер хоче, щоб узагальнені транспорти надсилали native-ідентичність ходу провайдера                                                   |
| 23  | `resolveWebSocketSessionPolicy`  | Прикріплює native-заголовки WebSocket або політику cooling-down сесії                                          | Провайдер хоче, щоб узагальнені WS-транспорти налаштовували заголовки сесії або політику fallback                                          |
| 24  | `formatApiKey`                   | Форматер профілю auth: збережений профіль стає runtime-рядком `apiKey`                                         | Провайдер зберігає додаткові метадані auth і потребує власної форми runtime-токена                                                         |
| 25  | `refreshOAuth`                   | Перевизначення оновлення OAuth для власних endpoint оновлення або політики помилки оновлення                  | Провайдер не відповідає спільним refreshers `pi-ai`                                                                                         |
| 26  | `buildAuthDoctorHint`            | Підказка виправлення, що додається, коли оновлення OAuth не вдається                                           | Провайдеру потрібна власна порада з відновлення auth після помилки оновлення                                                               |
| 27  | `matchesContextOverflowError`    | Matcher переповнення контекстного вікна, що належить провайдеру                                                | Провайдер має сирі помилки переповнення, які узагальнені евристики пропустили б                                                            |
| 28  | `classifyFailoverReason`         | Класифікація причини failover, що належить провайдеру                                                          | Провайдер може зіставляти сирі API/транспортні помилки з rate-limit, overload тощо                                                         |
| 29  | `isCacheTtlEligible`             | Політика prompt-cache для proxy/backhaul-провайдерів                                                           | Провайдеру потрібне керування TTL кешу, специфічне для proxy                                                                                |
| 30  | `buildMissingAuthMessage`        | Замінник узагальненого повідомлення відновлення за відсутності auth                                            | Провайдеру потрібна власна підказка відновлення за відсутності auth                                                                         |
| 31  | `suppressBuiltInModel`           | Приховування застарілих upstream-моделей плюс необов’язкова підказка про помилку для користувача              | Провайдеру потрібно приховати застарілі upstream-рядки або замінити їх підказкою від vendor                                                |
| 32  | `augmentModelCatalog`            | Synthetic/остаточні рядки каталогу, додані після виявлення                                                     | Провайдеру потрібні synthetic-рядки прямої сумісності в `models list` і вибирачах                                                          |
| 33  | `isBinaryThinking`               | Перемикач reasoning увімк./вимк. для провайдерів із binary-thinking                                            | Провайдер відкриває лише binary thinking on/off                                                                                             |
| 34  | `supportsXHighThinking`          | Підтримка reasoning `xhigh` для вибраних моделей                                                               | Провайдер хоче `xhigh` лише для підмножини моделей                                                                                          |
| 35  | `resolveDefaultThinkingLevel`    | Типовий рівень `/think` для конкретного сімейства моделей                                                      | Провайдер володіє типовою політикою `/think` для сімейства моделей                                                                          |
| 36  | `isModernModelRef`               | Matcher сучасної моделі для фільтрів live profile і вибору smoke                                               | Провайдер володіє зіставленням preferred-model для live/smoke                                                                               |
| 37  | `prepareRuntimeAuth`             | Обмінює налаштовані облікові дані на фактичний runtime-токен/ключ безпосередньо перед інференсом             | Провайдеру потрібен обмін токена або короткоживучі облікові дані запиту                                                                     |
| 38  | `resolveUsageAuth`               | Визначає облікові дані usage/billing для `/usage` і пов’язаних поверхонь статусу                               | Провайдеру потрібен власний парсер токенів usage/quota або інші облікові дані для usage                                                   |
| 39  | `fetchUsageSnapshot`             | Отримує та нормалізує знімки usage/quota, специфічні для провайдера, після визначення auth                     | Провайдеру потрібен endpoint usage, специфічний для провайдера, або парсер корисного навантаження                                         |
| 40  | `createEmbeddingProvider`        | Створює адаптер embedding, що належить провайдеру, для пам’яті/пошуку                                          | Поведінка memory embedding має належати Plugin провайдера                                                                                  |
| 41  | `buildReplayPolicy`              | Повертає політику replay, яка керує обробкою транскриптів для провайдера                                       | Провайдеру потрібна власна політика транскриптів (наприклад, видалення блоків thinking)                                                   |
| 42  | `sanitizeReplayHistory`          | Переписує історію replay після узагальненого очищення транскрипту                                              | Провайдеру потрібні власні переписування replay, специфічні для провайдера, понад спільні допоміжні засоби Compaction                    |
| 43  | `validateReplayTurns`            | Остаточна валідація або переформатування ходів replay перед embedded runner                                    | Транспорту провайдера потрібна суворіша валідація ходів після узагальненої санітизації                                                    |
| 44  | `onModelSelected`                | Запускає побічні ефекти після вибору моделі, що належать провайдеру                                            | Провайдеру потрібна телеметрія або стан, що належить провайдеру, коли модель стає активною                                               |

`normalizeModelId`, `normalizeTransport` і `normalizeConfig` спочатку перевіряють
відповідний Plugin провайдера, а потім переходять до інших Plugin провайдерів, здатних обробляти хуки,
доки один із них справді не змінить id моделі або transport/config. Це зберігає
працездатність shim-рішень alias/compat для провайдерів без вимоги, щоб викликач знав,
який саме вбудований Plugin володіє переписуванням. Якщо жоден хук провайдера не переписує
підтримуваний запис конфігурації сімейства Google, нормалізатор конфігурації вбудованого Google
усе одно застосовує це сумісне очищення.

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

### Приклад провайдера

```ts
api.registerProvider({
  id: "example-proxy",
  label: "Example Proxy",
  auth: [],
  catalog: {
    order: "simple",
    run: async (ctx) => {
      const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey;
      if (!apiKey) {
        return null;
      }
      return {
        provider: {
          baseUrl: "https://proxy.example.com/v1",
          apiKey,
          api: "openai-completions",
          models: [{ id: "auto", name: "Auto" }],
        },
      };
    },
  },
  resolveDynamicModel: (ctx) => ({
    id: ctx.modelId,
    name: ctx.modelId,
    provider: "example-proxy",
    api: "openai-completions",
    baseUrl: "https://proxy.example.com/v1",
    reasoning: false,
    input: ["text"],
    cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
    contextWindow: 128000,
    maxTokens: 8192,
  }),
  prepareRuntimeAuth: async (ctx) => {
    const exchanged = await exchangeToken(ctx.apiKey);
    return {
      apiKey: exchanged.token,
      baseUrl: exchanged.baseUrl,
      expiresAt: exchanged.expiresAt,
    };
  },
  resolveUsageAuth: async (ctx) => {
    const auth = await ctx.resolveOAuthToken();
    return auth ? { token: auth.token } : null;
  },
  fetchUsageSnapshot: async (ctx) => {
    return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn);
  },
});
```

### Вбудовані приклади

- Anthropic використовує `resolveDynamicModel`, `capabilities`, `buildAuthDoctorHint`,
  `resolveUsageAuth`, `fetchUsageSnapshot`, `isCacheTtlEligible`,
  `resolveDefaultThinkingLevel`, `applyConfigDefaults`, `isModernModelRef`
  і `wrapStreamFn`, оскільки він володіє прямою сумісністю з Claude 4.6,
  підказками сімейства провайдера, вказівками з відновлення auth, інтеграцією
  з endpoint usage, придатністю prompt-cache, значеннями конфігурації за замовчуванням,
  що враховують auth, типовою/адаптивною політикою thinking для Claude і
  формуванням потоку, специфічним для Anthropic, для beta headers,
  `/fast` / `serviceTier` та `context1m`.
- Допоміжні засоби потоку Anthropic, специфічні для Claude, поки що залишаються у
  власному публічному шві `api.ts` / `contract-api.ts` вбудованого Plugin. Ця
  поверхня пакета експортує `wrapAnthropicProviderStream`, `resolveAnthropicBetas`,
  `resolveAnthropicFastMode`, `resolveAnthropicServiceTier` і низькорівневі
  засоби побудови обгорток Anthropic замість розширення узагальненого SDK
  навколо правил beta headers одного провайдера.
- OpenAI використовує `resolveDynamicModel`, `normalizeResolvedModel` і
  `capabilities`, а також `buildMissingAuthMessage`, `suppressBuiltInModel`,
  `augmentModelCatalog`, `supportsXHighThinking` і `isModernModelRef`,
  оскільки він володіє прямою сумісністю з GPT-5.4, прямою нормалізацією OpenAI
  `openai-completions` -> `openai-responses`, підказками auth з урахуванням Codex,
  приглушенням Spark, synthetic-рядками списку OpenAI та політикою thinking /
  live-model для GPT-5; сімейство потоків `openai-responses-defaults` володіє
  спільними native-обгортками OpenAI Responses для attribution headers,
  `/fast`/`serviceTier`, багатослівності тексту, native вебпошуку Codex,
  формуванням корисного навантаження reasoning-compat і керуванням контекстом Responses.
- OpenRouter використовує `catalog` разом із `resolveDynamicModel` і
  `prepareDynamicModel`, оскільки провайдер є наскрізним і може відкривати нові
  id моделей раніше, ніж оновиться статичний каталог OpenClaw; він також використовує
  `capabilities`, `wrapStreamFn` і `isCacheTtlEligible`, щоб
  заголовки запитів, метадані маршрутизації, патчі reasoning і
  політика prompt-cache, специфічні для провайдера, не потрапляли в core. Його політика replay надходить із
  сімейства `passthrough-gemini`, тоді як сімейство потоків `openrouter-thinking`
  володіє ін’єкцією reasoning через proxy і пропусками для непідтримуваних моделей / `auto`.
- GitHub Copilot використовує `catalog`, `auth`, `resolveDynamicModel` і
  `capabilities`, а також `prepareRuntimeAuth` і `fetchUsageSnapshot`, оскільки йому
  потрібні вхід через пристрій, що належить провайдеру, поведінка fallback моделей,
  особливості транскриптів Claude, обмін GitHub token -> Copilot token і
  endpoint usage, що належить провайдеру.
- OpenAI Codex використовує `catalog`, `resolveDynamicModel`,
  `normalizeResolvedModel`, `refreshOAuth` і `augmentModelCatalog`, а також
  `prepareExtraParams`, `resolveUsageAuth` і `fetchUsageSnapshot`, оскільки він
  усе ще працює на транспортах core OpenAI, але володіє нормалізацією свого
  transport/base URL, політикою fallback для оновлення OAuth, типовим вибором
  транспорту, synthetic-рядками каталогу Codex та інтеграцією з endpoint usage ChatGPT;
  він використовує те саме сімейство потоків `openai-responses-defaults`, що й прямий OpenAI.
- Google AI Studio та Gemini CLI OAuth використовують `resolveDynamicModel`,
  `buildReplayPolicy`, `sanitizeReplayHistory`,
  `resolveReasoningOutputMode`, `wrapStreamFn` і `isModernModelRef`, оскільки
  сімейство replay `google-gemini` володіє fallback прямої сумісності для Gemini 3.1,
  native-валідацією replay Gemini, санітизацією bootstrap replay, режимом
  tagged reasoning-output і зіставленням сучасних моделей, тоді як
  сімейство потоків `google-thinking` володіє нормалізацією payload thinking Gemini;
  Gemini CLI OAuth також використовує `formatApiKey`, `resolveUsageAuth` і
  `fetchUsageSnapshot` для форматування токена, розбору токена і
  проводки endpoint quota.
- Anthropic Vertex використовує `buildReplayPolicy` через
  сімейство replay `anthropic-by-model`, щоб очищення replay, специфічне для Claude, залишалося
  в межах id Claude, а не кожного транспорту `anthropic-messages`.
- Amazon Bedrock використовує `buildReplayPolicy`, `matchesContextOverflowError`,
  `classifyFailoverReason` і `resolveDefaultThinkingLevel`, оскільки він володіє
  класифікацією помилок throttle/not-ready/context-overflow, специфічною для Bedrock,
  для трафіку Anthropic-on-Bedrock; його політика replay і далі використовує той самий
  guard `anthropic-by-model`, призначений лише для Claude.
- OpenRouter, Kilocode, Opencode і Opencode Go використовують `buildReplayPolicy`
  через сімейство replay `passthrough-gemini`, оскільки вони проксіюють моделі Gemini
  через транспорти, сумісні з OpenAI, і потребують санітизації thought-signature Gemini
  без native-валідації replay Gemini або переписувань bootstrap.
- MiniMax використовує `buildReplayPolicy` через
  сімейство replay `hybrid-anthropic-openai`, оскільки один провайдер володіє як
  семантикою повідомлень Anthropic, так і OpenAI-сумісною семантикою; він зберігає
  відкидання блоків thinking лише для Claude на боці Anthropic, водночас
  перевизначаючи режим виводу reasoning назад на native, а сімейство потоків
  `minimax-fast-mode` володіє переписуванням моделей fast-mode на спільному шляху потоку.
- Moonshot використовує `catalog` разом із `wrapStreamFn`, оскільки він і далі використовує спільний
  транспорт OpenAI, але потребує нормалізації payload thinking, що належить провайдеру; сімейство потоків
  `moonshot-thinking` відображає config і стан `/think` у його native payload binary thinking.
- Kilocode використовує `catalog`, `capabilities`, `wrapStreamFn` і
  `isCacheTtlEligible`, оскільки йому потрібні заголовки запитів, що належать провайдеру,
  нормалізація payload reasoning, підказки транскриптів Gemini та керування
  TTL кешу Anthropic; сімейство потоків `kilocode-thinking` утримує ін’єкцію thinking Kilo
  на спільному шляху proxy-потоку, водночас пропускаючи `kilo/auto` та інші proxy id моделей,
  які не підтримують явні payload reasoning.
- Z.AI використовує `resolveDynamicModel`, `prepareExtraParams`, `wrapStreamFn`,
  `isCacheTtlEligible`, `isBinaryThinking`, `isModernModelRef`,
  `resolveUsageAuth` і `fetchUsageSnapshot`, оскільки він володіє fallback для GLM-5,
  значеннями `tool_stream` за замовчуванням, UX binary thinking, зіставленням сучасних моделей
  і як auth для usage, так і отриманням quota; сімейство потоків `tool-stream-default-on`
  утримує обгортку `tool_stream`, увімкнену за замовчуванням, поза вручну написаним glue-кодом для кожного провайдера.
- xAI використовує `normalizeResolvedModel`, `normalizeTransport`,
  `contributeResolvedModelCompat`, `prepareExtraParams`, `wrapStreamFn`,
  `resolveSyntheticAuth`, `resolveDynamicModel` і `isModernModelRef`,
  оскільки він володіє нормалізацією native-транспорту xAI Responses, переписуванням
  псевдонімів fast-mode для Grok, типовим `tool_stream`, очищенням strict-tool / reasoning-payload,
  повторним використанням fallback auth для інструментів, що належать Plugin,
  прямим визначенням моделей Grok і compat-патчами, що належать провайдеру, такими як профіль
  schema інструментів xAI, непідтримувані ключові слова schema, native `web_search` і декодування
  аргументів виклику інструментів із HTML-сутностями.
- Mistral, OpenCode Zen і OpenCode Go використовують лише `capabilities`, щоб
  не виносити особливості транскриптів/інструментів у core.
- Вбудовані провайдери лише з каталогом, такі як `byteplus`, `cloudflare-ai-gateway`,
  `huggingface`, `kimi-coding`, `nvidia`, `qianfan`,
  `synthetic`, `together`, `venice`, `vercel-ai-gateway` і `volcengine`, використовують
  лише `catalog`.
- Qwen використовує `catalog` для свого текстового провайдера разом зі спільними реєстраціями
  розуміння медіа й генерації відео для своїх мультимодальних поверхонь.
- MiniMax і Xiaomi використовують `catalog` разом із usage-хуками, оскільки їхня поведінка `/usage`
  належить Plugin, хоча сам інференс і далі виконується через спільні транспорти.

## Допоміжні засоби runtime

Plugin можуть отримувати доступ до вибраних допоміжних засобів core через `api.runtime`. Для TTS:

```ts
const clip = await api.runtime.tts.textToSpeech({
  text: "Hello from OpenClaw",
  cfg: api.config,
});

const result = await api.runtime.tts.textToSpeechTelephony({
  text: "Hello from OpenClaw",
  cfg: api.config,
});

const voices = await api.runtime.tts.listVoices({
  provider: "elevenlabs",
  cfg: api.config,
});
```

Примітки:

- `textToSpeech` повертає звичайне корисне навантаження виводу TTS із core для поверхонь файлів/голосових нотаток.
- Використовує конфігурацію core `messages.tts` і вибір провайдера.
- Повертає PCM-аудіобуфер + sample rate. Plugin мають виконати повторну дискретизацію/кодування для провайдерів.
- `listVoices` є необов’язковим для кожного провайдера. Використовуйте його для вибирачів голосу або потоків setup, що належать vendor.
- Списки голосів можуть містити багатші метадані, такі як locale, gender і теги personality для вибирачів, обізнаних про провайдера.
- OpenAI і ElevenLabs сьогодні підтримують телефонію. Microsoft — ні.

Plugin також можуть реєструвати провайдери мовлення через `api.registerSpeechProvider(...)`.

```ts
api.registerSpeechProvider({
  id: "acme-speech",
  label: "Acme Speech",
  isConfigured: ({ config }) => Boolean(config.messages?.tts),
  synthesize: async (req) => {
    return {
      audioBuffer: Buffer.from([]),
      outputFormat: "mp3",
      fileExtension: ".mp3",
      voiceCompatible: false,
    };
  },
});
```

Примітки:

- Зберігайте політику TTS, fallback і доставку відповіді в core.
- Використовуйте провайдери мовлення для поведінки синтезу, що належить vendor.
- Застарілий вхід Microsoft `edge` нормалізується до id провайдера `microsoft`.
- Бажана модель володіння є орієнтованою на компанію: один vendor Plugin може володіти
  текстовими, мовленнєвими, графічними та майбутніми медіапровайдерами, коли OpenClaw додає відповідні
  контракти можливостей.

Для розуміння зображень/аудіо/відео Plugin реєструють один типізований
провайдер розуміння медіа, а не узагальнений набір ключ/значення:

```ts
api.registerMediaUnderstandingProvider({
  id: "google",
  capabilities: ["image", "audio", "video"],
  describeImage: async (req) => ({ text: "..." }),
  transcribeAudio: async (req) => ({ text: "..." }),
  describeVideo: async (req) => ({ text: "..." }),
});
```

Примітки:

- Зберігайте оркестрацію, fallback, конфігурацію та підключення каналів у core.
- Зберігайте поведінку vendor у Plugin провайдера.
- Адитивне розширення має залишатися типізованим: нові необов’язкові методи, нові необов’язкові
  поля результату, нові необов’язкові можливості.
- Генерація відео вже дотримується того самого шаблону:
  - core володіє контрактом можливості та допоміжним runtime-засобом
  - vendor Plugin реєструють `api.registerVideoGenerationProvider(...)`
  - Plugin функцій/каналів споживають `api.runtime.videoGeneration.*`

Для допоміжних runtime-засобів розуміння медіа Plugin можуть викликати:

```ts
const image = await api.runtime.mediaUnderstanding.describeImageFile({
  filePath: "/tmp/inbound-photo.jpg",
  cfg: api.config,
  agentDir: "/tmp/agent",
});

const video = await api.runtime.mediaUnderstanding.describeVideoFile({
  filePath: "/tmp/inbound-video.mp4",
  cfg: api.config,
});
```

Для транскрибування аудіо Plugin можуть використовувати або runtime
розуміння медіа, або старіший псевдонім STT:

```ts
const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
  filePath: "/tmp/inbound-audio.ogg",
  cfg: api.config,
  // Optional when MIME cannot be inferred reliably:
  mime: "audio/ogg",
});
```

Примітки:

- `api.runtime.mediaUnderstanding.*` — це бажана спільна поверхня для
  розуміння зображень/аудіо/відео.
- Використовує конфігурацію аудіо розуміння медіа з core (`tools.media.audio`) і порядок fallback провайдерів.
- Повертає `{ text: undefined }`, коли вихід транскрибування не створюється (наприклад, у разі пропущеного/непідтримуваного вводу).
- `api.runtime.stt.transcribeAudioFile(...)` залишається псевдонімом для сумісності.

Plugin також можуть запускати фонові виконання subagent через `api.runtime.subagent`:

```ts
const result = await api.runtime.subagent.run({
  sessionKey: "agent:main:subagent:search-helper",
  message: "Expand this query into focused follow-up searches.",
  provider: "openai",
  model: "gpt-4.1-mini",
  deliver: false,
});
```

Примітки:

- `provider` і `model` — це необов’язкові перевизначення для окремого запуску, а не постійні зміни сесії.
- OpenClaw враховує ці поля перевизначення лише для довірених викликачів.
- Для запусків fallback, що належать Plugin, оператори мають явно дозволити це через `plugins.entries.<id>.subagent.allowModelOverride: true`.
- Використовуйте `plugins.entries.<id>.subagent.allowedModels`, щоб обмежити довірені Plugin конкретними канонічними цілями `provider/model`, або `"*"`, щоб явно дозволити будь-яку ціль.
- Виконання subagent для недовірених Plugin усе одно працюють, але запити на перевизначення відхиляються, а не тихо переходять до fallback.

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

```ts
const providers = api.runtime.webSearch.listProviders({
  config: api.config,
});

const result = await api.runtime.webSearch.search({
  config: api.config,
  args: {
    query: "OpenClaw plugin runtime helpers",
    count: 5,
  },
});
```

Plugin також можуть реєструвати провайдери вебпошуку через
`api.registerWebSearchProvider(...)`.

Примітки:

- Зберігайте вибір провайдера, визначення облікових даних і спільну семантику запитів у core.
- Використовуйте провайдери вебпошуку для транспортів пошуку, специфічних для vendor.
- `api.runtime.webSearch.*` — це бажана спільна поверхня для Plugin функцій/каналів, яким потрібна поведінка пошуку без залежності від обгортки інструментів агента.

### `api.runtime.imageGeneration`

```ts
const result = await api.runtime.imageGeneration.generate({
  config: api.config,
  args: { prompt: "A friendly lobster mascot", size: "1024x1024" },
});

const providers = api.runtime.imageGeneration.listProviders({
  config: api.config,
});
```

- `generate(...)`: генерує зображення, використовуючи налаштований ланцюжок провайдерів генерації зображень.
- `listProviders(...)`: перелічує доступні провайдери генерації зображень та їхні можливості.

## HTTP-маршрути Gateway

Plugin можуть відкривати HTTP-endpoint-и через `api.registerHttpRoute(...)`.

```ts
api.registerHttpRoute({
  path: "/acme/webhook",
  auth: "plugin",
  match: "exact",
  handler: async (_req, res) => {
    res.statusCode = 200;
    res.end("ok");
    return true;
  },
});
```

Поля маршруту:

- `path`: шлях маршруту в HTTP-сервері gateway.
- `auth`: обов’язкове поле. Використовуйте `"gateway"`, щоб вимагати звичайну auth Gateway, або `"plugin"` для auth/webhook verification, якими керує Plugin.
- `match`: необов’язкове поле. `"exact"` (типово) або `"prefix"`.
- `replaceExisting`: необов’язкове поле. Дозволяє тому самому Plugin замінити власну наявну реєстрацію маршруту.
- `handler`: повертає `true`, якщо маршрут обробив запит.

Примітки:

- `api.registerHttpHandler(...)` вилучено, і він спричинить помилку завантаження Plugin. Натомість використовуйте `api.registerHttpRoute(...)`.
- Маршрути Plugin мають явно оголошувати `auth`.
- Конфлікти точного `path + match` відхиляються, якщо не вказано `replaceExisting: true`, і один Plugin не може замінити маршрут іншого Plugin.
- Маршрути, що перекриваються та мають різні рівні `auth`, відхиляються. Зберігайте ланцюги проходження `exact`/`prefix` лише в межах одного рівня auth.
- Маршрути `auth: "plugin"` **не** отримують автоматично runtime-scopes оператора. Вони призначені для webhook-ів/перевірки підписів, якими керує Plugin, а не для привілейованих допоміжних викликів Gateway.
- Маршрути `auth: "gateway"` працюють у межах runtime-scope запиту Gateway, але цей scope навмисно консервативний:
  - bearer auth зі спільним секретом (`gateway.auth.mode = "token"` / `"password"`) утримує runtime-scopes маршрутів Plugin зафіксованими на `operator.write`, навіть якщо викликач надсилає `x-openclaw-scopes`
  - довірені HTTP-режими з ідентичністю (наприклад, `trusted-proxy` або `gateway.auth.mode = "none"` у приватному ingress) враховують `x-openclaw-scopes` лише тоді, коли цей заголовок явно присутній
  - якщо `x-openclaw-scopes` відсутній у таких запитах до маршрутів Plugin з ідентичністю, runtime-scope переходить до `operator.write`
- Практичне правило: не вважайте маршрут Plugin із gateway-auth неявною admin-поверхнею. Якщо вашому маршруту потрібна поведінка лише для admin, вимагайте режим auth із носієм ідентичності та задокументуйте явний контракт заголовка `x-openclaw-scopes`.

## Шляхи імпорту Plugin SDK

Під час створення Plugin використовуйте підшляхи SDK замість монолітного імпорту `openclaw/plugin-sdk`:

- `openclaw/plugin-sdk/plugin-entry` для примітивів реєстрації Plugin.
- `openclaw/plugin-sdk/core` для узагальненого спільного контракту, орієнтованого на Plugin.
- `openclaw/plugin-sdk/config-schema` для експорту кореневої схеми Zod `openclaw.json`
  (`OpenClawSchema`).
- Стабільні примітиви каналів, такі як `openclaw/plugin-sdk/channel-setup`,
  `openclaw/plugin-sdk/setup-runtime`,
  `openclaw/plugin-sdk/setup-adapter-runtime`,
  `openclaw/plugin-sdk/setup-tools`,
  `openclaw/plugin-sdk/channel-pairing`,
  `openclaw/plugin-sdk/channel-contract`,
  `openclaw/plugin-sdk/channel-feedback`,
  `openclaw/plugin-sdk/channel-inbound`,
  `openclaw/plugin-sdk/channel-lifecycle`,
  `openclaw/plugin-sdk/channel-reply-pipeline`,
  `openclaw/plugin-sdk/command-auth`,
  `openclaw/plugin-sdk/secret-input` і
  `openclaw/plugin-sdk/webhook-ingress` для спільного підключення setup/auth/reply/webhook.
  `channel-inbound` — це спільне місце для debounce, зіставлення згадок,
  допоміжних засобів політики вхідних згадок, форматування envelope і
  допоміжних засобів контексту вхідного envelope.
  `channel-setup` — це вузький шов setup для необов’язкового встановлення.
  `setup-runtime` — це безпечна для runtime поверхня setup, яка використовується `setupEntry` /
  відкладеним запуском, зокрема безпечними для імпорту адаптерами патчів setup.
  `setup-adapter-runtime` — це env-aware шов адаптера setup облікового запису.
  `setup-tools` — це малий шов допоміжних засобів CLI/архівів/документації (`formatCliCommand`,
  `detectBinary`, `extractArchive`, `resolveBrewExecutable`, `formatDocsLink`,
  `CONFIG_DIR`).
- Доменні підшляхи, такі як `openclaw/plugin-sdk/channel-config-helpers`,
  `openclaw/plugin-sdk/allow-from`,
  `openclaw/plugin-sdk/channel-config-schema`,
  `openclaw/plugin-sdk/telegram-command-config`,
  `openclaw/plugin-sdk/channel-policy`,
  `openclaw/plugin-sdk/approval-gateway-runtime`,
  `openclaw/plugin-sdk/approval-handler-adapter-runtime`,
  `openclaw/plugin-sdk/approval-handler-runtime`,
  `openclaw/plugin-sdk/approval-runtime`,
  `openclaw/plugin-sdk/config-runtime`,
  `openclaw/plugin-sdk/infra-runtime`,
  `openclaw/plugin-sdk/agent-runtime`,
  `openclaw/plugin-sdk/lazy-runtime`,
  `openclaw/plugin-sdk/reply-history`,
  `openclaw/plugin-sdk/routing`,
  `openclaw/plugin-sdk/status-helpers`,
  `openclaw/plugin-sdk/text-runtime`,
  `openclaw/plugin-sdk/runtime-store` і
  `openclaw/plugin-sdk/directory-runtime` для спільних допоміжних засобів runtime/config.
  `telegram-command-config` — це вузький публічний шов для нормалізації/валідації
  власних команд Telegram і залишається доступним навіть якщо поверхня контракту вбудованого
  Telegram тимчасово недоступна.
  `text-runtime` — це спільний шов тексту/markdown/логування, зокрема
  видалення видимого для асистента тексту, допоміжні засоби рендерингу/розбиття markdown, засоби редагування,
  засоби directive-tag і утиліти безпечного тексту.
- Специфічним для схвалення швам каналу слід надавати перевагу одному контракту
  `approvalCapability` у Plugin. Тоді core зчитує auth схвалення, доставку, рендеринг,
  native-routing і ліниву поведінку native-handler через цю одну можливість
  замість змішування поведінки схвалення з не пов’язаними полями Plugin.
- `openclaw/plugin-sdk/channel-runtime` застарів і залишається лише як
  shim сумісності для старіших Plugin. Новий код має імпортувати натомість вужчі
  узагальнені примітиви, а код репозиторію не повинен додавати нові імпорти цього shim.
- Внутрішні механізми вбудованих розширень залишаються приватними. Зовнішні Plugin повинні використовувати лише підшляхи `openclaw/plugin-sdk/*`. Код core/test OpenClaw може використовувати публічні точки входу репозиторію в корені пакета Plugin, такі як `index.js`, `api.js`,
  `runtime-api.js`, `setup-entry.js` і вузькоспрямовані файли, такі як
  `login-qr-api.js`. Ніколи не імпортуйте `src/*` пакета Plugin з core або з
  іншого розширення.
- Поділ точок входу репозиторію:
  `<plugin-package-root>/api.js` — це barrel допоміжних засобів/типів,
  `<plugin-package-root>/runtime-api.js` — це barrel лише для runtime,
  `<plugin-package-root>/index.js` — це точка входу вбудованого Plugin,
  а `<plugin-package-root>/setup-entry.js` — це точка входу Plugin для setup.
- Поточні приклади вбудованих провайдерів:
  - Anthropic використовує `api.js` / `contract-api.js` для допоміжних засобів потоку Claude, таких
    як `wrapAnthropicProviderStream`, засоби для beta-header і розбір `service_tier`.
  - OpenAI використовує `api.js` для builder-ів провайдера, допоміжних засобів типових моделей і
    builder-ів провайдера realtime.
  - OpenRouter використовує `api.js` для свого builder-а провайдера разом із допоміжними
    засобами onboarding/config, тоді як `register.runtime.js` усе ще може повторно експортувати узагальнені
    допоміжні засоби `plugin-sdk/provider-stream` для локального використання в репозиторії.
- Публічні точки входу, завантажені через facade, надають перевагу активному знімку конфігурації runtime,
  коли він існує, а потім переходять до визначеного файла конфігурації на диску, коли
  OpenClaw ще не обслуговує знімок runtime.
- Узагальнені спільні примітиви залишаються бажаним публічним контрактом SDK. Невеликий
  зарезервований набір брендових допоміжних швів вбудованих каналів для сумісності все ще існує.
  Розглядайте їх як шви для супроводу вбудованих рішень/сумісності, а не як нові цілі імпорту для сторонніх рішень;
  нові міжканальні контракти, як і раніше, мають з’являтися у вузьких підшляхах
  `plugin-sdk/*` або в локальних barrel `api.js` /
  `runtime-api.js` самого Plugin.

Примітка щодо сумісності:

- Уникайте кореневого barrel `openclaw/plugin-sdk` у новому коді.
- Спочатку надавайте перевагу вузьким стабільним примітивам. Новіші підшляхи
  setup/pairing/reply/feedback/contract/inbound/threading/command/secret-input/webhook/infra/
  allowlist/status/message-tool є цільовим контрактом для нової роботи з
  вбудованими та зовнішніми Plugin.
  Розбір/зіставлення цілей має належати `openclaw/plugin-sdk/channel-targets`.
  Обмеження дій повідомлень і допоміжні засоби message-id для реакцій мають належати
  `openclaw/plugin-sdk/channel-actions`.
- Barrels допоміжних засобів, специфічні для вбудованих розширень, за замовчуванням не є стабільними. Якщо
  допоміжний засіб потрібен лише вбудованому розширенню, тримайте його за локальним швом
  `api.js` або `runtime-api.js` цього розширення замість того, щоб просувати його в
  `openclaw/plugin-sdk/<extension>`.
- Нові спільні шви допоміжних засобів мають бути узагальненими, а не брендовими для каналу. Спільний розбір
  цілей має належати `openclaw/plugin-sdk/channel-targets`; внутрішні механізми, специфічні для каналу,
  залишаються за локальним швом `api.js` або `runtime-api.js` Plugin-власника.
- Підшляхи, специфічні для можливостей, такі як `image-generation`,
  `media-understanding` і `speech`, існують, бо вбудовані/власні Plugin
  уже використовують їх сьогодні. Їхня наявність сама по собі не означає, що кожен
  експортований допоміжний засіб є довгостроковим незмінним зовнішнім контрактом.

## Схеми інструмента повідомлень

Plugin мають володіти внесками до схеми `describeMessageTool(...)`, специфічними для каналу.
Зберігайте поля, специфічні для провайдера, у Plugin, а не в спільному core.

Для спільних переносних фрагментів схем повторно використовуйте узагальнені допоміжні засоби,
експортовані через `openclaw/plugin-sdk/channel-actions`:

- `createMessageToolButtonsSchema()` для payload у стилі сітки кнопок
- `createMessageToolCardSchema()` для структурованих payload карток

Якщо форма схеми має сенс лише для одного провайдера, визначайте її у власному
коді цього Plugin замість просування в спільний SDK.

## Визначення цілей каналу

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

- `messaging.inferTargetChatType({ to })` визначає, чи нормалізовану ціль
  слід трактувати як `direct`, `group` або `channel` до пошуку в директорії.
- `messaging.targetResolver.looksLikeId(raw, normalized)` повідомляє core, чи
  слід вводу одразу перейти до визначення як id-подібної цілі, а не до пошуку в директорії.
- `messaging.targetResolver.resolveTarget(...)` — це fallback Plugin, коли
  core потребує остаточного визначення, що належить провайдеру, після нормалізації або
  після промаху в директорії.
- `messaging.resolveOutboundSessionRoute(...)` володіє побудовою маршруту сесії, специфічного для провайдера,
  після того як ціль уже визначена.

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

- Використовуйте `inferTargetChatType` для категорійних рішень, які мають відбуватися до
  пошуку peer/group.
- Використовуйте `looksLikeId` для перевірок «трактувати це як явний/native id цілі».
- Використовуйте `resolveTarget` для fallback-нормалізації, специфічної для провайдера, а не для
  широкого пошуку в директорії.
- Зберігайте native id провайдера, такі як id чату, id гілки, JID, handle та id кімнати,
  всередині значень `target` або параметрів, специфічних для провайдера, а не в узагальнених полях SDK.

## Директорії на основі конфігурації

Plugin, які виводять записи директорії з конфігурації, мають зберігати цю логіку в
Plugin і повторно використовувати спільні допоміжні засоби з
`openclaw/plugin-sdk/directory-runtime`.

Використовуйте це, коли каналу потрібні peer/group на основі конфігурації, наприклад:

- peer DM на основі allowlist
- налаштовані maps каналів/group
- fallback статичної директорії в межах облікового запису

Спільні допоміжні засоби в `directory-runtime` обробляють лише узагальнені операції:

- фільтрацію запитів
- застосування ліміту
- допоміжні засоби deduping/normalization
- побудову `ChannelDirectoryEntry[]`

Інспекція облікових записів і нормалізація id, специфічні для каналу, мають залишатися
в реалізації Plugin.

## Каталоги провайдерів

Plugin провайдерів можуть визначати каталоги моделей для інференсу за допомогою
`registerProvider({ catalog: { run(...) { ... } } })`.

`catalog.run(...)` повертає ту саму форму, яку OpenClaw записує в
`models.providers`:

- `{ provider }` для одного запису провайдера
- `{ providers }` для кількох записів провайдерів

Використовуйте `catalog`, коли Plugin володіє id моделей, базовими значеннями URL або
метаданими моделей, захищеними auth, специфічними для провайдера.

`catalog.order` керує тим, коли каталог Plugin зливається відносно
вбудованих неявних провайдерів OpenClaw:

- `simple`: прості провайдери з API-ключем або на основі env
- `profile`: провайдери, які з’являються, коли існують профілі auth
- `paired`: провайдери, які синтезують кілька пов’язаних записів провайдерів
- `late`: останній прохід, після інших неявних провайдерів

Пізніші провайдери перемагають у разі колізії ключів, тому Plugin можуть
навмисно перевизначити вбудований запис провайдера з тим самим id провайдера.

Сумісність:

- `discovery` усе ще працює як застарілий псевдонім
- якщо зареєстровано і `catalog`, і `discovery`, OpenClaw використовує `catalog`

## Інспекція каналу лише для читання

Якщо ваш Plugin реєструє канал, надавайте перевагу реалізації
`plugin.config.inspectAccount(cfg, accountId)` разом із `resolveAccount(...)`.

Чому:

- `resolveAccount(...)` — це шлях runtime. Йому дозволено припускати, що облікові дані
  повністю матеріалізовані, і швидко завершуватися з помилкою, якщо потрібних секретів бракує.
- Шляхи команд лише для читання, такі як `openclaw status`, `openclaw status --all`,
  `openclaw channels status`, `openclaw channels resolve`, а також потоки doctor/config
  repair не повинні вимагати матеріалізації runtime-облікових даних лише для опису конфігурації.

Рекомендована поведінка `inspectAccount(...)`:

- Повертайте лише описовий стан облікового запису.
- Зберігайте `enabled` і `configured`.
- Додавайте поля джерела/стану облікових даних, коли це доречно, наприклад:
  - `tokenSource`, `tokenStatus`
  - `botTokenSource`, `botTokenStatus`
  - `appTokenSource`, `appTokenStatus`
  - `signingSecretSource`, `signingSecretStatus`
- Вам не потрібно повертати сирі значення токенів лише для повідомлення про
  доступність у режимі лише читання. Для команд у стилі status достатньо повертати `tokenStatus: "available"`
  (і відповідне поле джерела).
- Використовуйте `configured_unavailable`, коли облікові дані налаштовані через SecretRef, але
  недоступні в поточному шляху команди.

Це дозволяє командам лише для читання повідомляти «налаштовано, але недоступно в цьому
шляху команди» замість аварійного завершення або хибного повідомлення, що обліковий запис не налаштований.

## Пакети-pack

Каталог Plugin може містити `package.json` з `openclaw.extensions`:

```json
{
  "name": "my-pack",
  "openclaw": {
    "extensions": ["./src/safety.ts", "./src/tools.ts"],
    "setupEntry": "./src/setup-entry.ts"
  }
}
```

Кожен запис стає Plugin. Якщо pack перелічує кілька розширень, id Plugin
стає `name/<fileBase>`.

Якщо ваш Plugin імпортує npm-залежності, встановіть їх у цьому каталозі, щоб
`node_modules` був доступний (`npm install` / `pnpm install`).

Захисне обмеження безпеки: кожен запис `openclaw.extensions` має залишатися в межах каталогу Plugin
після визначення symlink. Записи, які виходять за межі каталогу пакета,
відхиляються.

Примітка щодо безпеки: `openclaw plugins install` встановлює залежності Plugin через
`npm install --omit=dev --ignore-scripts` (без lifecycle scripts і без dev-залежностей під час runtime). Зберігайте дерева залежностей Plugin «чистими JS/TS» і уникайте пакетів, яким потрібні збірки через `postinstall`.

Необов’язково: `openclaw.setupEntry` може вказувати на легкий модуль лише для setup.
Коли OpenClaw потребує поверхонь setup для вимкненого Plugin каналу або
коли Plugin каналу ввімкнений, але ще не налаштований, він завантажує `setupEntry`
замість повної точки входу Plugin. Це зберігає запуск і setup легшими,
коли основна точка входу Plugin також підключає інструменти, хуки чи інший код лише для runtime.

Необов’язково: `openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen`
може перевести Plugin каналу на той самий шлях `setupEntry` під час
фази запуску gateway до початку прослуховування, навіть якщо канал уже налаштований.

Використовуйте це лише тоді, коли `setupEntry` повністю покриває поверхню запуску, яка має існувати
до того, як gateway почне прослуховувати. На практиці це означає, що точка входу setup
має реєструвати кожну можливість, що належить каналу, від якої залежить запуск, наприклад:

- саму реєстрацію каналу
- будь-які HTTP-маршрути, які мають бути доступні до того, як gateway почне прослуховувати
- будь-які методи Gateway, інструменти або сервіси, які мають існувати в тому самому вікні

Якщо ваша повна точка входу все ще володіє будь-якою потрібною можливістю запуску, не вмикайте
цей прапорець. Залиште Plugin на типовій поведінці й дозвольте OpenClaw завантажити
повну точку входу під час запуску.

Вбудовані канали також можуть публікувати допоміжні засоби поверхні контракту лише для setup, які core
може використовувати до завантаження повного runtime каналу. Поточна поверхня просування setup така:

- `singleAccountKeysToMove`
- `namedAccountPromotionKeys`
- `resolveSingleAccountPromotionTarget(...)`

Core використовує цю поверхню, коли йому потрібно просунути застарілу конфігурацію каналу з одним обліковим записом у
`channels.<id>.accounts.*` без завантаження повної точки входу Plugin.
Matrix — поточний вбудований приклад: він переміщує лише ключі auth/bootstrap у
іменований просунутий обліковий запис, коли іменовані облікові записи вже існують, і може
зберегти налаштований неканонічний ключ типового облікового запису замість того, щоб завжди створювати
`accounts.default`.

Ці адаптери патчів setup зберігають ліниве виявлення поверхні контракту вбудованих рішень. Час імпорту
залишається малим; поверхня просування завантажується лише під час першого використання замість
повторного входу в запуск вбудованого каналу під час імпорту модуля.

Коли ці поверхні запуску містять методи Gateway RPC, зберігайте їх на
префіксі, специфічному для Plugin. Простори імен admin core (`config.*`,
`exec.approvals.*`, `wizard.*`, `update.*`) залишаються зарезервованими й завжди визначаються
як `operator.admin`, навіть якщо Plugin запитує вужчий scope.

Приклад:

```json
{
  "name": "@scope/my-channel",
  "openclaw": {
    "extensions": ["./index.ts"],
    "setupEntry": "./setup-entry.ts",
    "startup": {
      "deferConfiguredChannelFullLoadUntilAfterListen": true
    }
  }
}
```

### Метадані каталогу каналів

Plugin каналів можуть оголошувати метадані setup/discovery через `openclaw.channel` і
підказки встановлення через `openclaw.install`. Це дозволяє не тримати дані каталогу в core.

Приклад:

```json
{
  "name": "@openclaw/nextcloud-talk",
  "openclaw": {
    "extensions": ["./index.ts"],
    "channel": {
      "id": "nextcloud-talk",
      "label": "Nextcloud Talk",
      "selectionLabel": "Nextcloud Talk (self-hosted)",
      "docsPath": "/channels/nextcloud-talk",
      "docsLabel": "nextcloud-talk",
      "blurb": "Self-hosted chat via Nextcloud Talk webhook bots.",
      "order": 65,
      "aliases": ["nc-talk", "nc"]
    },
    "install": {
      "npmSpec": "@openclaw/nextcloud-talk",
      "localPath": "<bundled-plugin-local-path>",
      "defaultChoice": "npm"
    }
  }
}
```

Корисні поля `openclaw.channel`, окрім мінімального прикладу:

- `detailLabel`: вторинна мітка для багатших поверхонь каталогу/статусу
- `docsLabel`: перевизначення тексту посилання для посилання на документацію
- `preferOver`: id Plugin/каналів із нижчим пріоритетом, які цей запис каталогу має перевершувати
- `selectionDocsPrefix`, `selectionDocsOmitLabel`, `selectionExtras`: керування текстом поверхні вибору
- `markdownCapable`: позначає канал як сумісний із markdown для рішень форматування вихідних повідомлень
- `exposure.configured`: приховує канал із поверхонь списку налаштованих каналів, якщо встановлено `false`
- `exposure.setup`: приховує канал з інтерактивних вибирачів setup/configure, якщо встановлено `false`
- `exposure.docs`: позначає канал як внутрішній/приватний для поверхонь навігації документації
- `showConfigured` / `showInSetup`: застарілі псевдоніми, які все ще приймаються для сумісності; надавайте перевагу `exposure`
- `quickstartAllowFrom`: додає канал до стандартного потоку quickstart `allowFrom`
- `forceAccountBinding`: вимагає явного прив’язування облікового запису, навіть якщо існує лише один обліковий запис
- `preferSessionLookupForAnnounceTarget`: надає перевагу пошуку сесії під час визначення announce target

OpenClaw також може зливати **зовнішні каталоги каналів** (наприклад, експорт
реєстру MPM). Розмістіть JSON-файл в одному з таких місць:

- `~/.openclaw/mpm/plugins.json`
- `~/.openclaw/mpm/catalog.json`
- `~/.openclaw/plugins/catalog.json`

Або вкажіть `OPENCLAW_PLUGIN_CATALOG_PATHS` (або `OPENCLAW_MPM_CATALOG_PATHS`) на
один чи кілька JSON-файлів (розділених комою/крапкою з комою/роздільником `PATH`). Кожен файл має
містити `{ "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }`. Парсер також приймає `"packages"` або `"plugins"` як застарілі псевдоніми для ключа `"entries"`.

## Plugin рушія контексту

Plugin рушія контексту володіють оркестрацією контексту сесії для ingest, assembly
і Compaction. Реєструйте їх зі свого Plugin через
`api.registerContextEngine(id, factory)`, а потім вибирайте активний рушій через
`plugins.slots.contextEngine`.

Використовуйте це, коли вашому Plugin потрібно замінити або розширити типовий
конвеєр контексту, а не просто додати пошук у пам’яті чи хуки.

```ts
import { buildMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";

export default function (api) {
  api.registerContextEngine("lossless-claw", () => ({
    info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
    async ingest() {
      return { ingested: true };
    },
    async assemble({ messages, availableTools, citationsMode }) {
      return {
        messages,
        estimatedTokens: 0,
        systemPromptAddition: buildMemorySystemPromptAddition({
          availableTools: availableTools ?? new Set(),
          citationsMode,
        }),
      };
    },
    async compact() {
      return { ok: true, compacted: false };
    },
  }));
}
```

Якщо ваш рушій **не** володіє алгоритмом Compaction, залишайте `compact()`
реалізованим і явно делегуйте його:

```ts
import {
  buildMemorySystemPromptAddition,
  delegateCompactionToRuntime,
} from "openclaw/plugin-sdk/core";

export default function (api) {
  api.registerContextEngine("my-memory-engine", () => ({
    info: {
      id: "my-memory-engine",
      name: "My Memory Engine",
      ownsCompaction: false,
    },
    async ingest() {
      return { ingested: true };
    },
    async assemble({ messages, availableTools, citationsMode }) {
      return {
        messages,
        estimatedTokens: 0,
        systemPromptAddition: buildMemorySystemPromptAddition({
          availableTools: availableTools ?? new Set(),
          citationsMode,
        }),
      };
    },
    async compact(params) {
      return await delegateCompactionToRuntime(params);
    },
  }));
}
```

## Додавання нової можливості

Коли Plugin потребує поведінки, яка не вписується в поточний API, не обходьте
систему Plugin через приватне звернення всередину. Додайте відсутню можливість.

Рекомендована послідовність:

1. визначте контракт core
   Вирішіть, якою спільною поведінкою має володіти core: політика, fallback, злиття конфігурації,
   життєвий цикл, семантика для каналів і форма допоміжного runtime-засобу.
2. додайте типізовані поверхні реєстрації/runtime для Plugin
   Розширте `OpenClawPluginApi` і/або `api.runtime` найменшою корисною
   типізованою поверхнею можливості.
3. під’єднайте споживачів core + каналів/функцій
   Канали та Plugin функцій мають споживати нову можливість через core,
   а не імпортуючи реалізацію vendor напряму.
4. зареєструйте реалізації vendor
   Потім vendor Plugin реєструють свої backends для цієї можливості.
5. додайте покриття контрактів
   Додайте тести, щоб володіння і форма реєстрації з часом залишалися явними.

Саме так OpenClaw зберігає чітку архітектурну позицію, не стаючи жорстко прив’язаним до
світогляду одного провайдера. Дивіться [Capability Cookbook](/uk/plugins/architecture)
для конкретного контрольного списку файлів і готового прикладу.

### Контрольний список можливості

Коли ви додаєте нову можливість, реалізація зазвичай має одночасно торкатися
таких поверхонь:

- типи контракту core в `src/<capability>/types.ts`
- runner/допоміжний runtime-засіб core в `src/<capability>/runtime.ts`
- поверхня реєстрації API Plugin в `src/plugins/types.ts`
- підключення реєстру Plugin в `src/plugins/registry.ts`
- відкриття runtime Plugin у `src/plugins/runtime/*`, коли Plugin функцій/каналів
  мають це споживати
- допоміжні засоби capture/test у `src/test-utils/plugin-registration.ts`
- перевірки володіння/контрактів у `src/plugins/contracts/registry.ts`
- документація для операторів/Plugin у `docs/`

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

### Шаблон можливості

Мінімальний шаблон:

```ts
// core contract
export type VideoGenerationProviderPlugin = {
  id: string;
  label: string;
  generateVideo: (req: VideoGenerationRequest) => Promise<VideoGenerationResult>;
};

// plugin API
api.registerVideoGenerationProvider({
  id: "openai",
  label: "OpenAI",
  async generateVideo(req) {
    return await generateOpenAiVideo(req);
  },
});

// shared runtime helper for feature/channel plugins
const clip = await api.runtime.videoGeneration.generate({
  prompt: "Show the robot walking through the lab.",
  cfg,
});
```

Шаблон тесту контракту:

```ts
expect(findVideoGenerationProviderIdsForPlugin("openai")).toEqual(["openai"]);
```

Це зберігає правило простим:

- core володіє контрактом можливості + оркестрацією
- vendor Plugin володіють реалізаціями vendor
- Plugin функцій/каналів споживають допоміжні runtime-засоби
- тести контрактів зберігають явність володіння
