---
read_when:
    - Вы создаёте плагин, которому требуются хуки before_tool_call, before_agent_reply, хуки сообщений или хуки жизненного цикла
    - Вам нужно блокировать, изменять или требовать подтверждения вызовов инструментов из плагина
    - Вы выбираете между внутренними хуками и хуками плагинов
    - Вы переносите пробуждения Cron OpenClaw во внешний планировщик хоста
summary: 'Хуки плагинов: перехват событий жизненного цикла агента, инструмента, сообщения, сеанса и Gateway'
title: Хуки плагинов
x-i18n:
    generated_at: "2026-07-13T20:02:16Z"
    model: gpt-5.6
    postprocess_version: locale-links-v1
    prompt_version: 24
    provider: openai
    source_hash: 9e4e94220bca59b710b7b46c87bb889942c88b0d44f723e7133f271d34d9c929
    source_path: plugins/hooks.md
    workflow: 16
---

Хуки плагинов — это внутрипроцессные точки расширения для плагинов OpenClaw: они позволяют проверять или
изменять запуски агентов, вызовы инструментов, поток сообщений, жизненный цикл сессий, маршрутизацию
субагентов, установки или запуск Gateway.

Вместо них используйте [внутренние хуки](/ru/automation/hooks), если нужен небольшой устанавливаемый оператором
скрипт `HOOK.md`, реагирующий на события команд и Gateway, такие как `/new`,
`/reset`, `/stop`, `agent:bootstrap` или `gateway:startup`.

## Быстрый старт

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

```typescript
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";

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](/ru/plugins/codex-harness-runtime#hook-boundaries).
- Хук `block: true` с более низким приоритетом по-прежнему может заблокировать действие после того, как хук с более высоким приоритетом
  запросил подтверждение.
- `onResolution` получает итоговое решение: `allow-once`, `allow-always`,
  `deny`, `timeout` или `cancelled`.

Сведения о маршрутизации подтверждений, поведении решений и случаях использования `requireApproval`
вместо необязательных инструментов или подтверждений выполнения см. в разделе
[Запросы разрешений плагинов](/ru/plugins/plugin-permission-requests).

Плагины, которым нужна политика уровня хоста, могут регистрировать доверенные политики инструментов с помощью
`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
import { setTimeout as sleep } from "node:timers/promises";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";

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](/ru/plugins/sdk-migration#active-deprecations).
- **`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 → Активные устаревания](/ru/plugins/sdk-migration#active-deprecations).

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

- [Миграция Plugin SDK](/ru/plugins/sdk-migration) — активные устаревания и сроки удаления
- [Создание плагинов](/ru/plugins/building-plugins)
- [Обзор Plugin SDK](/ru/plugins/sdk-overview)
- [Точки входа плагинов](/ru/plugins/sdk-entrypoints)
- [Внутренние хуки](/ru/automation/hooks)
- [Внутреннее устройство архитектуры плагинов](/ru/plugins/architecture-internals)
