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

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

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

Обзор

failproofai состоит из двух независимых подсистем:
  1. Обработчик хука — быстрый CLI подпроцесс, который Claude Code вызывает при каждом вызове инструмента агента. Оценивает политики и возвращает решение.
  2. Agent Monitor (Dashboard) — веб-приложение 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 с выходом инструмента. Обработчик устанавливает ограничение входного потока в 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, даже если операция разрешена. Обработчик хука записывает в stdout следующий JSON (не в файл конфигурации — это ответ обработчика на Claude Code, как и ответы deny и instruct выше):
// Written to stdout by the hook handler process
{
  "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        ← project  (highest priority)
[2] {cwd}/.failproofai/policies-config.local.json  ← local
[3] ~/.failproofai/policies-config.json             ← global   (lowest priority)
Логика объединения:
  • enabledPolicies — объединение всех трех файлов без дубликатов
  • policyParams — на политику, первый файл, который ее определяет, полностью выигрывает
  • customPoliciesPath — первый файл, который его определяет, выигрывает
  • llm — первый файл, который его определяет, выигрывает
Веб-панель мониторинга использует readHooksConfig() (только глобальная) для чтения и записи, так как она не вызывается с рабочей директорией проекта.

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

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, выдать ответ deny.
  • Если были собраны возвращаемые значения instruct, выдать единый ответ instruct со всеми объединенными сообщениями.
  • В противном случае выдать ответ allow (пустой stdout, выход 0).

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

src/hooks/builtin-policies.ts определяет все 26 встроенных политик как объекты 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
}
Одна строка на каждую политику, которая приняла решение, отличное от allow. Решения allow не регистрируются (чтобы файл оставался маленьким).

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

Панель мониторинга — это приложение 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   ← Export session as ZIP/JSONL
Поток данных:
  • Компоненты страниц вызывают lib/projects.ts и lib/log-entries.ts для чтения данных проекта/сеанса непосредственно из файловой системы (без слоя API для операций чтения).
  • Страница Policies использует 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       # 26 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