Перейти к содержимому

Hooks

Хуки позволяют запускать произвольные shell-команды в определённых точках жизненного цикла Kodik: перед и после вызова инструмента, при запуске сессии, при компактировании и в других ситуациях. С помощью хуков можно блокировать нежелательные действия, вести аудит-лог, автоматически модифицировать входные данные инструментов и многое другое.

Хуки могут поступать из трёх источников, которые загружаются одновременно:

Источник Расположение файла
Проект .kodik/hooks/hooks.json (или .kodik/hooks/hooks.toml) в корне рабочей директории
Пользователь ~/.kodik/hooks/hooks.json (или hooks.toml)
Плагин hooks/hooks.json внутри директории установленного плагина

Проектные хуки применяются только к доверенным рабочим директориям. При первом обнаружении файла хуков Kodik предложит подтвердить доверие к проекту.

Kodik обнаруживает и записывает хуки только в строчной .kodik/hooks. Uppercase-каталоги Hooks не перемещаются, не объединяются и не удаляются; их содержимое неактивно и не выполняется. Нужные хуки перенесите вручную в строчный каталог.

Быстро создать хук можно с помощью команды /create-hook.

Событие Когда срабатывает Матчер
PreToolUse Перед одобрением и выполнением вызова инструмента Regex по имени инструмента
PostToolUse После выполнения инструмента Regex по имени инструмента
PermissionRequest Перед показом запроса на одобрение Regex по имени инструмента
SubagentStart При запуске субагента Regex по типу агента
SubagentStop При остановке субагента Regex по типу агента
SessionStart При старте или возобновлении сессии Regex по источнику (startup или resume)
PreCompact Перед компактированием контекста Regex по триггеру (manual или auto)
PostCompact После каждой попытки компактирования Regex по триггеру (manual или auto)
UserPromptSubmit При отправке пользователем сообщения Матчер не применяется
Stop Когда ход агента завершён или отменён Матчер не применяется
StopFailure Когда ход агента завершился ошибкой Regex по фазе сбоя (before_dispatch или provider)

За каждым SubagentStart следует ровно один SubagentStop, в том числе для субагента, который не удалось запустить. Запущенный субагент вызывает SubagentStop, когда завершает работу, даже если запустивший его ход уже закончился.

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

  1. Один раз срабатывает PreToolUse. Он может заблокировать вызов, изменить входные данные через modified_input или одобрить вызов через decision: "approve".
  2. Правила автоодобрения проверяются по входным данным с учётом изменений хука.
  3. Если вызов всё ещё требует одобрения, срабатывает PermissionRequest, затем показывается запрос на одобрение с теми данными, которые действительно будут выполнены.
  4. Инструмент выполняется, затем срабатывает PostToolUse.

Блокировка в PreToolUse отклоняет вызов до появления запроса на одобрение, а агент получает причину блокировки. Одобрение в PreToolUse пропускает запрос так же, как одобрение в PermissionRequest. Вызовы инструментов субагентов проходят тот же порядок. Интерактивные инструменты ask_questions и check_understanding не вызывают PreToolUse.

Для автономной работы, когда некому одобрять правки, используйте режим одобрения Автопилот или Полный доступ либо хук, возвращающий decision: "approve".

Поле matcher — это строка регулярного выражения, которая проверяется против дискриминатора события:

  • События с инструментами (PreToolUse, PostToolUse, PermissionRequest): проверяется имя инструмента.
  • События субагентов (SubagentStart, SubagentStop): проверяется тип агента.
  • SessionStart: проверяется источник — startup или resume.
  • PreCompact / PostCompact: проверяется триггер — manual или auto.
  • StopFailure: проверяется фаза сбоя — before_dispatch или provider.
  • UserPromptSubmit, Stop: матчер игнорируется, хук срабатывает всегда.

Матчер привязывается к началу и концу значения — например, Bash соответствует только инструменту с именем Bash. Чтобы сопоставить несколько инструментов, используйте синтаксис Edit|Write или .*.

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 audit_tool.py",
"timeout": 10
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo session started >> ~/kodik-sessions.log"
}
]
}
]
}
}

Каждая запись в массиве события содержит:

  • matcher — необязательная regex-строка (отсутствие означает «всегда совпадает»).
  • hooks — массив обработчиков. Сейчас поддерживается только тип command.
    • command — shell-команда, которую нужно выполнить.
    • timeout — таймаут в секундах (по умолчанию 30, диапазон 1–300).

Хук получает на stdin JSON-объект с информацией о событии:

{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "ls -la" },
"context": { ... }
}

Stop срабатывает, когда ход завершён или отменён; во входных данных есть поле outcome: "completed" или "canceled". Если ход завершился ошибкой, вместо него срабатывает StopFailure с такими полями:

Поле Тип Описание
failure_phase string before_dispatch — запрос не дошёл до модели (нет сети, вход, кредиты, доступ, настройки). provider — провайдер вернул ошибку, с частичным ответом или без него.
error_code string Код ошибки, например insufficient-credits, account-auth-required, invalid-request или provider-overloaded.
transient boolean true, если сбой временный (сеть, таймаут, перегрузка, лимит запросов) и повтор позже может пройти.
error_message string Текст ошибки, показанный в чате.
{
"hook_event_name": "StopFailure",
"failure_phase": "before_dispatch",
"error_code": "insufficient-credits",
"transient": false,
"error_message": "No credits remaining.",
"context": { ... }
}

Вывод хуков Stop и StopFailure игнорируется.

PreCompact срабатывает перед компактированием чата, и компактирование ждёт его завершения в пределах timeout хука, поэтому хук успевает сохранить снимок. Блокировка пропускает компактирование по /compact, при смене модели или после простоя. Компактирование посреди хода пропустить нельзя — без него ход не продолжится, — поэтому там блокировка только записывается в лог.

PostCompact срабатывает после каждой попытки компактирования, в том числе внутри субагента. Для субагента заполнено поле agent_type, а context описывает собственный чат субагента. Поле outcome принимает значения succeeded, failed или superseded (чат успел измениться раньше, и сводка отброшена). Компактирование, остановленное кнопкой «Стоп», не вызывает PostCompact. Если нажать «Стоп», пока выполняется PreCompact, завершение хука не запускает сводку и не сообщает о неудачной попытке. Это также действует при остановке чата из другого окна.

Хук сигнализирует об исходе через код возврата и/или stdout:

Код возврата Значение
0 Продолжить выполнение
2 Заблокировать действие; содержимое stderr становится причиной блокировки

Кроме того, хук может вернуть структурированный JSON в последней непустой строке stdout. Поддерживаемые поля:

Поле Тип Описание
decision "block" | "approve" Заблокировать или разрешить действие (approve работает для PreToolUse и PermissionRequest)
reason string Причина решения, показывается пользователю
modified_input object Изменённые входные данные инструмента (только для PreToolUse)
modified_prompt string Изменённый текст промпта (только для UserPromptSubmit)
additional_context string Дополнительный контекст, передаваемый агенту

Kodik передаёт additional_context как исходный текст в блоке Markdown Hook context. Происхождение и идентификатор доставки берутся из запуска хука, а не из тегов или заголовков в его выводе. Поля modified_prompt, modified_input, разрешение и блокировка продолжают работать согласно правилам выше.

Пример блокировки с причиной:

{
"decision": "block",
"reason": "Изменения в продакшн-ветке запрещены без код-ревью."
}

Kodik передаёт в хук переменные окружения в зависимости от источника хука:

  • Проектный хук: KODIK_PROJECT_ROOT — путь к корню проекта.
  • Пользовательский хук: KODIK_HOOKS_ROOT — путь к директории пользовательских хуков.
  • Хук плагина: KODIK_PLUGIN_ROOT и KODIK_PLUGIN_ID.

Любой хук также получает KODIK_SESSION_ID — id чата, который его вызвал. Хуки для вызовов инструментов субагента сообщают id чата, который запустил субагента. Команды, которые запускает агент, получают ту же переменную, поэтому скрипт может определить, из какого чата он запущен; команды субагента получают его собственный id в KODIK_SESSION_ID и id запустившего чата в KODIK_PARENT_SESSION_ID.

Объект context во входных данных хука содержит sessionId чата, его текущий заголовок title, режим mode и рабочую директорию cwd.

Хуки запускаются через cmd.exe. JSON на stdin содержит только ASCII-символы: каждый символ вне ASCII записан как \uXXXX, поэтому хук получает один и тот же текст при любой кодовой странице консоли, а любой JSON-парсер (ConvertFrom-Json, модуль json в Python) точно восстанавливает кириллицу и другие письменности. Хуки и команды агента также получают PYTHONUTF8=1 и PYTHONIOENCODING=utf-8, если вы не задали эти переменные сами, поэтому скрипты Python читают и печатают UTF-8.

Команды, которые агент запускает в Windows PowerShell 5.1, PowerShell 7 и cmd, используют UTF-8 для вывода и для текста, передаваемого другим программам через конвейер.

Windows PowerShell 5.1 читает файл .ps1 без метки порядка байтов (BOM) в системной ANSI-кодировке. Сохраняйте скрипты PowerShell с нелатинским текстом в кодировке UTF-8 с BOM.

Напечатайте /create-hook в чате и опишите, что должен делать хук. Kodik сгенерирует и сохранит файл хуков в .kodik/hooks/hooks.json вашего проекта (или в глобальную директорию, если вы явно укажете). После создания хука перезагрузите окно IDE или начните новую задачу, чтобы изменения вступили в силу.