Building plugins
Хуки плагинов
Хуки плагинов — это внутрипроцессные точки расширения для плагинов 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: "Run web search", description: `Allow search query: ${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 |
Запуск или остановка принадлежащих плагину сервисов вместе с Gateway |
deactivate |
Устаревший псевдоним совместимости для gateway_stop; в новых плагинах используйте gateway_stop |
cron_reconciled |
Сверка с полным состоянием Cron в Gateway после запуска или перезагрузки |
cron_changed |
Наблюдение за изменениями жизненного цикла Cron, управляемого Gateway (добавление, обновление, удаление, запуск, завершение, планирование) |
before_install |
Проверка подготовленных материалов установки Skills или плагина из загруженной среды выполнения плагина |
Запросы на сопряжение канала
Используйте 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 и передается запросам
на выполнение на хосте Node.
Сохранение результатов инструментов
Результаты инструментов могут включать структурированные данные 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? }, чтобы принудительно завершить обработку, или не возвращайте результат, чтобы продолжить.
По умолчанию обработчикам отводится 15s; при тайм-ауте 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 считается отсутствием решения. Сбои
обработчиков блокируют установку по принципу запрета при сбое.
Жизненный цикл 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(`сбой внешней проекции Cron; повторная попытка через ${retryMs} мс`); 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 не предоставило планировщик"); 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 не предоставляет
исходящую очередь для эффектов хуков плагина. Если процесс завершается до долговременного принятия,
при следующем запуске 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 — см. в разделе
Миграция Plugin SDK → Активные устаревания.
Связанные материалы
- Миграция Plugin SDK — активные устаревания и сроки удаления
- Создание плагинов
- Обзор Plugin SDK
- Точки входа плагинов
- Внутренние хуки
- Внутреннее устройство архитектуры плагинов