Building plugins

Хуки плагинов

Хуки плагинов — это внутрипроцессные точки расширения для плагинов 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: "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 перестаёт ожидать этот обработчик и продолжает работу. Обработчик и его побочные эффекты не отменяются. Не указывайте значение, чтобы использовать стандартный тайм-аут средства запуска для отдельного хука.

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

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 Запуск или остановка принадлежащих плагину сервисов вместе с Gateway
deactivate Устаревший псевдоним совместимости для gateway_stop; в новых плагинах используйте gateway_stop
cron_reconciled Сверка с полным состоянием Cron в Gateway после запуска или перезагрузки
cron_changed Наблюдение за изменениями жизненного цикла Cron, управляемого Gateway (добавление, обновление, удаление, запуск, завершение, планирование)
before_install Проверка подготовленных материалов установки Skills или плагина из загруженной среды выполнения плагина

Запросы на сопряжение канала

Используйте 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 и передается запросам на выполнение на хосте 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 — идентификатор отправителя в области канала (например, 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? }, чтобы принудительно завершить обработку, или не возвращайте результат, чтобы продолжить. По умолчанию обработчикам отводится 15s; при тайм-ауте 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 считается отсутствием решения. Сбои обработчиков блокируют установку по принципу запрета при сбое.

Жизненный цикл 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(`сбой внешней проекции 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-authcommand-status — см. в разделе Миграция Plugin SDK → Активные устаревания.

Связанные материалы

Was this useful?
On this page

On this page