Building plugins
Хуки Pluginів
Точки розширення Plugin працюють усередині процесу для плагінів OpenClaw: вони дають змогу перевіряти або змінювати запуски агентів, виклики інструментів, потік повідомлень, життєвий цикл сеансів, маршрутизацію субагентів, установлення або запуск Gateway.
Натомість використовуйте внутрішні хуки для невеликого встановленого оператором
скрипту HOOK.md, який реагує на події команд і Gateway, як-от /new,
/reset, /stop, agent:bootstrap або gateway:startup.
Швидкий старт
Зареєструйте типізовані хуки за допомогою api.on(...) у точці входу плагіна:
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 припиняє очікувати на цей обробник і продовжує роботу. Це не скасовує обробник або його побічні ефекти. Не вказуйте, щоб використовувати стандартний тайм-аут засобу виконання для окремого хука. |
Оператори можуть задавати бюджети хуків без внесення змін до коду плагіна:
{ "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, коли плагін має сповістити оператора або
записати аудиторський запис після того, як несполучений відправник приватного повідомлення створює запит
на сполучення, що очікує на розгляд. Обробник викликається під час створення запиту; повільні обробники
або обробники з помилками не затримують доставлення каналом відповіді щодо сполучення.
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.toolNameevent.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
Він може повертати:
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.sessionKeyevent.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— ідентифікатор відправника в межах каналу (наприклад, Feishuopen_id, ідентифікатор користувача Discord). Заповнюється, коли запуск походить від повідомлення користувача з відомими метаданими відправника.ctx.chatId— нативний для транспорту ідентифікатор розмови (наприклад, Feishuchat_id, Telegramchat_id). Заповнюється, коли вихідний канал надає нативний ідентифікатор розмови.ctx.channelContext.sender.id— той самий ідентифікатор відправника, що йctx.senderId, у належному каналу об’єкті, який плагіни можуть розширювати специфічними для каналу полями.ctx.channelContext.chat.id— той самий ідентифікатор розмови, що йctx.chatId, у належному каналу об’єкті, який плагіни можуть розширювати специфічними для каналу полями.
Ядро визначає лише вкладені поля id. Плагіни каналів, які передають розширені
метадані відправника або чату через вхідний допоміжний засіб, можуть доповнювати
PluginHookChannelSenderContext або PluginHookChannelChatContext з
openclaw/plugin-sdk/channel-inbound:
declare module "openclaw/plugin-sdk/channel-inbound" { interface PluginHookChannelSenderContext { unionId?: string; userId?: string; }}Плагіни каналів передають ці поля через вхідний допоміжний засіб SDK:
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, щоб
зробити додатковий прохід моделі обмеженим і безпечним для повторного відтворення:
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), мають установити:
{ "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 лише просить цього виконавця повторно прочитати
авторитетний екземпляр, тому запізніла підказка не може відновити старіший планувальник.
Новіша ревізія перериває активну спробу хоста, перш ніж та зможе прийняти застарілий
знімок.
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-auth → command-status — див. у
Міграція SDK Plugin → Активні припинення підтримки.
Пов’язані матеріали
- Міграція SDK Plugin — активні припинення підтримки та графік видалення
- Створення плагінів
- Огляд SDK Plugin
- Точки входу Plugin
- Внутрішні обробники
- Внутрішня архітектура плагінів