Перейти к основному содержанию

title: Архитектура description: “Как работают обработчик крючков, загрузка конфигурации и оценка политик” icon: sitemap

Этот документ объясняет внутреннее устройство failproofai: как система крючков перехватывает вызовы инструментов агента, как загружается и объединяется конфигурация, как оцениваются политики и как панель мониторинга отслеживает активность агента.

Обзор

failproofai состоит из двух независимых подсистем:
  1. Обработчик крючков — быстрый CLI подпроцесс, который Claude Code вызывает при каждом вызове инструмента агента. Оценивает политики и возвращает решение.
  2. Монитор агента (Панель) — веб-приложение Next.js для мониторинга сеансов агента и управления политиками.
Обе подсистемы используют файлы конфигурации в ~/.failproofai/ и в директории проекта .failproofai/, но работают как отдельные процессы и взаимодействуют только через файловую систему.

Обработчик крючков

Интеграция с Claude Code

Когда вы выполняете failproofai policies --install, он записывает записи вроде этой в ~/.claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
Claude Code затем вызывает failproofai --hook PreToolUse как подпроцесс перед каждым вызовом инструмента, передавая JSON-полезную нагрузку на stdin.

Формат полезной нагрузки

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl",
  "cwd": "/home/user/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "sudo apt install nodejs" }
}
Для событий PostToolUse полезная нагрузка также содержит tool_result с выводом инструмента. Обработчик применяет лимит stdin в 1 МБ. Полезные нагрузки, превышающие это значение, отбрасываются и все политики неявно разрешают операцию.

Формат ответа

Запретить (PreToolUse):
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
Запретить (PostToolUse):
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
Инструктировать (любое событие кроме Stop):
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
Событие Stop с инструкцией:
  • Код выхода: 2
  • Причина записывается в stderr (не в stdout)
Разрешить:
  • Код выхода: 0
  • Пустой stdout
Разрешить с сообщением: allow(message) позволяет политике отправить информационный контекст обратно Claude даже когда операция разрешена. Обработчик крючков записывает следующий JSON в stdout (не в файл конфигурации — это ответ обработчика на Claude Code, так же как запрет и инструкции выше):
// Записано в stdout процессом обработчика крючков
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
  • Код выхода: 0 (операция разрешена)
  • Когда несколько политик возвращают allow с сообщением, их сообщения объединяются новыми строками в одну строку additionalContext
  • Если ни одна политика не предоставляет сообщение, stdout пустой (как и раньше)

Конвейер обработки

src/hooks/handler.ts реализует полный конвейер:
stdin JSON
  → parse payload (max 1 MB)
  → extract session metadata (session_id, cwd, tool_name, tool_input, etc.)
  → readMergedHooksConfig(cwd)    ← merges project + local + global config
  → register enabled builtin policies with resolved params
  → load custom policies from customPoliciesPath (if set)
  → register custom policies into policy registry
  → evaluate all policies (builtins first, then custom)
      → first deny short-circuits
      → instruct decisions accumulate
      → allow messages accumulate
  → write JSON decision to stdout
  → persist event to ~/.failproofai/hook-activity.jsonl
  → exit
Весь процесс выполняется менее чем за 100 мс для типичных полезных нагрузок без вызовов LLM.

Загрузка конфигурации

src/hooks/hooks-config.ts реализует загрузку конфигурации с тремя уровнями видимости.
[1] {cwd}/.failproofai/policies-config.json        ← проект  (наивысший приоритет)
[2] {cwd}/.failproofai/policies-config.local.json  ← локальная
[3] ~/.failproofai/policies-config.json             ← глобальная   (наименьший приоритет)
Логика объединения:
  • enabledPolicies — дедублицированное объединение всех трех файлов
  • policyParams — ключ для каждой политики, первый файл, который его определяет, полностью побеждает
  • customPoliciesPath — первый файл, который его определяет, побеждает
  • llm — первый файл, который его определяет, побеждает
Веб-панель использует readHooksConfig() (только глобальное) для чтения и записи, так как она не вызывается с cwd проекта.

Оценка политик

src/hooks/policy-evaluator.ts запускает политики по порядку. Для каждой политики:
  1. Найти схему params политики (если она есть).
  2. Прочитать policyParams[policy.name] из объединенной конфигурации.
  3. Объединить предоставленные пользователем значения со значениями по умолчанию схемы для получения ctx.params.
  4. Вызвать policy.fn(ctx) с разрешенным контекстом.
  5. Если результат deny, остановиться немедленно и вернуть это решение.
  6. Если результат instruct, накопить сообщение и продолжить.
  7. Если результат allow, перейти к следующей политике.
После выполнения всех политик:
  • Если возвращен какой-либо deny, выдать ответ с запретом.
  • Если собраны какие-либо instruct, выдать единый ответ с инструкцией со всеми сообщениями, объединенными вместе.
  • В противном случае выдать ответ с разрешением (пустой stdout, выход 0).

Встроенные политики

src/hooks/builtin-policies.ts определяет все 39 встроенных политик как объекты BuiltinPolicyDefinition:
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
Политики, которые принимают params, объявляют PolicyParamsSchema с типами и значениями по умолчанию для каждого параметра. Оценщик политик внедряет разрешенные значения в ctx.params перед вызовом fn. Функции политик читают ctx.params без проверок на null, потому что значения по умолчанию всегда применяются первыми. Сопоставление шаблонов внутри политик использует разобранные токены команд (argv), а не прямое сопоставление строк. Это предотвращает обход через инъекции операторов оболочки (например, шаблон для sudo systemctl status * не может быть обойден путем добавления ; rm -rf / к команде).

Пользовательские политики

src/hooks/custom-hooks-registry.ts реализует реестр на основе globalThis:
const REGISTRY_KEY = "__failproofai_custom_hooks__";

export const customPolicies = {
  add(hook: CustomHook): void { ... }
};

export function getCustomHooks(): CustomHook[] { ... }
export function clearCustomHooks(): void { ... }  // used in tests
src/hooks/custom-hooks-loader.ts загружает файл политик пользователя:
  1. Прочитать customPoliciesPath из конфигурации; пропустить если отсутствует.
  2. Разрешить абсолютный путь; проверить существование файла.
  3. Переписать все импорты from "failproofai" на фактический путь dist, чтобы customPolicies разрешился на тот же реестр globalThis.
  4. Рекурсивно переписать переходные локальные импорты для обеспечения совместимости ESM.
  5. Записать временные файлы .mjs и import() файл точки входа.
  6. Вызвать getCustomHooks() для получения зарегистрированных крючков.
  7. Очистить все временные файлы в блоке finally.
При любой ошибке (файл не найден, синтаксическая ошибка, ошибка импорта) ошибка регистрируется в ~/.failproofai/hook.log и загрузчик возвращает пустой массив. Встроенные политики не затрагиваются. Пользовательские политики оцениваются после всех встроенных политик. Пользовательская политика deny по-прежнему короткозамыкает дальнейшие пользовательские политики (но все встроенные уже были выполнены к этому моменту).

Логирование активности

После каждого события крючка обработчик добавляет строку JSONL в ~/.failproofai/hook-activity.jsonl:
{
  "timestamp": "2026-04-06T12:34:56.789Z",
  "sessionId": "abc123",
  "eventType": "PreToolUse",
  "toolName": "Bash",
  "policyName": "block-sudo",
  "decision": "deny",
  "reason": "sudo command blocked by failproofai",
  "durationMs": 12
}
Одна строка на политику, которая приняла решение не-разрешение. Решения о разрешении не регистрируются (чтобы файл был меньше).

Архитектура панели

Панель — это приложение Next.js 16, использующее App Router с React Server Components и Server Actions.
app/
  layout.tsx                  ← Root layout (theme, telemetry, nav)
  projects/page.tsx           ← Server component: list all Claude projects
  project/[name]/page.tsx     ← Server component: list sessions in a project
  project/[name]/session/
    [sessionId]/page.tsx      ← Server component: render session viewer
  policies/page.tsx           ← Client component: policy management + activity log
  actions/
    get-hooks-config.ts       ← Read config + policy list
    update-hooks-config.ts    ← Toggle policy on/off
    update-policy-params.ts   ← Update policy parameters
    get-hook-activity.ts      ← Paginate/search activity log
    install-hooks-web.ts      ← Install/remove hooks from the browser
  api/
    download/[project]/[session]/route.ts   ← Per-CLI session export (JSONL or JSON)
Поток данных:
  • Компоненты страниц вызывают lib/projects.ts и lib/log-entries.ts для прямого чтения данных проекта/сеанса из файловой системы (нет API слоя для чтения).
  • Страница Политик использует Server Actions для всех мутаций (переключение, обновление параметров, установка/удаление).
  • Средство просмотра сеансов анализирует формат JSONL-транскрипта Claude и отображает временную шкалу сообщений и вызовов инструментов.
Ключевые решения по проектированию:
  • Нет базы данных — все постоянное состояние находится в простых файлах (~/.failproofai/, ~/.claude/projects/).
  • Server Actions для мутаций — не требуется REST API для операций CRUD.
  • React Server Components для страниц чтения — быстрая первоначальная загрузка, нет клиентского пакета для выборки данных.
  • Клиентские компоненты только где нужна интерактивность (переключатели политик, поиск активности, средство просмотра логов).

Расположение файлов

failproofai/
├── bin/
│   └── failproofai.mjs           # CLI router (hook / dashboard / install / etc.)
├── src/hooks/
│   ├── handler.ts                # Hook event pipeline
│   ├── builtin-policies.ts       # 39 policy definitions
│   ├── policy-evaluator.ts       # Policy execution engine
│   ├── policy-registry.ts        # Policy registration and lookup
│   ├── policy-types.ts           # TypeScript interfaces
│   ├── hooks-config.ts           # Multi-scope config loading
│   ├── custom-hooks-registry.ts  # globalThis-backed hook registry
│   ├── custom-hooks-loader.ts    # ESM loader for user JS hooks
│   ├── manager.ts                # install / remove / list operations
│   ├── install-prompt.ts         # Interactive policy selection prompt
│   ├── hook-logger.ts            # Logging to hook.log
│   ├── hook-activity-store.ts    # Persist activity to hook-activity.jsonl
│   └── llm-client.ts             # LLM API client (for AI-powered policies)
├── app/                          # Next.js dashboard (pages + server actions)
├── lib/                          # Shared utilities
│   ├── projects.ts               # Enumerate Claude projects from filesystem
│   ├── log-entries.ts            # Parse Claude transcript JSONL format
│   ├── paths.ts                  # Resolve system paths
│   └── ...
├── components/                   # Shared React UI components
├── contexts/                     # React context providers (theme, auto-refresh, telemetry)
├── examples/                     # Example custom hook files
└── __tests__/                    # Unit and E2E tests