Building plugins

Хуки Pluginів

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

Натомість використовуйте внутрішні хуки для невеликого встановленого оператором скрипту HOOK.md, який реагує на події команд і Gateway, як-от /new, /reset, /stop, agent:bootstrap або gateway:startup.

Швидкий старт

Зареєструйте типізовані хуки за допомогою api.on(...) у точці входу плагіна:

typescript
 export default definePluginEntry({  id: "tool-preflight",  name: "Tool Preflight",  register(api) {    api.on(      "before_tool_call",      async (event) => {        if (event.toolName !== "web_search") {          return;        }         return {          requireApproval: {            title: "Запустити вебпошук",            description: `Дозволити пошуковий запит: ${String(event.params.query ?? "")}`,            severity: "info",            timeoutMs: 60_000,          },        };      },      { priority: 50 },    );  },});

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

api.on(name, handler, opts?) приймає:

Параметр Ефект
priority Порядок виконання; вище значення виконується першим.
timeoutMs Бюджет очікування для окремого хука. Після його вичерпання OpenClaw припиняє очікувати на цей обробник і продовжує роботу. Це не скасовує обробник або його побічні ефекти. Не вказуйте, щоб використовувати стандартний тайм-аут засобу виконання для окремого хука.

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

json
{  "plugins": {    "entries": {      "my-plugin": {        "hooks": {          "timeoutMs": 30000,          "timeouts": {            "before_prompt_build": 90000,            "agent_end": 60000          }        }      }    }  }}

hooks.timeouts.<hookName> перевизначає hooks.timeoutMs, а той перевизначає значення api.on(..., { timeoutMs }), задане автором плагіна. Кожне значення має бути додатним цілим числом до 600000 мс. Для хуків із відомою повільною роботою надавайте перевагу окремим перевизначенням, щоб один плагін не отримував довший бюджет усюди.

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

Вихідні хуки-модифікатори message_sending і reply_payload_sending за замовчуванням мають 15 секунд на кожен обробник. Якщо час одного з них спливає, OpenClaw записує помилку плагіна в журнал і продовжує роботу з останнім корисним навантаженням, щоб серіалізований канал доставки міг завершити роботу. Установіть більший бюджет для окремого хука для плагінів, які навмисно виконують повільнішу роботу перед доставкою.

Плагіни каналів, які використовують createReplyDispatcher, так само можуть оголосити більший додатний бюджет для окремого етапу за допомогою beforeDeliverOptions: { timeoutMs } або під час додавання роботи за допомогою dispatcher.appendBeforeDeliver(handler, { timeoutMs }). Без бюджету, оголошеного власником, ці зворотні виклики використовують ті самі стандартні 15 секунд, щоб завислий зворотний виклик не міг утримувати серіалізований канал доставки.

Кожен хук отримує event.context.pluginConfig — визначену конфігурацію плагіна, який зареєстрував цей обробник. OpenClaw впроваджує її окремо для кожного обробника, не змінюючи спільний об’єкт події, який бачать інші плагіни.

Каталог хуків

Хуки згруповано за поверхнею, яку вони розширюють. Назви, виділені жирним, приймають результат рішення (блокування, скасування, перевизначення або вимогу схвалення); решта призначені лише для спостереження.

Хід агента

Хук Призначення
before_model_resolve Перевизначити постачальника або модель до завантаження повідомлень сеансу
agent_turn_prepare Обробити поставлені в чергу вставки ходу від плагінів і додати контекст того самого ходу перед хуками запиту
before_prompt_build Додати динамічний контекст або текст системного запиту перед викликом моделі
before_agent_start Комбінований етап лише для сумісності; надавайте перевагу двом хукам вище
before_agent_run Перевірити остаточний запит і повідомлення сеансу перед надсиланням моделі; може заблокувати запуск
before_agent_reply Достроково завершити хід моделі синтетичною відповіддю або без відповіді
before_agent_finalize Перевірити природну остаточну відповідь і запросити ще один прохід моделі
agent_end Спостерігати за остаточними повідомленнями, станом успіху й тривалістю запуску
heartbeat_prompt_contribution Додати контекст лише для Heartbeat для плагінів фонового моніторингу та життєвого циклу

Спостереження за розмовою

Хук Призначення
model_call_started / model_call_ended Очищені метадані виклику постачальника/моделі: час, результат, обмежені хеші ідентифікаторів запитів. Без вмісту запиту або відповіді.
llm_input Вхідні дані постачальника: системний запит, запит, історія
llm_output Вихідні дані постачальника, використання та визначений contextTokenBudget, якщо доступний

Інструменти

Хук Призначення
before_tool_call Переписати параметри інструмента, заблокувати виконання або вимагати схвалення
after_tool_call Спостерігати за результатами інструмента, помилками й тривалістю
resolve_exec_env Додати змінні середовища, якими володіє плагін, до exec
tool_result_persist Переписати повідомлення асистента, створене з результату інструмента
before_message_write Перевірити або заблокувати запис повідомлення, що виконується (рідко)

Повідомлення та доставка

Хук Призначення
inbound_claim Перехопити вхідне повідомлення до маршрутизації агента (синтетичні відповіді)
channel_pairing_requested Спостерігати за новоствореними запитами на сполучення приватних повідомлень
message_received Спостерігати за вхідним вмістом, відправником, гілкою та метаданими
message_sending Переписати вихідний вміст або скасувати доставку
reply_payload_sending Змінити або скасувати нормалізоване корисне навантаження відповіді перед доставкою
message_sent Спостерігати за успіхом або невдачею вихідної доставки
before_dispatch Перевірити або переписати вихідну диспетчеризацію перед передаванням каналу
reply_dispatch Брати участь у кінцевому конвеєрі диспетчеризації відповіді

Сеанси та Compaction

Хук Призначення
session_start / session_end Відстежувати межі життєвого циклу сеансу. reason — одне зі значень new, reset, idle, daily, compaction, deleted, shutdown, restart або unknown. shutdown/restart спрацьовують із фіналізатора завершення роботи Gateway, коли процес зупиняється або перезапускається з активними сеансами, щоб плагіни (пам’яті, сховищ транскриптів) могли завершити фантомні рядки, а не залишати їх відкритими між перезапусками. Фіналізатор обмежений у часі, щоб повільний плагін не міг блокувати SIGTERM/SIGINT.
before_compaction / after_compaction Спостерігати за циклами Compaction або додавати до них анотації
before_reset Спостерігати за подіями скидання сеансу (/reset, програмні скидання)

Субагенти

  • subagent_spawned / subagent_ended — спостереження за запуском і завершенням підагента.
  • subagent_delivery_target — механізм сумісності для доставлення результату завершення, коли прив’язка основного сеансу не може спроєктувати маршрут.
  • subagent_spawning — застарілий механізм сумісності. Тепер ядро готує прив’язки підагентів thread: true через адаптери прив’язки сеансів каналів до спрацювання subagent_spawned.
  • subagent_spawned містить resolvedModel і resolvedProvider, коли OpenClaw визначив нативну модель дочірнього сеансу перед запуском.
  • subagent_ended передає targetSessionKey (ідентичність — відповідає subagent_spawned.childSessionKey), targetKind ("subagent" або "acp"), reason, необов’язковий outcome ("ok", "error", "timeout", "killed", "reset" або "deleted"), необов’язковий error, runId, endedAt, accountId і sendFarewell. Він не містить agentId або childSessionKey; використовуйте targetSessionKey для зіставлення з відповідною подією subagent_spawned.

Життєвий цикл

Обробник Призначення
gateway_start / gateway_stop Запуск або зупинення служб, якими володіє Plugin, разом із Gateway
deactivate Застарілий псевдонім сумісності для gateway_stop; у нових плагінах використовуйте gateway_stop
cron_reconciled Узгодження з повним станом Cron Gateway після запуску або перезавантаження
cron_changed Спостереження за змінами життєвого циклу Cron, яким володіє Gateway (додано, оновлено, видалено, запущено, завершено, заплановано)
before_install Перевірка підготовлених матеріалів для встановлення навички або плагіна із завантаженого середовища виконання плагіна

Запити на сполучення каналу

Використовуйте channel_pairing_requested, коли плагін має сповістити оператора або записати аудиторський запис після того, як несполучений відправник приватного повідомлення створює запит на сполучення, що очікує на розгляд. Обробник викликається під час створення запиту; повільні обробники або обробники з помилками не затримують доставлення каналом відповіді щодо сполучення.

typescript
api.on("channel_pairing_requested", async (event) => {  await notifyOperator({    text: `Новий запит на сполучення ${event.channel} від ${event.senderId}: ${event.code}`,  });});

Обробник призначений лише для спостереження. Він не схвалює, не відхиляє, не приховує й не переписує відповідь щодо сполучення. Корисне навантаження містить канал, необов’язковий accountId, обмежений каналом senderId, code сполучення та метадані каналу. Вважайте код сполучення чинними одноразовими обліковими даними схвалення й передавайте його лише до довіреного приймача оператора. Вважайте metadata ненадійним текстом ідентичності, наданим відправником. Обробник не містить тіла або медіавмісту вхідного повідомлення.

Обробники налагодження середовища виконання

Використовуйте before_model_resolve, щоб змінити постачальника або модель для ходу агента — він виконується до визначення моделі. llm_output виконується лише після того, як спроба моделі створить вивід асистента.

Щоб підтвердити фактичну модель сеансу, перевірте реєстрації середовища виконання, а потім використайте openclaw sessions або поверхні сеансу/стану Gateway. Щоб налагодити корисні навантаження постачальника, запустіть Gateway з --raw-stream і --raw-stream-path <path>, щоб записувати необроблені події потоку моделі у файл jsonl.

Політика виклику інструментів

before_tool_call отримує:

  • event.toolName
  • event.params
  • необов’язкові event.toolKind і event.toolInputKind — авторитетні для хоста дискримінатори для інструментів, які навмисно мають однакові назви; наприклад, зовнішні виклики exec у режимі коду використовують toolKind: "code_mode_exec" і містять toolInputKind: "javascript" | "typescript", коли мова введення відома
  • необов’язковий event.derivedPaths — орієнтовні підказки щодо цільових шляхів, отримані хостом, для відомих оболонок інструментів, як-от apply_patch; ці шляхи можуть бути неповними або надмірно наближено описувати те, чого інструмент фактично торкнеться (наприклад, за некоректних або часткових вхідних даних)
  • необов’язковий event.runId
  • необов’язковий event.toolCallId
  • поля контексту, як-от ctx.agentId, ctx.sessionKey, ctx.sessionId, ctx.runId, ctx.toolKind, ctx.toolInputKind і діагностичний ctx.trace

Він може повертати:

typescript
type BeforeToolCallResult = {  params?: Record<string, unknown>;  block?: boolean;  blockReason?: string;  requireApproval?: {    title: string;    description: string;    severity?: "info" | "warning" | "critical";    timeoutMs?: number;    /** @deprecated Нерозв’язані запити на схвалення завжди відхиляються. */    timeoutBehavior?: "allow" | "deny";    allowedDecisions?: Array<"allow-once" | "allow-always" | "deny">;    pluginId?: string;    onResolution?: (      decision: "allow-once" | "allow-always" | "deny" | "timeout" | "cancelled",    ) => Promise<void> | void;  };};

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

  • block: true є кінцевим і пропускає обробники з нижчим пріоритетом.
  • block: false трактується як відсутність рішення.
  • params переписує параметри інструмента для виконання.
  • requireApproval призупиняє виконання агента й запитує користувача через механізм схвалень плагіна. /approve може схвалювати як виконання команд, так і запити плагінів. У нативних ретрансляціях PreToolUse у режимі звіту сервера застосунку Codex це передається відповідному запиту на схвалення сервера застосунку; див. середовище виконання оболонки Codex.
  • Обробник block: true з нижчим пріоритетом усе ще може заблокувати виклик після того, як обробник із вищим пріоритетом запитав схвалення.
  • onResolution отримує остаточне рішення: allow-once, allow-always, deny, timeout або cancelled.

Відомості про маршрутизацію схвалень, поведінку рішень і випадки використання requireApproval замість необов’язкових інструментів або схвалень виконання див. у розділі Запити дозволів плагінів.

Плагіни, яким потрібна політика на рівні хоста, можуть реєструвати довірені політики інструментів через api.registerTrustedToolPolicy(...). Вони виконуються перед звичайними обробниками before_tool_call і перед звичайними рішеннями обробників. Спочатку виконуються вбудовані довірені політики; далі — довірені політики встановлених плагінів у порядку завантаження плагінів; після них — звичайні обробники before_tool_call. Вбудовані плагіни зберігають наявний шлях довірених політик. Установлені плагіни потрібно явно ввімкнути, і вони мають оголосити кожен ідентифікатор політики в contracts.trustedToolPolicies; неоголошені ідентифікатори відхиляються до реєстрації. Ідентифікатори політик обмежені плагіном, який їх реєструє, тому різні плагіни можуть повторно використовувати той самий локальний ідентифікатор. Використовуйте цей рівень лише для довірених хостом обмежень, як-от політика робочого простору, контроль бюджету або безпека зарезервованих робочих процесів.

Обробник середовища виконання команд

resolve_exec_env дає змогу плагінам додавати змінні середовища до викликів інструмента exec до виконання команди. Він отримує:

  • event.sessionKey
  • event.toolName, наразі завжди "exec"
  • event.host, одне зі значень "gateway", "sandbox" або "node"
  • поля контексту, як-от ctx.agentId, ctx.sessionKey, ctx.messageProvider і ctx.channelId

Поверніть Record<string, string> для об’єднання із середовищем виконання команд. Обробники виконуються за пріоритетом; пізніші результати замінюють попередні для того самого ключа.

Вивід обробника перед об’єднанням фільтрується за політикою ключів середовища виконання команд хоста. PATH завжди відкидається (від нього залежать визначення команд і перевірки безпечних двійкових файлів). Некоректні ключі та небезпечні ключі перевизначення хоста, як-от LD_*, DYLD_*, NODE_OPTIONS, змінні проксі (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY) і змінні перевизначення TLS (NODE_TLS_REJECT_UNAUTHORIZED, SSL_CERT_FILE та подібні), відкидаються. Відфільтроване середовище плагіна додається до метаданих схвалення й аудиту Gateway та пересилається до запитів виконання на вузлі-хості.

Збереження результатів інструментів

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

  • OpenClaw вилучає toolResult.details перед повторним відтворенням для постачальника та передаванням на вхід Compaction, щоб метадані не стали контекстом моделі.
  • Збережені записи сеансу залишають лише обмежені details. Завеликі подробиці замінюються стислим підсумком і persistedDetailsTruncated: true.
  • tool_result_persist і before_message_write виконуються до застосування остаточного обмеження збереження. Зберігайте повернені details малими й не розміщуйте текст, важливий для запиту, лише в details; вивід інструмента, видимий моделі, розміщуйте в content.

Обробники запитів і моделей

Для нових плагінів використовуйте обробники окремих фаз:

  • before_model_resolve: отримує лише поточний запит і метадані вкладень. Поверніть providerOverride або modelOverride.
  • agent_turn_prepare: отримує поточний запит, підготовлені повідомлення сеансу та всі ін’єкції з черги для одноразового застосування, вилучені для цього сеансу. Поверніть prependContext або appendContext.
  • before_prompt_build: отримує поточний запит і повідомлення сеансу. Поверніть prependContext, appendContext, systemPrompt, prependSystemContext або appendSystemContext.
  • heartbeat_prompt_contribution: виконується лише для ходів Heartbeat і повертає prependContext або appendContext. Призначений для фонових моніторів, яким потрібно підсумовувати поточний стан, не змінюючи ходи, ініційовані користувачем.

before_agent_start зберігається для сумісності. Віддавайте перевагу явним обробникам вище, щоб плагін не залежав від застарілої об’єднаної фази.

before_agent_run виконується після побудови запиту й до будь-якого введення в модель, зокрема до завантаження локальних зображень запиту та спостереження llm_input. Він отримує поточні вхідні дані користувача як prompt, а також завантажену історію сеансу в messages і активний системний запит. Поверніть { outcome: "block", reason, message? }, щоб зупинити виконання до того, як модель прочитає запит. reason є внутрішнім; message — його користувацька заміна. Підтримуються лише результати pass і block; непідтримувані форми рішень спричиняють безпечну відмову.

Коли виконання заблоковано, OpenClaw зберігає лише текст заміни в message.content разом із нечутливими метаданими блокування, як-от ідентифікатор плагіна, що заблокував виконання, і позначка часу. Початковий текст користувача не зберігається в транскрипті або майбутньому контексті. Внутрішні причини блокування вважаються чутливими й вилучаються з корисних навантажень транскрипту, історії, трансляції, журналу та діагностики. Для спостережуваності слід використовувати очищені поля, як-от ідентифікатор блокувальника, результат, позначка часу або безпечна категорія.

before_agent_start і agent_end містять event.runId, коли OpenClaw може визначити активне виконання; те саме значення також міститься в ctx.runId. Виконання, ініційовані Cron, також надають ctx.jobId (ідентифікатор початкового завдання Cron) у контексті ходу агента, щоб обробники могли обмежувати метрики, побічні ефекти або стан конкретним запланованим завданням. ctx.jobId не є частиною контексту інструмента before_tool_call.

Для запусків, ініційованих каналом, ctx.channel і ctx.messageProvider визначають поверхню провайдера, як-от discord або telegram, тоді як ctx.channelId є ідентифікатором цільової розмови, якщо OpenClaw може вивести його з ключа сеансу або метаданих доставки.

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

  • ctx.senderId — ідентифікатор відправника в межах каналу (наприклад, Feishu open_id, ідентифікатор користувача Discord). Заповнюється, коли запуск походить від повідомлення користувача з відомими метаданими відправника.
  • ctx.chatId — нативний для транспорту ідентифікатор розмови (наприклад, Feishu chat_id, Telegram chat_id). Заповнюється, коли вихідний канал надає нативний ідентифікатор розмови.
  • ctx.channelContext.sender.id — той самий ідентифікатор відправника, що й ctx.senderId, у належному каналу об’єкті, який плагіни можуть розширювати специфічними для каналу полями.
  • ctx.channelContext.chat.id — той самий ідентифікатор розмови, що й ctx.chatId, у належному каналу об’єкті, який плагіни можуть розширювати специфічними для каналу полями.

Ядро визначає лише вкладені поля id. Плагіни каналів, які передають розширені метадані відправника або чату через вхідний допоміжний засіб, можуть доповнювати PluginHookChannelSenderContext або PluginHookChannelChatContext з openclaw/plugin-sdk/channel-inbound:

ts
declare module "openclaw/plugin-sdk/channel-inbound" {  interface PluginHookChannelSenderContext {    unionId?: string;    userId?: string;  }}

Плагіни каналів передають ці поля через вхідний допоміжний засіб SDK:

ts
buildChannelInboundEventContext({  // ...  channelContext: {    sender: { id: senderOpenId, unionId, userId },    chat: { id: chatId },  },});

Ці поля необов’язкові й відсутні для запусків, ініційованих системою (heartbeat, cron, exec-event).

ctx.senderExternalId залишається застарілим полем для сумісності вихідного коду зі старішими плагінами. Ядро його не заповнює; нові специфічні для каналу ідентичності відправників мають розміщуватися в ctx.channelContext.sender через розширення модуля.

agent_end — це хук спостереження. Шляхи Gateway і постійного рушія запускають його асинхронно без очікування після ходу, тоді як короткочасні одноразові шляхи CLI очікують виконання промісу хука перед очищенням процесу, щоб довірені плагіни могли вивантажити термінальні дані спостережуваності або зафіксувати стан. Засіб запуску хуків застосовує 30-секундний тайм-аут, щоб завислий плагін або кінцева точка вбудовування не могли назавжди залишити проміс хука в стані очікування. Тайм-аут записується в журнал, і OpenClaw продовжує роботу; він не скасовує мережеву роботу, якою володіє плагін, якщо плагін також не використовує власний сигнал переривання.

Використовуйте model_call_started і model_call_ended для телеметрії викликів провайдера, яка не повинна отримувати необроблені запити, історію, відповіді, заголовки, тіла запитів або ідентифікатори запитів провайдера. Ці хуки містять стабільні метадані, як-от runId, callId, provider, model, необов’язкові api/transport, термінальні durationMs/outcome і upstreamRequestIdHash, коли OpenClaw може вивести обмежений хеш ідентифікатора запиту провайдера. Коли середовище виконання визначило метадані контекстного вікна, подія та контекст хука також містять contextTokenBudget — ефективний бюджет токенів після застосування обмежень моделі, конфігурації та агента, а також contextWindowSource і contextWindowReferenceTokens, коли було застосовано нижче обмеження.

before_agent_finalize запускається лише тоді, коли рушій збирається прийняти природну остаточну відповідь асистента. Це не шлях скасування /stop, і він не запускається, коли користувач перериває хід. Поверніть { action: "revise", reason }, щоб запросити в рушія ще один прохід моделі перед завершенням, { action: "finalize", reason? }, щоб примусово завершити, або не повертайте результат, щоб продовжити. Обробники за замовчуванням мають бюджет 15 с; у разі тайм-ауту OpenClaw записує помилку в журнал і продовжує з початковою остаточною відповіддю. Нативні хуки Codex Stop передаються в цей хук як рішення OpenClaw before_agent_finalize.

Повертаючи action: "revise", плагіни можуть додавати метадані retry, щоб зробити додатковий прохід моделі обмеженим і безпечним для повторного відтворення:

typescript
type BeforeAgentFinalizeRetry = {  instruction: string;  idempotencyKey?: string;  maxAttempts?: number;};

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

Невбудовані плагіни, яким потрібні хуки необробленої розмови (before_model_resolve, before_agent_reply, llm_input, llm_output, before_agent_finalize, agent_end або before_agent_run), мають установити:

json
{  "plugins": {    "entries": {      "my-plugin": {        "hooks": {          "allowConversationAccess": true        }      }    }  }}

Хуки, що змінюють запит, і довготривалі вставки для наступного ходу можна вимкнути для кожного плагіна за допомогою plugins.entries.<id>.hooks.allowPromptInjection=false.

Розширення сеансу та вставки для наступного ходу

Плагіни робочих процесів можуть зберігати невеликий сумісний із JSON стан сеансу за допомогою api.session.state.registerSessionExtension(...) та оновлювати його через метод Gateway sessions.pluginPatch. Рядки сеансу проєктують зареєстрований стан розширення через pluginExtensions, даючи Control UI та іншим клієнтам змогу відображати належний плагіну стан без знання внутрішньої будови плагіна. api.registerSessionExtension(...) усе ще працює, але вважається застарілим на користь простору імен api.session.state.

Використовуйте api.session.workflow.enqueueNextTurnInjection(...), коли плагіну потрібен довготривалий контекст, який має потрапити до наступного ходу моделі рівно один раз (верхньорівневий api.enqueueNextTurnInjection(...) — це застарілий псевдонім із такою самою поведінкою). OpenClaw спорожнює чергу вставок перед хуками запитів, відкидає прострочені вставки й усуває дублікати за idempotencyKey для кожного плагіна. Це належна точка інтеграції для відновлення після схвалення, підсумків політик, змін від фонових моніторів і продовжень команд, які мають бути видимі моделі під час наступного ходу, але не повинні ставати постійним текстом системного запиту.

Семантика очищення є частиною контракту. Зворотні виклики очищення розширення сеансу та життєвого циклу середовища виконання отримують reset, delete, disable або restart. Хост видаляє належний плагіну постійний стан розширення сеансу та очікувані вставки для наступного ходу під час скидання, видалення або вимкнення; перезапуск зберігає довготривалий стан сеансу, тоді як зворотні виклики очищення дають плагінам змогу звільнити завдання планувальника, контекст запуску та інші позасмугові ресурси старого покоління середовища виконання.

Хуки повідомлень

Використовуйте хуки повідомлень для маршрутизації на рівні каналу та політики доставки:

  • message_received: спостерігає за вхідним вмістом, відправником, threadId, messageId, senderId, необов’язковою кореляцією запуску/сеансу та метаданими.
  • message_sending: переписує content або повертає { cancel: true }.
  • reply_payload_sending: переписує нормалізовані об’єкти ReplyPayload (зокрема presentation, delivery, посилання на медіа й текст) або повертає { cancel: true }.
  • message_sent: спостерігає за остаточним успіхом або помилкою.

Для аудіовідповідей TTS без тексту content може містити приховану вимовлену транскрипцію, навіть якщо корисне навантаження каналу не має видимого тексту чи підпису. Переписування цього content оновлює лише видиму хуку транскрипцію; вона не відтворюється як підпис до медіа.

Події reply_payload_sending можуть містити usageState — отриманий із максимально можливими зусиллями актуальний знімок моделі, використання та контексту для кожного ходу. Довготривала доставка, відновлене повторне відтворення та відповіді без точної кореляції із запуском не містять його.

Контексти хуків повідомлень надають стабільні поля кореляції, коли вони доступні: ctx.sessionKey, ctx.runId, ctx.messageId, ctx.senderId, ctx.trace, ctx.traceId, ctx.spanId, ctx.parentSpanId і ctx.callDepth. Вхідні контексти та контексти before_dispatch також надають метадані відповіді, коли канал має відфільтровані за видимістю дані цитованого повідомлення: replyToId, replyToIdFull, replyToBody, replyToSender і replyToIsQuote. Віддавайте перевагу цим першокласним полям перед читанням застарілих метаданих.

Віддавайте перевагу типізованим полям threadId і replyToId перед використанням специфічних для каналу метаданих.

Правила прийняття рішень:

  • message_sending з cancel: true є термінальним.
  • message_sending з cancel: false вважається відсутністю рішення.
  • Переписаний content передається хукам із нижчим пріоритетом, якщо пізніший хук не скасує доставку.
  • reply_payload_sending запускається після нормалізації корисного навантаження та перед доставкою каналом, зокрема для відповідей, спрямованих назад у вихідний канал. Обробники запускаються послідовно, і кожен обробник бачить найновіше корисне навантаження, створене обробниками з вищим пріоритетом.
  • Корисні навантаження reply_payload_sending не розкривають маркери довіри середовища виконання, як-от trustedLocalMedia; плагіни можуть редагувати форму корисного навантаження, але не можуть надавати довіру до локальних медіа.
  • message_sending може повернути cancelReason та обмежений metadata разом зі скасуванням. Нові API життєвого циклу повідомлень надають це як результат пригніченої доставки з причиною cancelled_by_message_sending_hook; застаріла пряма доставка задля сумісності й надалі повертає порожній масив результатів.
  • message_sent призначений лише для спостереження. Помилки обробників записуються в журнал і не змінюють результат доставки.

Хуки встановлення

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

before_install — це хук життєвого циклу середовища виконання плагіна. Він запускається після security.installPolicy лише в процесі OpenClaw, де хуки плагінів уже завантажено, наприклад у потоках установлення через Gateway. Він корисний для належних плагіну спостережень, попереджень і перевірок сумісності, але не є основною межею безпеки підприємства або хоста для встановлень. Поле builtinScan залишається в корисному навантаженні події задля сумісності, але OpenClaw більше не виконує вбудованого блокування небезпечного коду під час установлення, тому воно містить порожній результат ok. Поверніть додаткові висновки або { block: true, blockReason }, щоб зупинити встановлення в цьому процесі.

block: true є термінальним. block: false вважається відсутністю рішення. Помилки обробників блокують установлення за принципом fail-closed.

Життєвий цикл Gateway

Використовуйте gateway_start для запуску загальних служб плагінів, а gateway_stop — для очищення довготривалих ресурсів. Планувальник Cron усе ще може завантажуватися, коли запускається gateway_start, тому не використовуйте його як базовий сигнал для зовнішньої проєкції Cron.

Не покладайтеся на внутрішній хук gateway:startup для належних плагіну служб середовища виконання.

cron_reconciled спрацьовує після того, як планувальник Cron Gateway і його спостерігачі завершення узгодили свій довготривалий стан. Він спрацьовує і під час початкового запуску, і під час заміни планувальника внаслідок перезавантаження конфігурації. Подія повідомляє reason (startup або reload) та ефективний стан enabled. Вимкнений Cron усе одно надсилає подію з enabled: false, даючи зовнішній проєкції змогу очистити застарілі пробудження. Використовуйте ctx.getCron?.() для точної інстанції планувальника, яка завершила узгодження; подальше перезавантаження не перенаправляє цей зворотний виклик. ctx.abortSignal належить тому самому знімку планувальника. Gateway перериває його, щойно активується новіший планувальник або починається завершення роботи. Передавайте його в кожен довготривалий побічний ефект і не приймайте знімок після його переривання. Це сигнал життєвого циклу планувальника, а не сигнал активації плагіна: гаряче перезавантаження лише плагіна не відтворює його повторно. Щойно ввімкнений споживач отримує свій перший базовий стан під час наступної заміни планувальника або запуску Gateway.

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

cron_changed спрацьовує для подій життєвого циклу Cron, якими керує Gateway, із типізованим корисним навантаженням події, що охоплює причини added, updated, removed, started, finished і scheduled. Подія містить знімок PluginHookGatewayCronJob (включно з state.nextRunAtMs, state.lastRunStatus і state.lastError, якщо вони наявні), а також PluginHookGatewayCronDeliveryStatus зі значенням not-requested | delivered | not-delivered | unknown. Події видалення відбуваються після фіксації: вони спрацьовують лише після успішного стійкого видалення й усе одно містять знімок видаленого завдання, щоб зовнішні планувальники могли узгодити стан.

Подія scheduled відбувається після фіксації: вона спрацьовує лише після того, як успішний стійкий запис змінить фактичне значення nextRunAtMs наявного завдання, за винятком явної події життєвого циклу added, updated або removed цього завдання. Значення верхнього рівня event.nextRunAtMs — це зафіксований наступний момент пробудження; якщо воно відсутнє, завдання не має наступного пробудження. Розглядайте ці події як підказки для узгодження, а не як упорядкований журнал змін. Використовуйте їх як підказки, які можна об’єднувати, щоб повторно прочитати планувальник, востаннє отриманий через cron_reconciled; не приймайте планувальник із контексту cron_changed. Зберігайте OpenClaw як джерело істини для перевірок строку виконання та запуску.

Безпечна зовнішня проєкція Cron

Проєктуйте повний знімок пробуджень замість пересилання змін подій Cron. Операція replaceAll зовнішнього адаптера має бути атомарною та ідемпотентною й має завершуватися лише після того, як хост стійко прийме знімок. Вона також має враховувати переданий сигнал переривання: якщо сигнал перерветься до стійкого прийняття, адаптер не повинен приймати цей знімок.

Цей шаблон залишає активним лише один виконавець з найновішим станом. Лише cron_reconciled приймає екземпляр планувальника; cron_changed лише просить цього виконавця повторно прочитати авторитетний екземпляр, тому запізніла підказка не може відновити старіший планувальник. Новіша ревізія перериває активну спробу хоста, перш ніж та зможе прийняти застарілий знімок.

typescript
  type ExternalWake = { jobId: string; runAtMs: number }; type ExternalWakeHost = {  replaceAll(wakes: readonly ExternalWake[], options: { signal: AbortSignal }): Promise<void>;  close(): Promise<void>;}; type CronReader = {  list(options: { includeDisabled: true }): Promise<    Array<{      id: string;      enabled?: boolean;      state?: { nextRunAtMs?: number };    }>  >;}; export function registerCronProjection(api: OpenClawPluginApi, host: ExternalWakeHost) {  const lifecycle = new AbortController();  let cron: CronReader | undefined;  let enabled = false;  let hasBaseline = false;  let reconciliationSignal: AbortSignal | undefined;  let requestedRevision = 0;  let appliedRevision = 0;  let worker = Promise.resolve();  let activeAttempt: AbortController | undefined;   const projectLatest = async () => {    let retryMs = 1_000;     while (!lifecycle.signal.aborted && appliedRevision < requestedRevision) {      const ownerSignal = reconciliationSignal;      if (!ownerSignal || ownerSignal.aborted) {        return;      }      const targetRevision = requestedRevision;      const attempt = new AbortController();      const signal = AbortSignal.any([lifecycle.signal, ownerSignal, attempt.signal]);      activeAttempt = attempt;       try {        const jobs = enabled && cron ? await cron.list({ includeDisabled: true }) : [];        if (signal.aborted || targetRevision !== requestedRevision) {          continue;        }        const wakes = jobs          .flatMap((job): ExternalWake[] => {            const runAtMs = job.enabled === false ? undefined : job.state?.nextRunAtMs;            return runAtMs === undefined ? [] : [{ jobId: job.id, runAtMs }];          })          .sort((a, b) => a.runAtMs - b.runAtMs || a.jobId.localeCompare(b.jobId));         await host.replaceAll(wakes, { signal });        if (signal.aborted || targetRevision !== requestedRevision) {          continue;        }        appliedRevision = targetRevision;        retryMs = 1_000;      } catch {        if (lifecycle.signal.aborted || ownerSignal.aborted) {          return;        }        if (attempt.signal.aborted) {          continue;        }        api.logger.warn(`external cron projection failed; retrying in ${retryMs}ms`);        try {          await sleep(retryMs, undefined, { signal });        } catch {          if (lifecycle.signal.aborted) {            return;          }          if (attempt.signal.aborted) {            continue;          }        }        retryMs = Math.min(retryMs * 2, 30_000);      } finally {        if (activeAttempt === attempt) {          activeAttempt = undefined;        }      }    }  };   const requestProjection = () => {    const targetRevision = ++requestedRevision;    activeAttempt?.abort();    worker = worker.then(async () => {      if (!lifecycle.signal.aborted && appliedRevision < targetRevision) {        await projectLatest();      }    });    return worker;  };   api.on("cron_reconciled", (event, ctx) => {    const reconciledCron = ctx.getCron?.();    if (event.enabled && !reconciledCron) {      api.logger.warn("cron reconciliation did not expose a scheduler");      return;    }    cron = reconciledCron;    enabled = event.enabled;    hasBaseline = true;    reconciliationSignal = ctx.abortSignal;    return requestProjection();  });   api.on("cron_changed", () => {    if (hasBaseline) {      return requestProjection();    }  });   api.on("gateway_stop", async () => {    lifecycle.abort();    await worker;    await host.close();  });}

Коли cron_reconciled повідомляє enabled: false, той самий шлях викликає replaceAll([]) і очищає застарілі зовнішні пробудження. Повторні спроби та експоненційна затримка в цьому прикладі є локальними для процесу й розглядають збої адаптера під час виконання як тимчасові; перевіряйте конфігурацію, для якої повторні спроби не допоможуть, до реєстрації. OpenClaw не надає вихідної черги для наслідків обробників Plugin. Якщо процес завершується до стійкого прийняття, наступний запуск Gateway випускає новий авторитетний знімок cron_reconciled. gateway_stop перериває поточну роботу хоста, очікує завершення виконавця, а потім закриває адаптер.

Майбутні припинення підтримки

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

  • Текстові конверти каналів в обробниках inbound_claim і message_received. Читайте BodyForAgent і структуровані блоки контексту користувача замість аналізу плоского тексту конверта. Див. Текстові конверти каналів → BodyForAgent.
  • before_agent_start залишається для сумісності. Нові плагіни мають використовувати before_model_resolve і before_prompt_build замість об’єднаної фази.
  • subagent_spawning залишається для сумісності зі старішими плагінами, але нові плагіни не повинні повертати з нього маршрутизацію гілки. Ядро готує прив’язки субагентів thread: true через адаптери прив’язування сеансів каналів до спрацювання subagent_spawned.
  • deactivate залишається застарілим псевдонімом сумісності для очищення до періоду після 2026-08-16. Нові плагіни мають використовувати gateway_stop.
  • onResolution у before_tool_call тепер використовує типізоване об’єднання PluginApprovalResolution (allow-once / allow-always / deny / timeout / cancelled) замість довільного string.
  • api.registerSessionExtension / api.enqueueNextTurnInjection залишаються псевдонімами сумісності верхнього рівня. Нові плагіни мають використовувати api.session.state.registerSessionExtension(...) і api.session.workflow.enqueueNextTurnInjection(...).

Повний список — реєстрацію можливостей пам’яті, профіль міркування провайдера, зовнішніх провайдерів автентифікації, типи виявлення провайдерів, засоби доступу до середовища виконання завдань і перейменування command-authcommand-status — див. у Міграція SDK Plugin → Активні припинення підтримки.

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

Was this useful?
On this page

On this page