# История изменений

Все значимые изменения в проекте `umbot` документируются в этом файле.
Формат основан на [Keep a CHANGELOG](http://keepachangelog.com/).

## [3.1.1] - 2026-09-14

### Исправлено

- **Re2 (совместимость окружения)**: у контрибьюторов с установленным `re2` падали юнит-тесты — `expect(...).toBeInstanceOf(RegExp)` не учитывал, что объект RE2 не наследует `RegExp` (проверено duck-typing-хелпером `isRegex`, как это делает сам фреймворк); тест точного совпадения флагов не учитывал, что re2 всегда дописывает `u`; тест ReDoS-логов ждал `error` там, где с re2 корректно пишется `warn` (уровень намеренно зависит от окружения); `tests/Text/re2Engine.test.ts` недетерминированно мокал `re2` поверх настоящего нативного модуля (теперь при реально установленном re2 контракт проверяется против настоящего движка, а мок — только в окружении без него).
- **Re2 (lookaround)**: `getRegExp` (склейка слотов команд, группы регулярок, `isPattern`-строки) бросал `SyntaxError` на lookaround-синтаксис (`(?<=`, `(?=`, …) при установленном `re2` — регистрация команды падала целиком. Теперь выражение, которое re2 не умеет компилировать, пересобирается штатным `RegExp` — та же политика, что у `getRegExpOrSelf` для одиночных слотов. Сообщение ReDoS-предупреждения уточнено: re2 совместим не со всеми выражениями, откатывающиеся выполняются нативно (небезопасные остаются под контролем `isDangerRegex`/strictMode).

## [3.1.0] - 2026-09-13

### Миграция с 3.0.x

- **Telegram**: автоматический `parse_mode` отключён — разметка применяется только при явном `telegram_parse_mode`. Если тексты бота полагались на автоматическую разметку (жирный, курсив), задайте опцию адаптера или экранируйте данные сами.
- **Вебхук**: нераспознанный или неразбираемый запрос платформы теперь возвращает `400` вместо `500`. На собственный код это не влияет, но мониторинг, алертящий на 5xx, больше не будет видеть эти случаи как ошибки сервера.
- **Типы VK/Viber** (влияет только на компиляцию, не на рантайм): поля `waleform` переименованы в `waveform` (`IVkAudioMessageInfo`, `IVkDocSave`); `IVkRequestObject.message` стал опциональным; `location.lat`/`lon` в `IViberParams` — `number` вместо `string`; поле `IViberUserInfo.mss` переименовано в `mcc` (реальное имя поля Viber API — Mobile Country Code; `mss` в ответе платформы всегда был undefined). Код, использовавший старые имена, нужно поправить при обновлении.

### Добавлено

- **Сессия в памяти процесса** (`MemorySessionStorage`, опция `memorySession`): при `isLocalStorage: true` на платформах без локального хранилища (Telegram, VK, MAX, Viber) и без DB-адаптера `userData` хранится в памяти процесса — шаги диалога и данные пользователя работают без БД, как `MemorySessionStorage` у grammY. Лимит — 10 000 пользователей (вытесняется дольше всех не писавший), время жизни — 24 часа с последнего запроса; настраивается `memorySession: { maxSize, ttl }`, отключается `memorySession: false`. Таймеров нет, пустой `userData` не хранится. Ограничения задокументированы и выводятся в предупреждении один раз на платформу: данные теряются при перезапуске и не разделяются между процессами, репликами и вызовами serverless-функции. Раньше в этой конфигурации `userData` между запросами не сохранялась вовсе, и сценарии `addStep` уходили в fallback.
- **Live-тест платформ** (`live-test/`, `npm run live`): консольный инструмент для проверки на настоящих платформах. Прогон сценариев отдаёт синтетический входящий запрос «от вашего тестового пользователя» в `bot.run()` — адаптер делает реальные запросы к API в ваш чат, каждый запрос печатается со статусом и ошибкой API; для голосовых платформ проверяются обязательные поля протокола. Webhook-режим поднимает демо-бота для живого диалога, есть регистрация webhook (Telegram/Viber/MAX). В npm-пакет не входит.
- **События**: новый событийный слой — обработка апдейтов по типу события для всех платформ сразу:
    - `bot.addEvent(eventType, handler)` — регистрация обработчика; выполняется до шагов и команд. Поддерживаются `async`-хендлеры, возврат строки-ответа и `false` («событие не моё» — синхронно и асинхронно одинаково событие передаётся следующему хендлеру, а после исчерпания цепочки запрос идёт по обычному конвейеру). У адаптера без поля `supportedEvents` (прямая реализация `IPlatformAdapter`) оно эквивалентно `['message']`. Управление: `bot.removeEvent(eventType)`, `bot.clearEvents()`.
    - `controller.eventType` — универсальный тип события (`TEventType`): `message`, `photo`, `voice`, `video`, `document`, `location`, `contact`, `sticker`, `callback`, `inline`, `message_edited`, `channel_post`, `start`, `subscribed`, `unsubscribed`, `auth`, `rating`. Заполняется адаптерами всех 7 платформ; хендлеры не вызываются там, где событие физически невозможно. Событие `auth` выставляется и на Марусе (`account_linking_complete_event`), симметрично Алисе.
    - Каждый адаптер объявляет поле `supportedEvents` (дефолт у `BasePlatform` — `['message']`), `bot.addEvent` предупреждает об опечатке в имени события и о событии, которого не поддерживает ни одна платформа. Кастомная платформа от `BasePlatform` объявляет собственный `supportedEvents` и участвует в валидации. Валидатор `isEventType` и перечень `ALL_EVENT_TYPES` — в `src/core/events.ts` (экспортируется из `umbot`).
- **Действия кнопок**: `bot.addAction(actionName, handler)` — обработчик нажатия callback-кнопки по payload. Адаптеры Telegram, VK и MAX нормализуют payload callback-кнопок: строка `'buy'` или JSON `{"command":"buy"}` попадает в `userCommand` как `buy` и срабатывает как обычная команда — без ручного разбора `requestObject`.
- **Match**: `controller.match` — результат совпадения команды с регулярным выражением (`RegExpExecArray`, группы в `match[1]`, `match.groups`). Вычисляется лениво — только при первом обращении обработчика; запросы, не читающие match, не платят за дополнительный прогон регулярки. Строковые `isPattern`-слоты компилируются через общий LRU-кэш `Text.getMatchRegExp` (тот же, что у поиска команд). Работает для RegExp-слотов, `isPattern`-паттернов и групп регулярок.
- **API-фасад**: `controller.api` — унифицированный доступ к возможностям активной платформы из обработчика: `sendPhoto/sendDocument/sendAudio/sendVideo(image, {caption})`, `answerCallback(text, showAlert?)`, `can(method)`. Реализация — ленивый объект-фасад (не класс), создаётся при первом обращении к `ctx.api`; на голосовых платформах — `null`. При невозможности определить `chat_id` фасад отказывается от отправки с явным warn, а не шлёт битый запрос. Telegram поддерживает весь набор; VK — sendPhoto (через штатный upload-flow)/sendDocument/answerCallback; MAX — весь набор через `POST /uploads` (вложения строятся по таблице типов `image|video|audio|file`, интерфейсы `IMaxVideo`/`IMaxFile`; `answerCallback` передаёт `dialog_id` — иначе очередь 2 callback-ответов/сек не включалась и быстрые нажатия давали 429); `caption` доходит до `messagesSend` на VK и MAX; Viber — честные `can() === false` (Bot API Viber требует URL+size). Фасад выбирается самим адаптером — новый опциональный метод `createApi(controller)` контракта `IPlatformAdapter` (базовая реализация `BasePlatform` возвращает `null`, фабрики встроенных платформ живут в `plugins/platforms/<Name>/apiFacade.ts`). Кастомная платформа подключает свой фасад переопределением одного метода — правки ядра не нужны. Диспетчер `makePlatformApi` из `umbot/plugins` сохранён для ручного использования.
- **Telegram webhook-reply**: опция адаптера `new TelegramAdapter(token, { telegram_webhook_reply: true })` — простой текстовый ответ уходит телом webhook-ответа (`{method: 'sendMessage', ...}`) без исходящего POST. Текст проходит ту же обрезку 4096 (со снятием `parse_mode` у обрезанного — единая функция `prepareTelegramMessageText`), что и `sendMessage`: webhook-ответ не получает ошибки API, и слишком длинное сообщение иначе пропадало бы молча. Opt-in, автоматически отключается для callback/inline-запросов и ответов с карточками/звуками (они уходят штатным путём). ID callback-запроса сверяется по обоим источникам (`requestData` и `platformOptions`) — спиннер кнопки подтверждается и при ответе webhook-телом.
- **Env**: новое каноническое имя переменной окружения `ALISA_TOKEN` для Алисы. Старое имя `YANDEX_TOKEN` сохраняется для обратной совместимости — при одновременном задании обеих приоритет у `ALISA_TOKEN`.
- **Env**: новая переменная окружения `SPEECH_KIT_TOKEN` — единый токен Yandex SpeechKit для синтеза речи (TTS) в чат-ботах. Значение автоматически раскладывается в `speech_kit_token` токенов Telegram, VK и Max.
- **Env**: токены из `process.env` теперь подхватываются без настройки `env`/`.env`-файла. Если `appConfig.env` не задан, фреймворк без логов заполняет отсутствующие токены из переменных окружения и не перезаписывает явно заданные (в конструкторе адаптера или конфиге). Благодаря этому `docker run -e TELEGRAM_TOKEN=...` работает без дополнительной конфигурации. Явное `env: 'local'` или путь к файлу сохраняет прежнее поведение с перезаписью.
- **CLI**: новая команда `npx umbot validate <flow.json>` для проверки корректности flow.json перед генерацией (битые ссылки в `edges`, дубликаты id, невалидные идентификаторы, невалидный JSON). Циклы в графе допустимы и ошибкой не считаются.
- **CLI**: новая команда `npx umbot stats --log <path>` для агрегации метрик из лога (счётчики строк/ошибок/ворнингов, топ команд, p50/p95/p99 latency).
- **Middleware**: добавлены стандартные middleware в `src/middleware/` и `umbot/middleware`:
    - `authGuard(check, options)` — проверка авторизации пользователя перед выполнением остальных обработчиков (кастомный `deniedText`, поддержка async check, исключения в `check` трактуются как отказ).
    - `requestId()` — проставление уникального `requestId` в `platformOptions.requestId` для сквозного трейсинга. Запасной вариант для Node < 14.17 (без `crypto.randomUUID`).
    - `maintenance(check, options)` — возврат "сервис на техобслуживании" пока `check()` возвращает true. Ошибки в `check` логируются и не ломают pipeline.
    - `ipFilter(options)` — фильтрация входящих webhook-запросов по IP клиента. Поддерживает IPv4 и CIDR, автоматически нормализует IPv6-mapped. IP берётся из сокета HTTP-запроса (заполняется в `webhookHandle` в `platformOptions.clientIp`). Fail-open вне HTTP-контекста (`bot.run()` напрямую — запрос пропускается).
- **Bot**: новый метод `bot.addForm(formName, options)` для многошаговых форм (опросников). Реализован поверх существующего `addStep` — для каждого поля регистрируется отдельный шаг `__form_<formName>_<idx>`, состояние ответа сохраняется в `userData.__formdata_<formName>`. Поддерживаются синхронные и асинхронные валидаторы (включая async `onComplete`), prompt-функция, команды отмены с кастомным текстом; ответы пользователя сохраняются в исходном регистре (`originalUserCommand`). Снять форму можно методом `bot.removeForm(formName)`.
- **Bot**: класс `Bot` теперь принимает дополнительный generic `TPlatformState`. Пример: `new Bot<MyUserData, MyState>()`. Это даёт типобезопасный доступ к `ctx.state`.
- **BotController**: аналогично `Bot` — второй generic `TPlatformState` для типизации локального хранилища платформы (state).
- **IPlatformData**: в интерфейс добавлено опциональное служебное поле `oldIntentName` для восстановления шагов диалога через локальное хранилище платформы.
- **BotTest**: новый метод `bot.simulate(query, { platform, userId, count, state })` — сам генерирует webhook payload через `getQueryExample` платформы и возвращает структурированный ответ. Упрощает написание интеграционных тестов. Работает без сети: `skipAutoReply` выставляется на время запроса (и восстанавливается после, включая ошибку), поэтому на чат-платформах ответ возвращается локально (`'ok'`), а текст читается через `bot.getBotController()`. Без явной `platform` берётся первая зарегистрированная.
- **VK**: метод `sendMessageEvent()` в `VkRequest` для корректного подтверждения callback-кнопок.
- **AppContext**: поле `eventId` в `IPlatformOptions` для хранения ID события callback-кнопки VK.
- **Маруся**: поддержка типа карточки `ImageGallery`.
- **Маруся**: обработка health-check (ping → pong) для корректного ответа на служебные запросы платформы.
- **Маруся**: исправлено формирование session-объекта в ответе — добавлен fallback для `user_id`.
- **Маруся**: расширен тип `account_linking_complete_event` с `boolean` до `object | boolean`.
- **CLI**: флаг `--force` для `create from-flow`, чтобы перезапись непустой папки была явным действием.
- **Плагины**: новый хелпер `createPlugin(fn)` (экспортируется из `umbot`) — оборачивает функцию-плагин и автоматически выставляет маркер `isPlugin = true`. Убирает частую ошибку, когда забытый флаг заставлял `bot.use()` трактовать плагин как middleware.
- **Bot**: новый метод `bot.webhookEvent(data, headers, clientIp)` — серверлесс-точка входа для обработки входящего события (Yandex Cloud Functions и т.п.) с предварительной проверкой подлинности webhook-запроса через `isCorrectQuery` платформы. В отличие от `run()`, принимает заголовки запроса и отклоняет поддельные запросы со статусом 401 до выполнения логики. Возвращает `{ statusCode, body }`, готовые для возврата из cloud-функции.
- **Документация**: в GUIDE.md добавлена секция «Высокая нагрузка: несколько процессов и горизонтальное масштабирование» (в разделе «Деплой»): масштабирование обработки webhook`ов на несколько инстансов (PM2 cluster, Docker-реплики), требование общей БД и неприменимость `FileAdapter` в multi-process конфигурациях, изоляция платформ по отдельным процессам, особенности serverless-деплоя (read-only ФС, настройка логгера).
- **Примеры**: добавлен демо-проект `examples/umbot-demo` («Кофейня-бот») по ТЗ `src/docs/demo-project-spec.md` — эталонный законченный бот в концепции «один код — 7 платформ». Вся логика построена только на командах (`addCommand`/`addStep`/`addForm`) без собственного контроллера: типизированный `userData` даёт дженерик `BotController<ICoffeeUserData>`. Показаны команды и fallback, многошаговая форма заказа с валидацией, шаг подтверждения с NLU, кнопки, карточка-галерея с ценами, извлечение чисел из фразы, middleware (`rateLimiter`, `requestId`) и `FileAdapter`. Продуктовый слой (`src/menu.ts`): цены напитков и размеров, номер заказа, итоговая сумма и программа лояльности «каждый 5-й кофе в подарок». Два режима запуска: консольный (`BotTest`) и webhook (`Bot.start`). Включает Jest-тесты через `BotTest.simulate()` для Алисы и Telegram. Временно использует локальную версию `umbot` (`file:../..`) до публикации релиза 3.1.0 в npm.
- **Производительность**: `hasAnyNluKey` (экспорт `pUtils`) — фильтр пустых NLU-объектов платформ. Алиса и Маруся присылают `request.nlu` всегда (поле в протоколе), но в большинстве запросов оно пусто; адаптеры больше не записывают пустой nlu: `setNlu({})` семантически идентичен отсутствию вызова (проверено тестами), а геттер `controller.nlu` иначе создавал объект Nlu (+Map кэша) на каждый запрос голосовой платформы. Чат-платформы (Telegram/VK/Max/Viber) пишут в nlu реальные данные (`thisUser`) и не затронуты.
- **Бенчмарки**: `npm run comparison` принимает внешнего участника — `UM_COMPARISON_ROUTER=./my-router.js npm run comparison`. Модуль экспортирует класс с контрактом `addCommand(name, slots, handler)` + `async run(appType, req)`; в таблицах появляется колонка `yours` с теми же сценариями, lockstep-порядком и метриками. Сравнение с umbot можно проверить лично, а не принимать на слово (пример контракта — в заголовке `benchmark/um.js`).
- **Бенчмарки**: новый стенд `npm run compare` (`benchmark/comparison/telegram.js`) — сравнение umbot с реальными Telegram-фреймворками grammy и telegraf. Один Telegram-апдейт на вход всем троим (umbot через TelegramAdapter, конкуренты через их штатный `handleUpdate`); семантика выровнена (подстрочный матч у всех), сеть исключена у всех (`skipAutoReply` у umbot, без `ctx.reply` у конкурентов), lockstep-раунды, p50/p95/RPS/память на запрос. 13 сценариев: 6/50/500/1000 команд × точное/частичное совпадение/регулярка + fallback; найденная команда — в середине списка. Актуальные результаты с оговорками (где umbot уступает или идёт вровень, что именно меряется, как проверить самому) — в BENCHMARKS.md. Стенд изолирован: собственный `benchmark/comparison/package.json` (зависимости ставятся `npm i --prefix benchmark/comparison`, не попадая в package.json репозитория); новые конкуренты добавляются туда же.
- **Бенчмарки**: сравнение с конкурентами расширено на остальные чат-платформы — три новых стенда в той же папке `benchmark/comparison/`, по файлу на платформу (как у Telegram-стенда): `npm run compare:vk` (`vk.js`, конкурент — vk-io + @vk-io/hear), `npm run compare:viber` (`viber.js`, конкурент — viber-bot), `npm run compare:max` (`max.js`, конкурент — официальный @maxhub/max-bot-api). Общая логика (сценарии, lockstep, метрики, печать) вынесена в `runner.js`, платформенные файлы содержат только вход и подключение конкурентов; сценарии и правила честности — те же, что у Telegram-стенда (одинаковый апдейт байт в байт, сеть исключена, подстрочный матч, найденная команда в середине списка; у VK umbot работает с `vk_load_user_info: false` — иначе адаптер по умолчанию ходит в VK API за именем пользователя). Результаты (Windows 10, Node 24): против vk-io umbot лучший в 5 из 12 (vk-io быстрее на 6–50-командных ботах — у него нет цены универсального каркаса; при 500+ командах umbot впереди, а на fallback и частичном матче — почти вдвое, и памяти меньше в 3–10 раз); против viber-bot — лучший в 7 из 12 (на 500–1000 командах быстрее в 4–7 раз, памяти в 2–8 раз меньше; оговорка: viber-bot — единственный и не обновлявшийся с 2022 г. фреймворк Viber в npm); против официального max-bot-api — лучший в 9 из 12 (на 500–1000 командах быстрее в 2–10 раз, памяти на запрос в 8–15 раз меньше). Отдельный стенд Алисы добавлен позже (`compare:alisa`, yandex-dialogs-sdk); для Марусии и SmartApp сторонних фреймворков в npm нет — сравнивать не с кем.
- **Buttons**: метод `ctx.buttons.remove()` — явная просьба убрать ранее показанную клавиатуру. Нужен там, где клавиатура «прилипает» к диалогу и живёт до явной замены: Telegram (reply-клавиатура) и ВКонтакте. У Viber, MAX, Алисы, Маруси и SmartApp клавиатура привязана к сообщению/ответу и исчезает сама, поэтому вызов там безопасен и ничего не меняет. Без этого метода снять клавиатуру было нельзя вообще: пустой список кнопок платформе не отправляется, и старая клавиатура висела у пользователя бесконечно.

### Изменено

- **CLI**: шаблоны генерируемых проектов требуют Node.js `>=22.0.0` (`engines`): совпадает с рантаймом Yandex Cloud Functions (`nodejs22`) и не отсекает Node 22 LTS. В package.json от `from-flow` `engines` теперь тоже задан.

- **Тесты (тестовые артефакты)**: временные папки сьютов переведены из `os.tmpdir()` (недра `AppData/Local/Temp` — артефакты упавшего прогона было не найти) и фиксированных путей в корне репо (`tests/Utils/__util_tmp__`, `__app_context_logs__` — гонки между параллельными прогонами Jest) в единый корень `tests/.tmp/<suite>-<rand>/` (мьюткан в `.gitignore`). Хелпер `tests/helpers/tmpDir.ts`: `createTestDir(suite)` / `await removeTestDir(dir)` (дожидается хвоста in-flight записи tmp+rename — иначе папка пересоздавалась после удаления осиротевшим `.tmp`-хвостом). Отладка упавшего прогона: `UMBOT_TEST_KEEP=1` (все сьюты) или `UMBOT_TEST_KEEP=<suite>` (выборочно) оставляет артефакты в `tests/.tmp`; зачистка — `npm run clean:tmp`. Заодно закрыты утечки таймеров в сьютах FileAdapter: `destroy()` в afterEach (init предзагружает три таблицы, тесты закрывали только одну — debounce-таймеры остальных пересоздавали папку) и `clearTimeout` перед занулением кэша в тесте destroy (таймеры продолжали жить в очереди Node после сьюта).
- **Безопасность типов**: в `tsconfig.json` включены `noUncheckedIndexedAccess` и `exactOptionalPropertyTypes` (рекомендация аудита безопасности). Индексный доступ к массивам/объектам теперь возвращает `T | undefined`, а опциональные поля не принимают явный `undefined`. Весь `src/` приведён к новым флагам: добавлены guard-проверки на lookup-доступ (`for...of` вместо индексных циклов, ранние guard-выходы вместо `arr[0].field`), опциональные поля заполняются только реальными значениями, `x.field = undefined` заменены на `delete x.field`. Поведение в рантайме не изменилось (1347 тестов зелёные), производительность ядра сохранена (~81k RPS в stress-lite).
- **Вебхук**: нераспознанный или неразбираемый запрос платформы теперь возвращает `400`, а не `500` (`webhookHandle`, `webhookEvent`). На `5xx` Telegram и VK включают повторную доставку и в итоге отключают вебхук, хотя проблема в самом запросе, а не в сервере. (См. «Миграция с 3.0.x».)
- **Telegram**: `isPlatformOnQuery` определяет платформу по наличию `update_id`, а не по списку известных полей — теперь распознаются все типы апдейтов (`my_chat_member`, `poll_answer`, `message_reaction` и др.).
- **MAX**: `isPlatformOnQuery` дополнительно принимает неизвестные типы событий по связке `update_type` + `timestamp`, поэтому новые события платформы не ломают обработку.
- **VK**: результат `users.get` кэшируется в памяти процесса (TTL 1 час, до 5000 записей) — раньше запрос уходил на каждое входящее сообщение и удваивал число обращений к API. Кэш не хранит неудачные запросы (сбой API не «замораживает» имя пользователя на час). Загрузку можно отключить опцией адаптера `vk_load_user_info: false`, сброс кэша — экспорт `clearVkUserCache()`.
- **Viber**: раскладка `rich_media` считается по сетке платформы: `Columns` — это доля от `ButtonsGroupColumns = 6`, а не количество карточек (подтверждено документацией Viber). Одиночная карточка занимает всю ширину, галерея раскладывается так, чтобы уложиться в `ButtonsGroupRows = 7`.
- **Middleware**: в JSDoc `rateLimiter` явно описано, что лимит считается на пару `{platform}:{userId}` и что попадание в очередь задерживает ответ минимум на секунду — с учётом таймаутов платформ (у Алисы ~3 с).
- **Telegram**: автоматический `parse_mode` отключён. Разметка применяется только при явном `telegram_parse_mode`; разработчик сам отвечает за экранирование динамических данных. (См. «Миграция с 3.0.x».)
- **Telegram**: устаревший режим `markdown` заменён на `MarkdownV2`.
- **Telegram**: `sendPoll` приведён к Bot API 10.2: 1–12 объектов `InputPollOption`, актуальное поле `correct_option_ids` и проверка индексов без незаметного удаления вариантов.
- **Telegram**: `sendMediaGroup` сохраняет `media` при передаче дополнительных параметров через `FormData` и отклоняет группы вне диапазона 2–10 без молчаливой потери элементов.
- **API версий**: VK API обновлён с 5.103 до 5.199; версия Viber API по умолчанию повышена с 2 до 7 (вынесено в константу `VIBER_DEFAULT_API_VERSION`).
- **Yandex SpeechKit**: исправлен некорректный код языка `en_EN` на стандартный `en-US`. Добавлены новые голоса: `kirkorov`, `lera`, `madusa`.
- **Команды**: базовые команды (`welcome`, `help`), добавленные через `addCommand` без явного указания слотов, теперь автоматически регистрируются со слотами по умолчанию.
- **MongoDB**: пакет `mongodb` переведён в опциональные peer-зависимости и подгружается лениво (динамический `import`) только при вызове `MongoAdapter.connect()`. Пользователи, которым Mongo не нужен (например, только `FileAdapter`), больше не обязаны его устанавливать. Если пакет не установлен, `connect()` возвращает `false` и пишет в лог инструкцию `npm install mongodb` вместо падения процесса.
- **Ядро (рефакторинг)**: константы `FALLBACK_COMMAND`, `WELCOME_INTENT_NAME`, `WELCOME_INTENT_SLOTS`, `HELP_INTENT_NAME`, `HELP_INTENT_SLOTS` больше не реэкспортируются из `AppContext` и `CommandReg` — все потребители и публичный баррель `core/index.ts` ссылаются напрямую на листовой модуль `core/constants.ts`. Публичный API не изменился: константы по-прежнему доступны из корневого `umbot`.
- **Ядро (рефакторинг)**: разорвано последнее runtime-ребро циклической зависимости `core/Bot → controller`. Дефолтный `BaseBotController` подгружается лениво (`require` внутри `#getBotController`) вместо статического импорта в шапке файла — к моменту вызова конструктора все модули уже инициализированы. Типы `BotController`/`IUserData`/`IPlatformData` импортируются как `import type` и стираются при сборке. Поведение и публичный API не изменились.
- **Примеры**: `examples/` переведены в единый запускаемый пакет (`npm install && npm run build && npm run <пример>`): добавлены общие `package.json` и `tsconfig.json`, скрипты запуска для каждого примера, примеры `game`/`gameInCommand` приведены к общей структуре без вложенных пакетов. Код примеров актуализирован под текущий API (`IButtonType`, `IUserData`, индексация в DB-адаптере) и исправлен перезапуск `userData` на `Object.assign` (анти-паттерн, ломающий сохранение данных). Компиляция примеров против текущего API проверяется тестом `tests/examples/compile.test.ts`.
- **BotController**: если `action()` объявлен как `async` и вернул Promise, фреймворк теперь пишет предупреждение в лог с подсказкой использовать async-колбэки `addCommand`/`addStep`/`addForm`. Раньше такой код компилировался без ошибки, но всё после первого `await` молча не попадало в ответ. Ошибки внутри такого промиса логируются вместо `unhandledRejection`.
- **Производительность**: горячий цикл поиска команд (`BotController._getCommand`) переведён с итерации `for...of` по `Map` на индексированный обход по массиву-снимку (`CommandReg.getActualCommandsList()`). Итератор `Map` аллоцирует пару-массив `[ключ, значение]` на каждый шаг (спецификация JS), что при скане давало до 7,8 КБ транзиентного мусора на запрос (1500 команд, матч в середине). Снимок — пары `[ссылка-строка, ссылка-объект]`, копий данных нет; пересобирается лениво только при изменении набора команд (addCommand/removeCommand/clearCommands инвалидируют флаг, сборка — при первом поиске). Измерения: мусор скана −67..100%, регистрация 20 000 команд осталась O(n) (~16 мс), постоянная память снимка ~82–136 Б на команду.
- **Производительность**: быстрый путь для команд с единственным RegExp-слотом без флагов `g`/`y` (поле `ICommandParam.__$singleStatelessRegExp`, вычисляется при регистрации). Такие команды проверяются прямым `re.test()`, минуя обёртку `Text.isSayText`: −37 нс (−54%) и −40 Б мусора на каждую проверенную команду скана. Условия безопасности: нет кастомного RegExp-движка (`plugins.regExp`), нет stateful-флагов — иначе прежний путь. Семантика не изменилась (сброс `lastIndex` сохранён).
- **Производительность**: `AppContext.usedMetric` кэшируется в приватное поле и пересчитывается только в `setLogger` (геттер вызывается несколько раз на каждый запрос из горячего пути). `Bot.#getAppContent` читает `isVoice` с переданного адаптера вместо повторного lookup `platforms[appType]`.
- **Производительность**: `AlisaAdapter.getContent()` не сериализует пустой state через `JSON.stringify` + `Buffer.byteLength` (пустой объект отправляется как `{}` без проверки лимита — 2 байта всегда в норме). Форма ответа не изменилась: по документации Яндекса отсутствие `session_state` сбрасывает стейт сессии, поэтому поле по-прежнему отправляется всегда.
- **Производительность**: `BasePlatform._initTTS` не сканирует текст на `#`/`<`, когда tts пустой или из 1 символа (маркер звука не короче 2 символов).
- **Производительность**: `AlisaAdapter._getResponse` переиспользует уже обрезанный text для tts в голосовом кейсе (`controller.tts === controller.text && text.length <= 1024` — в голосовом режиме Bot подставляет tts ссылочно из text). При длине ≤ 1024 обе функции (`Text.resize` и `resizeAlisaTts`) возвращают исходную строку без изменений — поведение идентично, а regex-скан и конкатенации `resizeAlisaTts` уходят из горячего пути (~0.1 мкс на каждый ответ Алисы).
- **Производительность (диагностика не в горячем пути)**: предупреждение «Платформа не поддерживает локальное хранилище» (`Bot.#runApp`) депонируется — выводится один раз на платформу за жизнь инстанса, а не с каждым запросом. Условие зависит только от конфигурации (`isLocalStorage` + поддержка платформы + DB-адаптер), но проверка стояла в горячем пути: на мультиплатформенных ботах (Алиса с localStorage + Telegram/VK/Viber/MAX без) каждый чат-запрос собирал warning-строку и прогонял её через конвейер маскирования секретов — ~4 мкс и ~1.5 КБ мусора на запрос. Эффект в сравнении с конкурентами (стенд `npm run compare*`, до/после): против vk-io umbot был лучшим в 4 из 12 сценариев — стал в 7 из 13, отставание на 6-командных ботах упало с 3.3× (7.3 против 2.2 мкс) до 1.2× (4.1 против 3.4 мкс), а сценарии с регулярками на 500–1000 команд из проигрыша (63.6 против 57.9 мкс) вышли в победу (78.3 против 93.5 мкс; vk-io в этих же прогонах деградировал до 68.6). Против viber-bot — 7 из 12 → 11 из 13; против max-bot-api — 10 из 12 → 11 из 13. Память на запрос упала с 6.2–6.6 до 4.5–5.1 КБ.
- **Производительность (чаты, ленивый NLU)**: кэш сущностей `Nlu.#cachedData` создаётся при первом обращении, а не в конструкторе. Чат-платформы пишут в NLU только `thisUser` и к сущностям не обращаются — пустой Map аллоцировался на каждый запрос впустую. Данные отправителя записываются через новый хелпер `setThisUserToNlu` (экспорт `pUtils`): если все поля пусты (анонимный канал, битый апдейт, `vk_load_user_info: false` без данных), запись пропускается целиком и объект Nlu не создаётся — зеркально `hasAnyNluKey` для голосовых платформ. Адаптеры Telegram/VK/MAX/Viber переведены на хелпер.
- **Производительность (скан команд)**: из горячего цикла `_getCommand` убран optional-chaining доступ к элементам снимка (`commandTuple?.[0]`/`?.[1]`): после полного GC (compaction двигает объекты) полиморфный доступ восстанавливался медленнее монолитного цикла конкурентов — GC-чувствительность скана была одной из причин узкого проигрыша vk-io в regex-сценариях на стендовой методике (forceGC перед каждой серией). Теперь полиморфизм по «дырявым» элементам снимка закрыт явной проверкой кортежа.
- **Производительность (аллокации)**: замыкание обработки ошибок в `BotController.#commandExecute` создаётся лениво — только когда ошибка действительно произошла (в счастливом пути не аллоцируется); `MaxAdapter.#setMessageData` не создаёт `requestData`-хранилище, когда в апдейте нет `chat_id`.
- **Производительность (группировка RegExp-слотов)**: команды, у которых ВСЕ слоты — готовые `RegExp` (без явного `isPattern`), теперь включаются в группы регулярок, как будто `isPattern = true`. Раньше счётчик группировки считал только явные isPattern-команды, поэтому 1000 готовых RegExp-слотов сканировались линейно (50 мкс/запрос на «1000 команд, регулярка»). Теперь группа комбинирует их в один combined-RegExp: 34.5 мкс против grammy 328.6, vk-io 51.5, viber-bot 141.4, max-bot-api 166.6 — из паритета/проигрыша в победу с отрывом 1.5–9.5×. Смешанные слоты (строка + RegExp) в группу НЕ попадают: их приоритет относительно строковых команд задаётся позицией в списке, а группа матчит «первый подходящий». Семантика матча не изменилась (проверено интеграционными тестами: первый/средний/последний член группы, приоритет с пост-зарегистрированной широкой командой).
- **Исправлено (группы регулярок, debounce)**: при регистрации группы из N команд промежуточные состояния могли «застыть» — `groupData.regExp` получал скомпилированный RegExp от НЕПОЛНОГО набора паттернов (1–3 из 20), и любой запрос, пришедший раньше 35-мс debounce-таймера, получал урезанную группу: часть команд группы не находилась (воспроизведено тестом: последний член группы не матчился). Теперь во время debounce `regExp` остаётся строкой, `getGroupRegExpCompiled` компилирует полную строку через кэш (запросы до таймера получают всю группу), таймер затем подменяет её прогретым объектом; добавлен прогрев JIT первой компиляции. Баг затрагивал и прежние явные isPattern-команды. Регрессионные тесты: `tests/Core/groupDebounce.test.ts`.
- **Производительность (ленивый thisUser в NLU)**: данные отправителя чат-платформ (username/first_name/last_name) записываются в приватный буфер контроллера (новый публичный метод `BotController.setThisUser`), а объект `Nlu` создаётся только при первом обращении бизнес-логики к `controller.nlu` (буфер переносится в Nlu автоматически). Раньше любой непустой `from` в апдейте создавал объект Nlu (+кэш) на каждый запрос, даже если логика NLU не читала — при 1000 RPS это ~40 КБ/с мусора впустую. `nlu.getUserName()` возвращает те же данные, что и раньше. Адаптеры Telegram/VK/MAX/Viber переведены на буфер (хелпер `setThisUserToNlu`), `clearStoreData()` сбрасывает буфер. Память на запрос: 4.6–5.1 → 3.8–4.6 КБ.
- **Производительность (мелочи горячего пути)**: `getExactMatchCommand` выходит раньше по пустой exact-карте (реестр без строковых команд не платит хэш-lookup по длинной строке — `Map.size` O(1)); пустой список интентов — замороженная константа вместо литерала `[]` на каждый fallback-запрос; инлайн бывшего `Bot.#getAppContent` в `#runApp` — минус один промис-хоп из цепочки каждого запроса; блок storage-инициализации вынесен в `#initRequestState` (читаемость горячего метода, без изменения поведения).
- **Бенчмарки (методика замера)**: стенды `npm run compare*` переведены на честную методику: разделённые фазы латентностей и памяти (GC не искажает замер), интерливинг раундов между участниками, единая пауза после регистрации команд (хвосты инициализации не попадают в замер), прогрев каждого участника до плато JIT перед раундом и порог победы «отрыв >5% и >0.3 мкс, иначе паритет». Побочно: Telegram-стенд `competitors.js` заменён на `telegram.js` в едином формате платформенных файлов, добавлен сценарий «50 команд, fallback». Актуальные результаты с оговорками (где umbot уступает или идёт вровень, что именно меряется, как проверить самому) — в BENCHMARKS.md. Подробные таблицы и история уточнений методики — в BENCHMARKS.md.
- **Бенчмарки (Alisa)**: новый стенд `npm run compare:alisa` (`benchmark/comparison/alisa.js`) — сравнение umbot с yandex-dialogs-sdk, самым скачиваемым фреймворком навыков Алисы в npm. Обработка через штатный `handleRequest` SDK (ровно путь его webhook-слоя); семантика выровнена (SDK `command(строка)` матчит только точное совпадение — проверено тестом, поэтому конкуренту слоты задаются RegExp); fallback — `any()`. Результаты (Windows 10, Node 24, методика v4): umbot лучший во всех 13 сценариях — от 1.7 против 3.1 мкс (6 команд, точное) до 35.7 против 207.7 (1000, регулярка) и 41.9 против 248.0 (1000, fallback); памяти 4.4–7.4 против 20–89 КБ на запрос. Оговорка честности: SDK не обновлялся с 2022 и не даёт аналога команд-шагов umbot из коробки (его Stage/Scene опциональны и в стенд не входят). Для Марусии и SmartApp сторонних фреймворков в npm по-прежнему нет.
- **Бенчмарки (стресс-стенд под потоком)**: новый стенд `npm run stress:compare:*` (`benchmark/comparison/stress.js` + `stress-worker.js`) — поведение под непрерывным потоком, а не латентность одного запроса: окно 200 запросов in-flight, 30 с, трафик-микс (точное/частичное/регулярка/fallback = 40/25/25/10), 1000 команд, ротация 1000 разных user_id (апдейты несут разных from_id/user_id — прогревает per-user пути и кэши), каждый участник в изолированном дочернем процессе (падение по heap limit роняет только его строку). Метрики: выполнено запросов и RPS под потоком, p50/p95/p99, доля времени в GC (perf_hooks-observer), тренд пола кучи, retained-дельта после финального GC (утечка). Результаты (Windows 10, Node 24): VK — umbot 75 420 RPS против 31 985 у vk-io (2.4×); Telegram — 78 487 против 370 у grammy и 253 у telegraf (212–310×; конкуренты при 1000 команд тонут в GC — до 66% времени: у grammy/telegraf каждый `hears` — отдельный async-слой middleware, промах проходит все 1000 слоёв); Alisa — 69 190 против 2 734 у yandex-dialogs-sdk (25×: его роутинг — Promise.all по всем командам на каждый запрос ради Levenshtein-релевантности, проверено по исходникам); Viber — 73 654 против 6 698 у viber-bot (11×); MAX — 75 290 против 742 у max-bot-api (101×). Утечек нет ни у одного участника. Метрика retained исправлена: раньше массив задержек самого воркера (8 байт на запрос) был жив в момент замера и давал ложную «утечку», пропорциональную числу запросов (у umbot 8–10 КБ/1000, у telegraf «177 КБ/1000», у max-bot-api «57 КБ/1000» — всё артефакт). Итоговая сводка по обеим методикам и всем конкурентам — в BENCHMARKS.md («Общая картина по всем платформам»), включая зоны честных уступок (vk-io на «6 командах» и fallback). Подготовка стенда вскрыла и задокументированную особенность: без fallback-команды `*` базовый контроллер на промахе отправляет ответ в API платформы — для офлайн-стенда это fetch-таймауты по 30–100 мс на каждом 10-м запросе, стенд регистрирует `*` с `skipAutoReply`.
- **Бенчмарки (cold-start и IQR)**: основной стенд дополнен двумя метриками. Cold-start — время первых 200 запросов после регистрации без прогрева (прода-метрика serverless/Cloud Functions: латентность первого запроса после подъёма инстанса; печатается отдельным блоком, числа несопоставимы со steady-state). Замер на VK: umbot на 500–1000 командах быстрее vk-io даже холодным стартом (точное совпадение O(1) не требует прогрева), на 6–50 командах легче стартует vk-io. IQR (p75−p25) — ширина ядра распределения латентности: p95 дёргается от единичных выбросов ОС, IQR устойчивее. Платформенные файлы стендов переведены на `buildBenchOptions()`-экспорт с guard `require.main` — их теперь подключают как модули stress/baseline-инструменты, не запуская стенд.
- **Бенчмарки (регрессионный baseline для CI)**: `npm run baseline:update|check` (`benchmark/comparison/baseline.js`) — night-job-контроль латентности: p50 umbot на маркерном сценарии «50 команд, точное совпадение» всех пяти стендов (медиана 3 раундов с прогревом до плато) против `baseline.json` с допуском +15% (JIT-шум между прогонами); `check` возвращает exit 1 при регрессии. Ловит «кто-то занёс тяжёлый компайл в горячий путь» автоматикой, а не ручным прогоном. Baseline создаётся на машине CI в setup-джобе, check гоняется ночным джобом на той же машине.
- **Бенчмарки**: проверка доступной памяти в `bench` и `stress` учитывает реальный потолок min(лимит heap V8, cgroup-лимит docker/k8s, физическая память с учётом сброса page cache) вместо `os.freemem()`. Раньше на unix-машинах, где память занята page cache, тест ложно отказывал в запуске даже для 1000 команд при 16 ГБ RAM.
- **Бенчмарки**: `predictMemoryUsage` в `benchmark/command.js` пересчитан по фактическим замерам (~466 Б/строковая команда, ~0.8 КБ/isPattern, ×12 прогонов на каждый count с учётом фрагментации V8 кучи). Старая формула (2 Б/команду) была занижена в сотни раз — на 200 000 команд тест падал по heap OOM с дампом вместо честного сообщения «Недостаточно памяти». Теперь тест честно отказывается от 200 000+ команд и печатает рекомендацию. В `stress-test.js` формула наоборот завышена в 14 раз — приведена к фактическим 1 КБ/команду.
- **Бенчмарки**: приведён к профессиональному виду вывод всех запускаемых бенчмарков. `command.js`: исправлен XOR-баг в `memResult` — ветка ГБ была недостижима (`(abs < 1024) ^ 2` всегда truthy), 2 ГБ печатались как «2048.00MB», единицы KB/MB/GB не соответствовали переданным килобайтам; времена ниже 1 мс печатались как «0.0002800 мс» (7 знаков) — теперь «0.28 мкс»; двойной знак «+-4.63 КБ» у отрицательных приростов убран. `stress-test.js`: «MB» в русском выводе → «МБ», «Rss» → «RSS» (аббревиатура), «p95 latency» → «p95 (время ответа)». `um.js`: исправлена опечатка «ассихронный» в названии сценария, добавлена легенда вердиктов (`++` / `== паритет` / `+-` / `--`) — раньше их приходилось расшифровывать по звёздочкам в подвале.
- **Публичный API (типы)**: класс `CommandReg` (тип публичного поля `AppContext.command`), интерфейс `IDangerRegex` (возврат `CommandReg.isDangerRegex()`) и тип `ITelegramWarnContext` (параметр `prepareTelegramMessageText()`) экспортированы из `umbot` и `umbot/plugins` — раньше они фигурировали в сигнатурах публичных членов, но не попадали в сгенерированную документацию и не были доступны потребителям для типизации. Чистое расширение, обратная совместимость не затронута. Заодно устранены все предупреждения typedoc (мёртвые entry points `cli/index.ts`/`src/docs`, битая ссылка на приватный `#getCache` в JSDoc `Nlu`).
- **Ссылки**: публичный адрес документации изменён с `https://www.maxim-m.ru/bot/ts-doc` на `https://www.maxim-m.ru/docs/umbot` — обновлены все ссылки в README, гайдах `src/docs/`, `cli/README.md`, примерах и скриптах, генерирующих ссылки (`scripts/fix-doc.js` — `baseUrl` для переписывания markdown-ссылок; `scriptDoc.js` — `DOCS_URL` для canonical/og:url/sitemap.xml/llms.txt). Файлы на сервере не переносятся: путь `/docs/umbot/*` обслуживается внутренней перезаписью Apache из прежней физической папки `bot/ts-doc`, а старые ссылки `/bot/ts-doc/*` пока получают временный (302) редирект — после стабилизации его стоит поменять на постоянный (301), чтобы поисковики склеили адреса (canonical/og:url/sitemap уже указывают на новый путь). Готовые правила для `.htaccess` — `htaccess-docs.txt` в корне репозитория.
- **Ссылки**: все ссылки на репозиторий GitHub переведены с `max36895/universal_bot-ts` на `max36895/umbot` (README, SECURITY.md, CHANGELOG, `typedoc.json`, `package.json`, примеры, live-тест) в связи с переименованием репозитория; старые адреса продолжают открываться через штатный редирект GitHub.

### Безопасность

- **Алиса**: `session.skill_id` интерполировался в URL Yandex API без экранирования (`skills/${skillId}/images` у YandexImageRequest/YandexSoundRequest). Вебхук Алисы не подписывается, значит skill_id полностью контролируется отправителем — `../../other-skill/images?x=` уводил запрос с OAuth-токеном за пределы пути навыка. Значение экранируется через `encodeURIComponent` (зеркально фиксу userId у SmartApp в этом же релизе). Регресс-тесты в `tests/Request/YandexImageRequest.test.ts` и `tests/Request/YandexSoundRequest.test.ts`.
- **[Критично] CLI**: инъекция кода в генератор проектов. Последовательность `*/` в имени команды, шага, блока или в тексте ответа закрывала JSDoc-комментарий в `src/index.ts`, и всё, что шло следом, попадало в сгенерированный проект как исполняемый код. Так как `flow.json` приходит из визуального редактора и может быть получен извне, это давало выполнение произвольного кода в проекте пользователя. Добавлено экранирование (`escapeComment`) во всех местах, где текст из flow попадает в комментарий, и регресс-тест в `tests/cli/flowGenerator.test.ts`.
- **[Критично] Логи**: конвейер `maskSecrets` не покрывал реальные форматы секретов. Токен Telegram в «голом» виде (`<bot_id>:<35 символов>`, как он лежит в `.env`) не маскировался — прежний шаблон требовал литеральный префикс `bot` и срабатывал только внутри URL API; шаблон VK (`vk1a<79 символов>`) не совпадал ни с одним реальным токеном (настоящий формат — `vk1.a.<payload>` с точками). Добавлены шаблоны для голого Telegram-токена (включая хвосты короче 35 символов), реального формата VK, JWT (Сбер SmartApp, OAuth), IAM-токена Яндекса (`t1.<seg>.<seg>` — точки разбивали строку на сегменты короче порога), токена Viber (~46 hex, ниже порога), MAX-токена (UUID с дефисами), Яндекс OAuth (`y0_…`/`y1_A…`), Api-Key SpeechKit, пароля БД по ключам `pass`/`password` и `access_token` в JSON-строках. Плюс контейнерное маскирование: объекты и массивы под ключами `token`/`secret`/`password`/`api_key`/`authorization` (и т.п.) маскируются целиком на любой глубине вложенности — раньше `tokens: {telegram: ...}` попадал в лог как есть. Все прежние покрытия сохранены. Регресс-тесты в `tests/Core/maskSecrets.test.ts`.
- **[Критично] SmartApp**: `userId` подставлялся в URL внешнего хранилища без экранирования. У SmartApp нет проверки подписи вебхука, поэтому `uuid.userId` полностью подконтролен отправителю: значение вида `../../admin?x=` выводило запрос за пределы пути хранилища и позволяло подменить query-параметры. Значение экранируется через `encodeURIComponent`.
- **ReDoS**: небезопасное регулярное выражение вне `strictMode` регистрировалось с обычным предупреждением. Без пакета `re2` (он опциональный) движок Node уязвим к катастрофическому бэктрекингу — на выражении вроде `^(a+)+$` одно сообщение из 41 символа занимает поток на минуты, и бот перестаёт отвечать всем пользователям. Теперь такая связка логируется как ошибка с объяснением последствий и тремя вариантами решения (`npm i re2`, `strictMode`, переписать выражение). ⚠️ Сама возможность зарегистрировать опасное выражение вне `strictMode` сохранена ради обратной совместимости — это решение остаётся за разработчиком приложения.
- **CLI**: инъекция в генерируемые конфигурационные файлы. Ключи и значения токенов из `flow.json` записывались в `.env` и `serverless.yml` без санитизации: перевод строки в ключе или значении позволял дописать произвольные переменные окружения или YAML-поля (файл приходит из визуального редактора и может быть получен извне). Имена переменных приводятся к безопасному виду `A-Z0-9_`, значения очищаются от переводов строк, а при перезаписи существующего `.env` выводится явное предупреждение. Регресс-тесты в `tests/cli/flowGenerator.test.ts`.
- **[Критично] CLI/Cloud**: command injection в генерируемом `scripts/deploy.js` на Windows. Скрипт вызывал `spawnSync('yc', args, { shell: true })`, при котором Node склеивает команду и аргументы в одну строку без экранирования: спецсимволы (`&`, `|`, `"`, `^`) в значении токена из `.env` исполнялись командной оболочкой, а значения приходят из недоверенного `flow.json`. Аргументы теперь оборачиваются в двойные кавычки (для cmd внутри кавычек метасимволы литеральны), значения `.env` чистятся от переводов строк, управляющих символов и кавычек. Регресс-тест в `tests/cli/flowGenerator.test.ts`.
- **Логирование**: `logMetric` передавал имя метрики и label в кастомный логгер без маскирования. `Request` кладёт в label полный URL запроса, а для Telegram он содержит токен бота (`https://api.telegram.org/bot<ТОКЕН>/...`) — токен утекал в системы наблюдаемости. Label метрик проходит тот же конвейер маскирования, что и logError/logWarn. Регресс-тест в `tests/Core/maskSecrets.test.ts`.
- **CLI**: `create` с параметрами записывал токены/пароли в `.env` без вырезания переводов строк — значение вида `x\nMALICIOUS_VAR=1` дописывало произвольные переменные окружения. Значения санитизируются так же, как в генераторе из `flow.json`. Регресс-тест в `tests/cli/consoleController.test.ts`.
- **FileAdapter**: выборка по первичному ключу `__proto__`/`constructor`/`prototype` возвращала унаследованные свойства `Object.prototype` как «найденную запись» (пользователь с таким platform-id ошибочно считался существующим, а его данные потом молча не сохранялись). Чтение защищено тем же guard'ом, что запись и удаление. Регресс-тесты в `tests/DbModel/fileAdapterSelect.test.ts`.
- **ReDoS**: `isRegexLikelySafe` получила эвристику «лестницы квантифицированных атомов» — раньше паттерны вида `.*.*.*!`, `x+x+y`, `\w*\w*\w*!` (последовательные неограниченные квантификаторы по пересекающимся классам символов, без групп) проходили проверку во всех режимах, включая `strict_prod`, и давали до ~75 секунд блокировки event loop на одном сообщении из 1024 символов (замер Node 24). Повторный red-team-аудит вскрыл обходы первого варианта эвристики, финальная версия закрывает и их: **тела групп анализируются рекурсивно** с «склейкой» хвоста цепочки изнутри группы с внешними атомами — без этого `(?:a*a*)c` проходил проверку и блокировал поток на ~2.7 минуты на 7000 символах; ловятся **backreference-лестницы** `(a+)(?:\1)+x`, `([a-z]+)[^]*\1z` (backref после неограниченного квантификатора) и `[^]` как взрывное тело группы. Ложные срабатывания первого варианта сняты: цепочки 3+ без хвоста (`.*.*.*`, `\w+\w+\w+`, `[0-9]+[0-9]+[0-9]+`) опасны только с последующим якорем конца строки `$`/`\Z`/`\z` (замер: без якоря V8 завершает early-exit за 0 мс, с якорем — таймаут), поэтому легитимные паттерны, ломавшиеся в strict_prod, снова проходят. Непересекающиеся классы (`[a-z]+[0-9]+x`) и пары без хвоста (`.*.*`) проходят как раньше. Ограниченные интервалы внутри квантифицированной группы (`(\d{1,3}\.){3}\d{1,3}` — IP-адрес, `(a{1,3}){3}`) проходят: ревью релиза показало, что раньше `isExplosiveGroupBody` браковал любой `{`/`.` в теле группы, и легитимный IP-паттерн молча отключался в strict_prod. Опасность теперь определяется по неограниченности внутреннего квантификатора (`+`, `*`, `{n,}` опасны; `{n}`/`{n,m}` — нет), а «интервал под неограниченным внешним квантификатором» (`(?:a{2,3})+`, `(a+){10,100}`, `(a{1,}){3}`) по-прежнему блокируется.
- **ReDoS**: `Bot.#runApp` обрезает `controller.userCommand` до 7000 символов (максимум легитимного сообщения среди платформ, лимит Viber) до NLU и матчинга команд. Платформенные лимиты Алисы (1024) фреймворком не проверялись: напрямую сконфигурированный вебхук без подписи мог протолкнуть в регулярки строку до 2 МБ. `originalUserCommand` не трогается — бизнес-логика сохраняет полный текст.
- **Исходящие запросы**: `Request` выставляет `redirect: 'manual'` — клиенты фреймворка ходят на фиксированные endpoint'ы API платформ с секретами в заголовках (`Authorization` у MAX, `X-Viber-Auth-Token`, OAuth у Яндекса) и в URL (токен Telegram в path). Автоследование редиректу при компрометации DNS/CDN унесло бы секреты на сторонний хост; теперь 3xx — ошибка запроса.
- **CLI**: `npx umbot create config.json` мигрирует токены из вложенного формата `config.tokens.{platform}.token` (задокументирован в README) в `.env` при `isEnv: true`, а при `isEnv: false`/отсутствии флага — вычищает их из генерируемого `src/config/*Config.ts` с предупреждением в консоль (раньше без флага токены сериализовались литералом в коммит-файл, не покрытый `.gitignore` сгенерированного проекта). Имя env-переменной из ключа платформы санитизируется до `A-Z0-9_`: ключ с переводом строки не может дописать произвольную переменную в `.env`.
- **CLI**: `create from-flow` больше не перезаписывает существующий `.env` — пользовательские значения (вписанные вручную реальные токены) сохраняются, дописываются только недостающие переменные. Раньше повторная генерация с `--force` молча затирала реальные токены значениями из flow.json.
- **CLI**: `sanitizeEnvValue` в генерируемом `scripts/deploy.js` экранирует `%` удвоением (`%%` раскрывается cmd.exe в литеральный `%`), а не вырезает: cmd раскрывает `%VAR%` даже внутри двойных кавычек, и значение из недоверенного flow.json могло подставить содержимое переменных окружения машины пользователя в аргументы деплоя — при этом легитимные секреты с процентом больше не искажаются.
- **Mongo**: `MongoAdapter` разделяет операторы на разрешённые в условиях выборки и опасные. Документированный API операторов сравнения (`$gt`, `$gte`, `$lt`, `$lte`, `$ne`, `$in`, `$nin`, `$or`, `$and`, `$elemMatch` и др. — JSDoc `IQueryData`, dbAdapter.md) продолжает работать; отклоняются до драйвера исполняющие на сервере БД операторы (`$where`, `$function`, `$accumulator`, `$expr`) и любые незнакомые `$`-операторы, ключи прототипа (включая dotted-пути вида `'a.__proto__.x'`, `'user.$ne'`) и вложенность глубже 10 уровней. В данных записи (update/insert) `$`-ключи запрещены полностью: фреймворк сам оборачивает данные в `$set`, а Mongo не хранит `$`-поля. Стандартный поток и раньше не пропускал операторы (объекты размываются `escapeString`), но кастомные модели и пользовательские `where` могли пронести их в `findOne`/`updateOne` напрямую.
- **ipFilter**: при запросе без IP клиента (fail-open by design для тестов/консоли) пишет предупреждение — однократно на инстанс middleware.
- **Bot**: предупреждения при `bot.start()` о небезопасной конфигурации (не блокируя запуск): режим `dev` (проверка ReDoS отключена), платформы с неподтверждённым вебхуком (секрет задан, но проверка подписи не включена — по точной логике каждого адаптера через новый опциональный метод `IPlatformAdapter.isSignatureCheckEnabled()`; определение платформы учитывает и VK, у которого подпись приходит в теле, без `signatureName`), прослушивание всех интерфейсов (`0.0.0.0`/`::`). Новый контракт `signatureName`/`isSignatureCheckEnabled` в `IPlatformAdapter` — backwards-compatible расширение.
- **Документация**: JSDoc `AppContext.log()` явно предупреждает, что аргументы НЕ маскируются (в отличие от `logError`/`logWarn`/`logMetric`) — метод предназначен для операционных сообщений, секреты передавать нельзя.
- **Бенчмарки**: `benchmark/request.js` — убраны захардкоженные живые токены Telegram/VK и ID чатов; теперь читаются из переменных окружения. Токены из git-истории считаются скомпрометированными — выведите их через @BotFather / настройки приложения VK.
- **ReDoS / re2**: при установленном `re2` RegExp-объекты из слотов команд выполнялись штатным движком Node в обход него — через `getRegExpOrSelf`, быстрый путь одиночного RegExp-слота и прямое использование слотов при числе команд меньше 500. Теперь такие слоты один раз пересобираются через `re2` (с кэшем); если `re2` не поддерживает синтаксис выражения (lookbehind, обратные ссылки), используется исходный объект.
- **ReDoS**: убраны квадратичные регулярки, работающие с входными данными: снятие тегов `/<[^>]*>/` (текст Telegram при обрезке HTML, текст Маруси из TTS, речь для SpeechKit) заменено линейной функцией `stripTags` (экспортируется из `umbot/utils`), проверка SSML в SmartApp, поиск номера элемента в `Navigation.selectedElement` и поиск ссылок `Nlu.getLink` переписаны без откатов. Поведение прежнее.

### Исправлено

- **Медиа без БД (все платформы)**: загруженный токен картинки/звука возвращался только при успешной записи в БД (`model.save()`), а без DB-адаптера запись всегда неуспешна — картинки и звуки из локальных файлов не доходили до пользователя на VK, MAX, Алисе, Марусе (у Telegram терялся кэш), хотя загрузка выполнялась. Кэширование вынесено в `cacheMediaToken`: токен возвращается всегда, неудачная запись в БД — только предупреждение.
- **Алиса/Маруся (кнопки)**: запрос `ButtonPressed` по протоколу не содержит `command`/`original_utterance` — только `payload` и `nlu.tokens`. Для объектного payload команда бралась из пустого `command`, и нажатие кнопки уходило в fallback. Теперь команда берётся из `payload.command`/`payload.action` (как `addAction` на Telegram/VK/MAX), иначе — из текста кнопки. Строковый payload кнопки (протокол требует JSON-объект) оборачивается в `{command: '...'}`; у Маруси из кнопки убрано недокументированное поле `hide`.
- **Алиса/Маруся (health-check)**: ответ на `ping` не содержал обязательного `end_session` (Алиса) и `session` (Маруся) — невалидный по протоколу ответ на периодическую проверку, из-за которой Яндекс отключает «неотвечающие» навыки.
- **Маруся (карточки)**: формат приведён к протоколу скиллов (сверено по архивной копии документации: VK удалил её после закрытия пользовательских скиллов 20.12.2024): `BigImage {type, image_id}`, `ItemsList {type, items: [{image_id}]}`, `image_id` — integer. Раньше отправлялась схема Алисы (заголовки, описания, кнопки, header/footer) и несуществующий у Маруси тип `ImageGallery` (теперь галерея уходит как ItemsList). Поля `title`/`description`/`button` в `IMarusiaImage` помечены `@deprecated`, `image_id` расширен до `number | string`.
- **Маруся**: `response.text` по протоколу не может быть пустым (в отличие от Алисы) — при пустом тексте он заполняется из `tts` без звуковой разметки и знаков ударения.
- **SmartApp (кнопки)**: `server_action` кнопки отправлялся гибридом `{action_id, payload}`, а в запросе SERVER_ACTION читался `parameters` — payload терялся, `userCommand` оставался пустым, нажатие уходило в fallback. Теперь используется документированная форма `{action_id, parameters}` (устаревшая `{type, payload}` читается для совместимости), имя действия — собственный `action_id` либо `command`/`action`/`value` из параметров.
- **SmartApp**: `payload.items` — обязательное поле ANSWER_TO_USER — отсутствовало в ответе без текста и карточек; успешное сохранение во внешнее хранилище (ответ 200 с пустым телом) логировалось как ошибка на каждом запросе.
- **Голос в чатах (Telegram/VK/MAX)**: вопреки документации («при заданном `speech_kit_token` tts озвучивается») синтез запускался только при непустом `controller.sound.sounds` — одного `tts` было недостаточно. Теперь достаточно `tts` + `speech_kit_token`; из текста для синтеза вычищается разметка голосовых платформ (`#game_win#`, `<speaker>`, паузы), которую SpeechKit иначе зачитывал вслух.
- **Telegram (TTS)**: синтезированный OGG/Opus отправлялся через `sendAudio`, который по Bot API принимает только MP3/M4A. Добавлен `TelegramRequest.sendVoice()`, TTS уходит голосовым сообщением.
- **MAX (`bot_started`)**: событие нажатия «Начать» помечалось `skipAutoReply` — приветствие не уходило никогда, даже из `bot.addEvent('start')`. Теперь на `bot_started` отвечает обычный конвейер (welcome / обработчик события), deep-link `payload` события попадает в `controller.payload`.
- **Алиса/Маруся**: предупреждение о нескольких хранилищах state выводилось на каждый запрос, хотя платформа присылает их одновременно штатно — теперь один раз на адаптер.
- **Ядро**: при `isLocalStorage: true` на платформах без локального хранилища (Telegram, VK, MAX, Viber) `userData` не читалась из подключённой БД, но писалась в неё — `oldIntentName` терялся между запросами, и все сценарии `addStep` уходили в fallback. Flow-редактор выставляет `isLocalStorage: true` по умолчанию, поэтому ломались сгенерированные чат-боты. Теперь, когда localStorage платформы недоступен, источник `userData` — БД, а без DB-адаптера — сессия в памяти процесса (см. «Добавлено»). Регресс-тест в `tests/Bot/regressions.test.ts`.
- **Ядро**: async-шаг (`addStep`), вернувший `false`, считался выполненным — конвейер команд не продолжался, пользователь получал пустой ответ. Теперь `Promise<false>` обрабатывается как синхронный `false` (тип `IStepParam.cb` расширен: `void | false | Promise<void | false>`).
- **Ядро**: синхронное исключение из `action()` и из `customCommandResolver` пробрасывалось до webhook-обработчика и давало HTTP 500 (Telegram повторяет апдейт, VK отключает сервер), тогда как async-ошибки тех же колбэков гасились. Теперь ошибка логируется: `action()` → текст «Не удалось выполнить команду», резолвер → команда считается не найденной (intent → fallback).
- **SpeechKit (TTS в чатах)**: запрос не соответствовал контракту Yandex Cloud и отклонялся всегда — тело уходило JSON (API принимает только `application/x-www-form-urlencoded`), авторизация — схемой `OAuth` (SpeechKit понимает только `Api-Key` и `Bearer`). Тело теперь сериализуется формой; токен `t1.…` уходит как `Bearer` (IAM), остальные — как `Api-Key`; явно заданная схема передаётся как есть. У API Диалогов (загрузка медиа Алисы) схема `OAuth` сохранена.
- **VK**: фото загружались в multipart-поле `file`, а сервер загрузки фото принимает только `photo` — картинки карточек/карусели и `controller.api.sendPhoto` из локальных файлов не отправлялись. `VkRequest.upload()` получил необязательный параметр `fieldName` (`'file'` по умолчанию, обратная совместимость).
- **VK**: `docs.save` возвращает обёртку `{type, doc | audio_message}`, а идентификаторы читались с верхнего уровня — в attachment уходило `docundefined_undefined` (голосовые/TTS и `controller.api.sendDocument`). Добавлен разбор вложенного объекта (`VkSound.getVkDocAttachment`).
- **MAX**: ответ сервера загрузки для `audio`/`video` — `retval` (не JSON), а `MaxRequest.upload()` разбирал его как JSON и падал — аудио и TTS не отправлялись. Ответ читается текстом, токен берётся из `token`/`photos.<id>.token` или с шага `POST /uploads`. При отсутствии токена `upload()` возвращает `null`: раньше одноразовый upload-URL кэшировался как токен картинки/звука и уходил битым `payload.url`.
- **MAX**: link-кнопка с payload получала недокументированное поле `payload` (оно есть только у callback); `controller.api.answerCallback()` в личном диалоге не передавал ключ диалога — очередь «2 callback-ответа/сек» не включалась.
- **VK/MAX (TTS)**: токен каждой озвучки кэшировался в `SoundTokens` по уникальному пути временного файла — таблица (и кэш FileAdapter в памяти) росла на каждый TTS-ответ без повторного использования. TTS загружается без кэширования.
- **Viber**: приветствие на `conversation_started` отправлялось REST `send_message`, который Viber отклоняет для неподписанного пользователя — welcome никогда не доходил. Теперь оно возвращается JSON-телом webhook-ответа (единственный документированный канал; новый `ViberRequest.buildWelcomeMessage()`). Карточки и звуки в приветствие не попадают (warn).
- **Telegram**: inline-ответ брал только `text` — при заполненном лишь `tts` уходил пустой результат (как в обычном ответе, используется `getChatText`).
- **Card**: `clear()` не сбрасывал кнопки карточки — они протекали в следующий запрос переиспользуемого контроллера (`BotTest`, `simulate`).
- **VK**: элемент карусели без кнопки в не-gallery ветке выбрасывался молча — добавлено предупреждение, как в gallery-ветке.
- **VK**: карусель с разным числом кнопок у элементов VK отклонял целиком (документация требует одинаковую структуру всех элементов). Число кнопок выравнивается до минимального среди элементов, с предупреждением.
- **VK**: сообщение с каруселью без текста VK отклоняет (`message` обязателен). Если текста ответа нет, в `message` подставляется заголовок карточки (`card.title`), затем заголовок первого элемента; если нет и их — предупреждение.
- **Маруся**: картинки загружались в multipart-поле `file`; сервер загрузки (та же инфраструктура, что у фото VK, ответ `{server, photo, hash}`) принимает поле `photo`.
- **MAX**: поля, специфичные для типа кнопки, уходили в кнопку любого типа. По схеме официального SDK `@maxhub/max-bot-api`: `quick` — только у `request_geo_location`, `contact_id` — только у `open_app`. `intent` в текущей документации и SDK отсутствует (наследие TamTam) и передаётся только `callback`-кнопке.
- **Preload**: для Алисы загрузка и удаление ресурсов не работали вовсе — API ресурсов Алисы адресуется по навыку, а skill_id брался из запроса, которого при предзагрузке нет. Добавлена опция `alisaSkillId` в `IOptions` (для `loadImages`/`loadSounds`, а также новым необязательным третьим аргументом `removeImages`/`removeSounds`); без неё Алиса пропускается с предупреждением.
- **Preload**: `removeImages()`/`removeSounds()` никогда не удаляли — поиск шёл через `where()` (не заполняет модель) по несуществующему полю `type`, а одна модель делилась между параллельными промисами. Теперь `whereOne({path, platform})` и своя модель на операцию.
- **CLI (create)**: без `isEnv` плоские `params.*_token` и `config.db.pass` сериализовались plaintext в коммит-файлы `Params.ts`/`Config.ts` (а `config.tokens` при этом вычищались). Теперь они удаляются с предупреждением (фреймворк 3.x плоские `*_token` не читает; `DB_PASSWORD` из окружения приоритетнее `db.pass`).
- **CLI (from-flow)**: сгенерированный `tsconfig.json` не задавал `types: ["node"]` — с TypeScript 6.0 (`types` по умолчанию пуст) проект с `http_request` не собирался (`TS2339: 'unref' does not exist on type 'number'`). Хелпер тайпчека в тестах больше не подставляет `types` сам и проверяет ровно пользовательский tsconfig.
- **Ядро**: устранено нарушение инварианта «core не зависит от plugins» — `Bot.run()` подключал фасад `controller.api` ленивым `require('../plugins/platforms/Base/apiFacade')` (хак в обход архитектуры), а сам диспетчер держал жёсткий `switch` по `appType` с зашитыми ветками встроенных платформ. Для любой кастомной платформы `controller.api` навсегда оставался `null` — фасад было невозможно ни получить, ни переопределить. Фасад теперь выбирается адаптером через новый метод `createApi(controller)` контракта `IPlatformAdapter` (см. «Добавлено»); из ядра удалены require-хак, кэш фабрики и знание о конкретных платформах. Поведение встроенных платформ не изменилось; для кастомных `null` сменяется на рабочий фасад, если адаптер переопределил `createApi`. Регресс-тесты в `tests/Platforms/apiFacade.test.ts` (в т.ч. полный цикл `BotTest.simulate` с кастомной платформой).
- **Bot**: warn при старте в режиме `dev` заявлял «проверка регулярных выражений на ReDoS отключена» — на деле проверка в dev выполняется всегда: опасные выражения логируются, но не отклоняются (отклонение — только `strict_prod`). Текст приведён к реальному поведению.
- **Viber**: payload-кнопки не нормализовались — Viber оставался единственной кнопочной платформой без `normalizeActionPayload` (Telegram/VK/MAX её применяют): кнопка с объектным payload `{"command":"buy"}` приходит текстовым сообщением с JSON-строкой, `userCommand` оставался сырой строкой, и `addAction`/`addCommand` не срабатывали, хотя на остальных платформах работали. Payload нормализуется в имя действия (`'buy'` или `'{"command":"buy"}'` → `'buy'`); поведение обычного текста не изменилось (lower + trim, как раньше). Регресс-тесты в `tests/Platforms/Viber/adapter.test.ts`.
- **CLI (create)**: повторная генерация с `--force` перезаписывала существующий `.env` значениями-черновиками из JSON-конфига — реальные токены, вписанные пользователем вручную, молча терялись (from-flow ведёт себя иначе: append-only). Create-ветка приведена к тому же контракту: существующий `.env` не перезаписывается, дописываются только отсутствующие переменные, при дописывании — предупреждение. Регресс-тест в `tests/cli/consoleController.test.ts`.
- **CLI (from-flow)**: санитизация Mongo-кредов (`DB_USER`/`DB_PASSWORD`) вырезает кавычки и управляющие символы из значения, попадающего в `.env`, — пароль `p"ss` превращался в `pss`, и подключение падало с «Authentication failed» без видимой причины (в `.env` лежал «правильный на вид» пароль). Искажение значения теперь сопровождается явным предупреждением с именем переменной и подсказкой. Регресс-тест в `tests/cli/flowGenerator.test.ts`.
- **CLI (from-flow)**: генератор писал `.env` (токены, `DB_USER`/`DB_PASSWORD`), но генерируемый `bot.setAppConfig` не содержал `env: './.env'` — ядро в тихом режиме читало только `process.env`, файл игнорировал, и записанные генератором токены/креды не действовали при обычном `npm start` (несимметрично с create-веткой). Генерат получает `env: './.env'`, когда генератор реально пишет `.env` (токены или Mongo-креды). Регресс-тест в `tests/cli/flowGenerator.test.ts`.
- **CLI (deploy.js)**: значения `.env` с запятой разрезали `--environment` на две битые переменные (yc CLI делит пары по запятым) — такая переменная теперь пропускается с явным warn вместо молчаливой порчи секрета.
- **CLI (create)**: объект вместо строки в `config.tokens.{platform}.token` записывался в `.env` как `[object Object]` — теперь переменная пропускается с предупреждением.
- **ReDoS**: `isRegexLikelySafe` — закрыто ложное срабатывание на ограниченные интервалы: `(\d{1,3}\.){3}\d{1,3}` (IP-адрес) и `(a{1,3}){3}` проходят; `(a+){10,100}`, `(a{1,}){3}`, `(a*){2}` и «интервал под неограниченным квантификатором» (`(?:a{2,3})+`) блокируются. Регресс-тесты в `tests/Core/regexSafety.test.ts`.
- **Env**: смена `appConfig.env` с одного файла на другой не перечитывала файл — кэш первого пути удерживался («победа первого значения»). Кэш теперь привязан к пути: смена пути перечитывает файл. Регресс-тест в `tests/Bot/env-process.test.ts`.
- **Telegram**: `cb.from?.id as number` / `iq.from?.id as number` / `cb.message?.message_id as number` — при отсутствии полей в `userId`/`messageId` молча попадал `undefined`; теперь честный `?? null` без кастов.
- **Платформы (стабильность)**: исключения слоя БД/API из асинхронных `cardProcessing` (Алиса, Маруся, VK) и `soundProcessing` (Telegram) роняли весь ответ в 500 — серия 5xx у VK отключает вебхук, Telegram реплеит апдейт. Теперь ответ деградирует без карточки/звука, ошибка логируется (по образцу одиночной ветки Telegram Card).
- **VK**: gallery-элемент карусели без валидной кнопки уходил в template без `action`/`buttons` (не-gallery ветка такие элементы отбрасывает) — VK отклонял всю карусель. Элемент пропускается с warn, симметрично не-gallery ветке.
- **Маруся**: `session.user_id` опционален в протоколе — `undefined + ''` давал литеральную строку `"undefined"`, попадавшую в ключ БД и в `session.user_id` ответа. Теперь `?? ''`. Регресс-тесты в `tests/Platforms/Marusia/adapter.test.ts`.
- **Viber**: заголовок и описание карточки rich_media склеивались в одну строку — между ними добавлен `<br>` (как в примере IViberCard). Платформенная опция `Text: ''` больше не может обойти фильтр пустой подписи кнопки.
- **Telegram**: кнопки теперь подставляют `style` из `options.style` (`TG_STYLE_PRIMARY`/`TG_STYLE_SUCCESS`/`TG_STYLE_DANGER` — ровно значения Bot API 9.4+ `primary`/`success`/`danger`; неизвестный стиль пропускается с warn, чтобы Telegram не отклонил всё сообщение) — константы были экспортированы, но поле не проставлялось (фича существовала только в типах).
- **Кнопки (все платформы)**: молчаливое усечение списка кнопок сверх лимита (`getCorrectButtons`) теперь сопровождается warn — раньше «лишние» кнопки исчезали без объяснения; warn о невалидной кнопке в карточках Алисы/Маруси/Viber больше не теряется (appContext передаётся в обработку кнопок карточек).
- **VK (типы)**: `IVkRequestObject.peer_id` стал опциональным — поле реально присутствует только в `message_event`; обязательность ломала типизированный разбор `message_new` (в нём peer_id лежит в `object.message.peer_id`).
- **[Критично] Viber**: `buttonProcessing` обращался к `button.options.request_contact`/`request_location` без защиты — Viber был единственным адаптером без `?.`/`?? {}` (Telegram/VK/Max защищены), и кнопка, собранная вручную вне компонента `Buttons` (JS-потребители, миграции), роняла весь `getContent()` с TypeError → webhook отвечал 5xx → Viber после серии 5xx отключал вебхук. Доступ — через `?.`, платформенные опции — через `?? {}`. Регресс-тесты в `tests/Platforms/Viber/buttonOptions.test.ts`.
- **[Критично] FileAdapter**: потеря последних записей при graceful shutdown — `close()` форсил запись только при живых debounce-таймерах: если таймер уже отработал, а асинхронная запись (tmp+rename) была в полёте, `process.exit()` через 500 мс дренажа убивал процесс до её завершения. Все записи регистрируются как in-flight промисы (`#inFlightSaves`), `close()`/`destroy()` дожидаются их и сохраняют данные таблиц независимо от состояния таймеров; повторный `close()` (после сброса кэша) не перезаписывает файл пустым объектом. Плюс: `destroy()` собирает таблицы из обоих хранилищ кэша (раньше — только из `databaseInfo` адаптера). Регресс-тесты в `tests/DbModel/fileAdapterShutdown.test.ts`.
- **Безопасность (ReDoS)**: байпас эвристики `isRegexLikelySafe` — квантифицированный backreference вне тела группы (`(\w+)z\1{2,}`, `([a-z]+)x\1+`) не ловился ни одной из шести проверок: квадратичный бэктрекинг на 7000-символьном входе блокировал event loop на секунды при advisory-режиме фильтра (strictMode=false по умолчанию). Квантифицированный backreference теперь признаётся опасным независимо от соседних квантификаторов; неквантифицированный (`(\w+)-\1`) и с фиксированным интервалом (`\1{2}`) по-прежнему легитимны. Регресс-тесты в `tests/Core/regexSafety.test.ts`.
- **Viber**: `ViberRequest` — единственный API-клиент без явного таймаута — работал с дефолтом `Request` (2000 мс): при латентности chatapi.viber.com выше 2 с `send_message`/`rich_media` молча обрывались по `AbortSignal.timeout`, тогда как соседние адаптеры (VK/Telegram/MAX — 5500 мс) продолжали работать. Установлен `maxTimeQuery = 5500`. Регресс-тест в `tests/Request/ViberRequest.test.ts`.
- **Request**: сброс `send()` выполнял контракт переиспользования инстанса частично — `header`, `isConvertJson`, `isBinaryResponse` оставались от прошлого вызова: заголовок одного API (например, `Authorization` MAX) мог уйти в запрос другого, а бинарный режим SpeechKit — в JSON-запрос. Все встроенные клиенты (VK/MAX/Viber/Yandex) выставляют их перед каждым `send()` (проверено), поэтому сброс безопасен и закрывает контракт; `maxTimeQuery` намеренно не сбрасывается — это настройка клиента. Плюс: исключение fetch нормализуется (`e instanceof Error ? e : String(e)`) — строковые rejection'и больше не маскируются под Error с `undefined`-message в логах. Регресс-тесты в `tests/Request/sendReset.test.ts`.
- **FileAdapter**: детекция многопроцессного доступа — два воркера (PM2 cluster) на одной директории `json/` молча теряли записи друг друга (mtime-кэш не видит чужих изменений, полная перезапись стирает их). При первом чтении таблицы файл с mtime новее запуска адаптера (за вычетом 5-секундного grace-порога на гранулярность ФС и собственную запись при инициализации) даёт предупреждение о переходе на MongoAdapter. Регресс-тесты в `tests/DbModel/fileAdapterShutdown.test.ts`.
- **Navigation**: `selectedElement` трактовал первую цифру в любом месте текста как выбор элемента — «закажи 2 литра» на странице выбора возвращало 2-й элемент вместо поиска по схожести. Число выбирает элемент только как последнее слово текста («2», «выбери 1», «вариант 3») — контракт прежних сценариев сохранён. Регресс-тест в `tests/Navigation/navigation.test.ts`.
- **Card**: `clear()` сбрасывал только изображения — при переиспользовании контроллера (`BotTest`, `_setBotController`) заголовок, описание и `template` протекали из прошлого запроса в новый ответ. Сбрасываются `title`, `desc`, `template`. Заодно warn про обход адаптеров через `template` выводится один раз на инстанс Card (депонирован) — стабильное использование больше не замусоривает логи. Регресс-тесты в `tests/Card/card.test.ts`.
- **Viber**: у карточки rich_media с несколькими кнопками молча использовалась первая — ячейка вмещает одну кнопку, но отбрасывание остальных не сопровождалось предупреждением, и разработчик не понимал, почему «половина кнопок пропала». Добавлен warn с количеством. Регресс-тест в `tests/Platforms/Viber/card.test.ts`.
- **MongoAdapter**: ошибки не-Error типа в кастомных `_query`-колбэках логировались как `[object Object]` (двойной каст `err as string, err as Error`) — реальный текст ошибки не читался без раскопок meta. Нормализация: `instanceof Error ? message : String(err)`. Регресс-тесты в `tests/DbModel/mongoSanitize.test.ts`.
- **CLI (from-flow)**: user/pass MongoDB из flow.json молча терялись — генератор передавал в `MongoAdapter` только host/database, а креды в `.env` не мигрировали (несимметрично с create-веткой, где сценарий решён): подключение падало без объяснения. Креды переносятся в `.env` (`DB_USER`/`DB_PASSWORD`) с вычисткой CRLF/кавычек; запись токенов больше не затирает их (дополнение вместо перезаписи). Регресс-тесты в `tests/cli/flowGenerator.test.ts`.
- **CLI**: JSON-конфиг без поля `name` падал внутри CreateController с невнятным `Cannot read properties of undefined` — теперь понятная ошибка «отсутствует поле name» рядом с парсингом (как в flowGenerator для flow.json).
- **CLI (тесты)**: правила санитизации `.env` для `deploy.js` (`sanitizeEnvValue`/`quoteArg` против shell-инъекций cmd.exe: CRLF, управляющие символы, кавычки, `%`→`%%`) зафиксированы тестами: контракт-зеркало правил + дословная проверка цепочки в генерате (раньше логика квотинга не покрывалась тестами на враждебные значения). Новый сьют `tests/cli/deploySanitize.test.ts` использует собственную директорию вывода — общая с flowGenerator.test.ts директория ломала параллельный прогон (rmSync в beforeEach удалял файлы соседнего воркера).
- **Бенчмарки (стенды)**: два исправления честности compare-стендов. (1) Метки «лучший/паритет» (`markRow`) помечали победой любого единственного лидера без проверки величины отрыва — теперь победа требует отрыва, пробивающего оба порога стенда (≥5% и ≥0.3 мкс); логика вынесена в тестируемый модуль `benchmark/comparison/markRow.js` с юнит-тестами. (2) Конкурентам передавались строковые слоты как есть, хотя их `hears(строка)`/`hear(строка)` матчит только точное совпадение — обёртка `asRegExp` (экранирование в unanchored RegExp) применяется во всех пяти стендах, чтобы «частичное совпадение» измерялось одинаково у всех участников. Подробности — в BENCHMARKS.md.
- **Env**: переменная окружения `SMARTAPP_TOKEN` объявлена в `IEnvConfig` и генерируется CLI в `.env`, но `AppContext` её не читал — значение молча отбрасывалось и не попадало в `tokens.smart_app.token`. Теперь переменная подхватывается из `.env`-файла и `process.env` по общим правилам (тихий подхват не перезаписывает явно заданные токены). Регресс-тесты в `tests/Bot/env-process.test.ts` и `tests/Bot/bot.test.ts` (env init).
- **VK**: опечатка в публичном типе — поля голосовых сообщений назывались `waleform` вместо `waveform` (`IVkAudioMessageInfo`, `IVkDocSave`), так что типизированный код пользователей не компилировался с реальным ответом `docs.save`. Поля переименованы в `waveform` (см. «Миграция с 3.0.x»).
- **VK**: поле `message` в `IVkRequestObject` было обязательным, хотя события без текста (в частности, `message_event` — callback-кнопка) реально приходят без него: типизированный разбор `requestObject` на callback-запросах не компилировался. Поле стало опциональным (`message?: IVkMessage`; см. «Миграция с 3.0.x»).
- **Viber**: координаты `location.lat`/`location.lon` в публичном типе `IViberParams` были объявлены строками, хотя Viber передаёт их числами (см. `LocationMessage` официального SDK viber-bot-node) — типизированная отправка геолокации не компилировалась. Тип исправлен на `number` (см. «Миграция с 3.0.x»).
- **[Критично] NLU**: `Nlu.getLink()` обрезал каждую ссылку на первой точке — в `LINK_REGEX` точка была исключена из тела совпадения, поэтому `https://example.com/page.html` превращался в `https://example`. Шаблон исправлен: точки внутри URL сохраняются, конечная пунктуация по-прежнему отбрасывается; ReDoS-безопасность проверена. Регресс-тесты в `tests/Nlu/nlu.test.ts`.
- **[Критично] Env**: `setPlatformParams` всегда перезаписывал токены из окружения (`overwrite=true`), в том числе в тихом режиме без `appConfig.env` — токен, явно переданный в конструкторе адаптера, затирался пустым значением из `process.env`. Теперь тихий подхват не трогает явно заданные токены; явный `env` сохраняет прежнее поведение с перезаписью. Там же: кэш прочитанного `.env` не сбрасывался, из-за чего явно переданный путь к env-файлу игнорировался после тихого подхвата. Регресс-тесты в `tests/Bot/env-process.test.ts`.
- **VK**: `message_event` без `payload` возвращал `false`, и VK Callback API отключал сервер после серии 5xx. Событие подтверждается статусом 200 через `skipAutoReply`. Регресс-тест в `tests/Platforms/VK/adapter.test.ts`.
- **Telegram**: fallback на `sendPhoto` для одиночного изображения после фильтрации отправлял поле `photo` с префиксом `attach://` — префикс валиден только в FormData `sendMediaGroup`, и изображение не отправлялось. Префикс снимается. Регресс-тест в `tests/Platforms/Telegram/card.test.ts`.
- **Telegram**: при `buttons.remove()` с пустым текстом клавиатура фактически не снималась (Telegram требует текст в сообщении с `remove_keyboard`), а в лог уходило только общее предупреждение о пустом ответе. Предупреждение теперь явно указывает, что для снятия клавиатуры ответ должен содержать текст. Регресс-тест в `tests/Platforms/Telegram/adapter.test.ts`.
- **Viber**: в Rich Media карточке без кнопок текст `title`/`description` терялся — элемент отправлялся без поля `Text`. Текст заполняется соответствующими шрифтами; при отсутствии заголовка и описания пустой `Text` не добавляется. Регресс-тесты в `tests/Platforms/Viber/card.test.ts`.
- **Viber**: событие `unsubscribed` пыталось отправить автоответ пользователю, который уже отписался. Событие подтверждается через `skipAutoReply` без отправки сообщения. Регресс-тест в `tests/Platforms/serviceEvents.test.ts`.
- **SmartApp**: определение SSML давало ложное срабатывание на тексте вида «x < y» — ответ помечался `application/ssml`, и парсер Сбера ломался на обычном тексте. Шаблон распознаёт только реальные теги. Регресс-тест в `tests/Platforms/SmartApp/adapter.test.ts`.
- **Маруся**: `resizeMarusiaTts` обрезал TTS-текст по символам без учёта тегов `<speaker>`/`sil` — при лимите тег разрывался посередине и разметка ломалась. Обрезка выполняется по видимому тексту (лимит 1024 символа), теги не учитываются в лимите и сохраняются целиком — по аналогии с Алисой. Регресс-тесты в `tests/Platforms/Marusia/adapter.test.ts`.
- **Вебхук**: тело, которое разбиралось как валидный JSON, но не являлось объектом (строка, число, массив), обрабатывалось как обычный запрос и давало неинформативную ошибку — теперь возвращается `400` с сообщением «Тело запроса не является JSON-объектом». Невалидный JSON в `webhookEvent` возвращает `400` вместо `422` — статусы унифицированы под политики повторов платформ. Регресс-тесты в `tests/Bot/bot.test.ts`.
- **CLI**: условие «содержит» в flow генерировало выражение `x.includes(y)` для переменных произвольного типа — сгенерированный проект не компилировался, если переменная была числом. Выражение оборачивается в `String(x).includes(String(y))`. Регресс-тест в `tests/cli/flowGenerator.test.ts`.
- **CLI**: Step-блок с `saveAs: 'original'` сохранял `ctrl.userCommand`, который все адаптеры платформ приводят к нижнему регистру для матчинга слотов — «оригинальный» ввод искажался («Иван» → «иван») во всех ботах со Step-блоками, созданных `create from-flow`. Теперь генерируется `ctrl.originalUserCommand ?? ctrl.userCommand ?? ''` — тот же контракт, что у встроенных форм (`addForm`). Режим `saveAs: 'lowercase'` не изменился. Регресс-тест в `tests/cli/flowGenerator.test.ts`.
- **CLI**: в шаблоне quiz-проекта (`quizParams.js`) не были заданы интенты `welcome`/`help` — реплики «начать» и «помощь» попадали в fallback. Интенты с типовыми слотами добавлены в шаблон. Регресс-тест в `tests/cli/createController.test.ts`.
- **[Критично] Telegram/движок**: повторная отправка файлов по `file_id` больше не трактует токен как локальный путь; `customCommandResolver` вызывает `action()` и при результате `null` продолжает цепочку intent/fallback.
- **[Критично] CLI/Cloud**: исправлен порядок аргументов `addCommand(..., cb, isPattern)` в проектах из flow; cloud-деплой собирает отдельный архив без `.env`/исходников и передаёт переменные через поддерживаемый `yc --environment`.
- **Платформы**: усилена валидация VK-кнопок и вложений, TTS Алисы обрезается без учёта служебных тегов, Viber Rich Media требует API 7, а неподдерживаемые звуковые маркеры SmartApp удаляются из TTS.
- **CLI**: flow валидируется до генерации, зарезервированные ключи прототипа отклоняются, Mongo-проекты получают зависимость `mongodb`; Docker и GitHub Actions используют выбранный порт и Node.js 20, установка работает без lock-файла.
- **Надёжность**: загрузкам Telegram/Yandex назначены ограниченные увеличенные таймауты; `AppContext.close()` дожидается логов, циклические данные дают rejected promise, отсутствующие числовые поля Mongo не превращаются в `NaN`.
- **[Критично] Логирование**: при сбое записи файла лога `logError` входил в бесконечный цикл самовоспроизводящихся ошибок: каждая неудачная запись порождала новую запись об ошибке, которая снова уходила на запись, а текст разрастался лавинообразно (в демо — 61 МБ вывода за 8 секунд при одной исходной ошибке). Путь обработки сбоя вынесен из конвейера логирования: после 3 неудачных попыток подряд запись файлов приостанавливается на 60 секунд (записи за время паузы отбрасываются), в stderr уходит одно сообщение, после паузы запись возобновляется автоматически. Поведение `saveFileData` не изменилось — ошибки записи данных по-прежнему логируются.
- **Request**: при нечитаемом или несуществующем attach-файле всё равно выполнялся `fetch(url, undefined)` — паразитный GET-запрос к API платформы без тела и метода, затиравший локальную причину отказа (например, при передаче `file_id` в `MaxRequest.upload`). Запрос не отправляется, ошибка attach возвращается вызывающему коду. Регресс-тесты в `tests/api/request/attach.test.ts`.
- **Middleware**: rateLimiter допускал до ~2× лимита платформы за окно: задачи из очереди исполнялись с безусловным инкрементом счётчика, и параллельно «свежие» запросы проходили проверку `count < limit`. Пачка теперь уважает счётчик. Регресс-тест в `tests/Middleware/rateLimiter.test.ts`.
- **Middleware**: вытеснение записи rateLimiter при переполнении карты состояния отклоняло промисы очереди, но работающий `processQueue` продолжал исполнять их задачи; `destroyRateLimiter` — то же для уже взятой в работу пачки. Записи помечаются `dead` и больше не обслуживаются.
- **Middleware**: переполнение очереди rateLimiter теперь выбрасывает экспортируемый класс `RateLimitQueueOverflowError` и выставляет `ctx.platformOptions.rateLimitOverflow = true` — раньше исключение перехватывалось ядром как «middleware прервал обработку», и перегрузку было невозможно отличить от ошибки в бизнес-логике.
- **Middleware**: `ipFilter` поддерживает IPv6-адреса и CIDR (включая `::`-сжатие, IPv4-хвост и `::ffff:`-mapped в hex-форме). Раньше настоящий IPv6 не сравнивался ни с одним правилом: в whitelist-режиме все IPv6-клиенты блокировались (fail-closed), в blacklist-режиме — никогда не блокировались (fail-open). Правила по-прежнему сравниваются по версии адреса; для фильтрации IPv6 добавляйте IPv6-правила. Регресс-тесты в `tests/Middleware/middleware.test.ts`.
- **Env**: значения `.env` с `#` без пробела перед ним больше не обрезаются: `DB_PASSWORD=pass#word` — это значение, а не комментарий (конвенция dotenv). Инлайн-комментарий по-прежнему отсекается, когда `#` начинается с пробела. Регресс-тесты в `tests/Utils/envConfig.test.ts`.
- **NLU**: `isSayTrue`/`isSayFalse` не распознавали ответы с пунктуацией — «Да!», «Нет, спасибо» не считались подтверждением/отрицанием и ломали сценарии `isIntentConfirm`/`isIntentReject`. Границы слова задаются lookaround'ами: пунктуация после ключевого слова допускается. Регресс-тесты в `tests/Text/text.test.ts`.
- **Telegram**: url-кнопка отбрасывалась, если у кнопки задан payload больше 64 байт — лимит `callback_data` проверялся до ветки url, хотя url-кнопки не используют `callback_data`. Проверка применяется только к callback-кнопкам. Регресс-тесты в `tests/Platforms/Telegram/button.test.ts`.
- **VK**: `hash` у vkpay-кнопки отправлялся на верхнем уровне объекта кнопки, а по документации VK API он находится внутри `action`; при отсутствии hash в payload в клавиатуру уходило `hash: null`, и VK отклонял всё сообщение ошибкой 100. Регресс-тесты в `tests/Platforms/VK/button.test.ts`.
- **VK/MAX**: загрузка файлов на сервера платформ обрывалась стандартным таймаутом 5.5 с — на медленном восходящем канале карточки и звуки молча терялись. Для upload-операций таймаут поднят до 30 с (как у Telegram).
- **Платформы**: malformed-запросы без обязательных полей не вызывают TypeError и 500: `initUserCommand` защищает отсутствие `original_utterance` (Алиса/Маруся), Telegram-адаптер — отсутствие `chat` в апдейте (такие апдейты пропускаются с `skipAutoReply`). Доступ к `button.options` в адаптерах VK, MAX и Viber защищён опциональной цепочкой на случай кнопок, собранных вне компонента `Buttons`.
- **Наблюдаемость**: молчаливое усечение карточек теперь логируется — медиа-группа Telegram (>10 изображений, элементы без токена/пути) и карусель VK (более 10 элементов, сбой загрузки image_id).
- **Команды**: при превышении лимита активных групп строковый паттерн группы перекомпилировался в RegExp на каждом запросе. Компиляция кэшируется в `getGroupRegExpCompiled` (WeakMap по объекту группы с проверкой актуальности паттерна).
- **RegExp**: эвристика `isRegexLikelySafe` отбраковывала безопасные паттерны — любую квантифицированную группу `(abc)+` и любое `.*`. Проверка заменена точечным анализом квантифицированных групп: опасны группы с квантификатором, альтернативой или `.` внутри (`(a+)+`, `(a|aa)+`, `(?:\w+\.)+`); в `strictMode` больше не отключаются валидные команды. Регресс-тесты в `tests/Core/regexSafety.test.ts`.
- **CLI**: `npx umbot create 2025bot` генерировал несобираемый TypeScript (`import { 2025botController }`) — имя, начинающееся с цифры, получает префикс `_`. Регресс-тест в `tests/cli/createController.test.ts`.
- **requestId**: удалено write-only скрытое поле `appContext.__lastRequestId` — оно никем не читалось, а JSDoc обещала несуществующую интеграцию с логгером. Единственный контракт middleware — `platformOptions.requestId`.
- **Типы**: `getErrorMsg` и лог-хелперы API-клиентов принимают `Error | string` — `Request.send()` фактически возвращает объект Error в `err` (интерфейс `IRequestSend.err` уже это допускал).
- **CLI**: `fetchWithTimeout` в сгенерированных проектах использует `.unref()` у таймера прерывания; в `serverless.yml` добавлен комментарий, что деплой выполняется через `scripts/deploy.js`, который этот файл не читает.
- **Telegram**: в групповых чатах `userId` теперь берётся из `from.id` (человек), а не из `chat.id` (группа). Раньше один и тот же пользователь получал в группе две разные записи в БД: сообщения сохранялись под ID группы, а нажатия inline-кнопок (`callback_query`) — под ID человека. Ответ по-прежнему доставляется в исходный чат (он сохраняется в `requestData.chatId`). `edited_message` обрабатывается как обычное сообщение с тем же источником `userId`; NLU-данные отправителя (`thisUser`) берутся из `from`, а не из чата. На личных чатах поведение не меняется (`from.id == chat.id`).
- **[Критично] Чат-платформы**: если бизнес-логика заполнила только `ctx.tts` (типично для кода, писавшегося под голосовую платформу), Telegram, VK, Viber и MAX не отправляли **вообще ничего** — пользователь видел молчание. Теперь при пустом `text` в чат уходит `tts` с вырезанной звуковой разметкой (`<speaker>`, `sil`, `#pause_<[мс]>#`).
- **VK / Viber**: кнопка с пустой подписью отправлялась платформе (`label: ""` у ВК, `Text: ""` у Viber) — ВК отклоняет такую кнопку вместе со всей клавиатурой. Теперь она пропускается с предупреждением, как это уже делали Telegram, MAX и Алиса.
- **Telegram**: пустой список кнопок больше не приводит к отправке `remove_keyboard` — снять клавиатуру можно только явным `buttons.remove()`. Отфильтрованные невалидные кнопки клавиатуру не трогают.
- **[Критично] Платформы**: служебные события больше не приводят к ответу `500`. Раньше `setQueryData()` возвращал `false` и `run()` бросал исключение на событии `webhook` у Viber (из-за чего вебхук вообще не удавалось зарегистрировать), на событиях группы VK (`message_reply`, `group_join`, `message_allow` и др. — VK Callback API отключал сервер после серии неудач) и на апдейтах Telegram вроде `my_chat_member` (Telegram бесконечно повторял апдейт и блокировал очередь). Теперь такие события помечаются `skipAutoReply` и подтверждаются статусом 200. Добавлен `tests/Platforms/serviceEvents.test.ts`.
- **[Критично] Команды**: команда, зарегистрированная с глобальным регулярным выражением (`addCommand('x', [/привет/g], cb, true)`), срабатывала через раз — `test()` продвигал `lastIndex` у кэшированного между запросами объекта. Флаги `g`/`y` снимаются при компиляции (`getRegExp`, `getRegExpOrSelf`), а `Text` дополнительно сбрасывает `lastIndex` перед проверкой.
- **[Критично] Текст**: `Text.resize()` разрывал суррогатные пары — обрезка по границе эмодзи оставляла одинокий суррогат, невалидный в UTF-8, и платформы отбивали такое сообщение. Обрезка выполняется по границе символа.
- **[Критично] HTTP**: кастомный заголовок (`Authorization` у MAX, `X-Viber-Auth-Token` у Viber) полностью затирал `Content-Type: application/json`, и JSON-тело уходило без Content-Type. Заголовки теперь объединяются; для `FormData` Content-Type по-прежнему выставляет сам `fetch`.
- **[Критично] SmartApp**: ответ помечался `pronounceTextType: 'application/ssml'` даже для обычного текста (ядро копирует `text` в `tts` для голосовых платформ) — любой `&`, `<` или `>` в пользовательских данных ломал SSML-парсер Сбера. Тип разметки определяется по фактическому наличию тегов. Там же: `intent` больше не уходит как `null`, а запрос без `payload`/`uuid` отклоняется понятной ошибкой вместо `TypeError`.
- **Алиса/Маруся**: если для элемента карточки была задана картинка, но получить `image_id` не удалось, элемент по-прежнему показывается (карточка с одним текстом — рабочий сценарий), но в лог пишется предупреждение с путём до картинки — раньше она пропадала молча.
- **[Критично] MAX**: при пустой клавиатуре в сообщение добавлялось вложение `inline_keyboard` с `buttons: []`, которое API отклоняет; кнопка без текста уходила как `{"type":"message","text":null}`. Оба случая отсекаются.
- **VK**: `messagesSend` при одновременно заданных `keyboard` и `template` отправлял в теле запроса строку `template=undefined` (VK отвечает ошибкой 100) — поле теперь удаляется. `httpBuildQuery` дополнительно пропускает `undefined`/`null`, чтобы такие значения не сериализовались как строки.
- **Telegram**: `TelegramRequest.initToken()` не влиял на адрес запроса — URL всегда собирался из `appConfig`, что ломало мульти-ботовые сценарии. Теперь приоритет у явно заданного токена.
- **Telegram**: при одновременно заданных inline- и обычных кнопках последние молча пропадали (платформа принимает только один тип клавиатуры в сообщении). Добавлено предупреждение в лог с объяснением, что делать.
- **[Критично] BotTest**: `clearStoreData()` сбрасывал `skipAutoReply`, поэтому со второго хода консольного тестирования адаптеры уходили в реальные API платформ. Флаг переустанавливается на каждой итерации.
- **BotController**: `clearStoreData()` теперь очищает `platformOptions` и кэш разобранных сущностей `Nlu`. Раньше технические данные запроса (`requestData`, `sendInInit`, `session`, `error`) и сущности NLU протекали в следующий запрос — например, VK-подтверждение возвращалось в ответ на любое сообщение.
- **Ядро**: `#getPlatformContent` не дожидался ответа адаптера перед валидацией, из-за чего проверка на `null` применялась к промису и никогда не срабатывала для асинхронных адаптеров (все, кроме SmartApp), а локальное хранилище записывалось параллельно формированию ответа.
- **Ядро**: `setLocalStorage()` больше не вызывается с пустым состоянием — у SmartApp это уходило вторым лишним HTTP-запросом (причём методом GET) на каждый ответ.
- **Ядро**: `bot.close()` дожидается закрытия HTTP-сервера, как и обещано в документации метода.
- **HTTP**: в текст ошибки попадает тело ответа платформы (`description` у Telegram, `error_msg` у VK) — раньше в логах оставался только код статуса. `Request.send()` также сбрасывает `get` и `customRequest`, чтобы параметры одного вызова не утекали в следующий у переиспользуемого инстанса.
- **VK/MAX/Telegram**: приведены к общему виду поля контроллера — `originalUserCommand` заполняется для callback-кнопок VK и для сообщений Telegram без текста, `username` в NLU у MAX приходит как `null`, а не `undefined`.
- **[Критично] Ядро**: `Bot` читал сохранённые данные пользователя по `userToken`, а записывал по `userId` — у авторизованных пользователей (например, Алиса с `isAuthUser`) `userData` не находилась между запросами и фактически не персистилась. Чтение унифицировано на `userId` (ключ записи). Добавлен регресс-тест в `tests/Bot/bot.test.ts`.
- **[Критично] VK**: `users.get` всегда возвращает массив, но адаптер читал `first_name`/`last_name` как у объекта — имя пользователя VK в `ctx.nlu.getUserName()` всегда было `null`. `VkRequest.usersGet` теперь типизирован как `IVkUsersGet[] | null` (по аналогии с `photosSaveMessagesPhoto`), адаптер берёт первый элемент массива; числовой ID отправляется документированным параметром `user_ids` (ранее — недокументированный `user_id`). Добавлены регресс-тесты в `tests/Platforms/VK/adapter.test.ts` и `tests/Request/VkRequest.test.ts`.
- **[Критично] Маруся**: `MarusiaRequest` наследовал токен VK от родителя и при отсутствии токена Маруси отправлял его в запросы к API Маруси (утечка секрета на чужой endpoint + нерабочие загрузки). Теперь при отсутствии `tokens.marusia.token` токен сбрасывается в `null`. Добавлен регресс-тест в `tests/Request/MarusiaRequest.test.ts`.
- **Документация**: полный копирайт/SEO/ИИ-аудит публикуемой документации (гайды, README, JSDoc) с применением правок:
    - Терминология унифицирована: «Сбер Салют»/«Салют»/«Сбер Salute» → «Сбер SmartApp» (README, GUIDE, deployment, JSDoc `index.ts`/`plugins.ts`/`Preload.ts`/адаптеров); «хендлер» → «обработчик» (гайды + JSDoc ядра, включая текст предупреждения `bot.addEvent`); «MAX (ВК)»/«MAX (мессенджер VK)» → «MAX».
    - Таблицы `supportedEvents` в api-reference.md и platform-integration.md приведены к коду: Маруся поддерживает `auth` (событие привязки аккаунта, симметрично Алисе) — раньше в доках был только `message`.
    - H1 гайдов переформулированы под поисковую выдачу: getting-started (96 симв. → «Быстрый старт: первый навык или чат-бот на umbot»), «API Reference» → «Справочник API umbot», «Часто задаваемые вопросы» → «FAQ по фреймворку umbot» (+ вводный абзац для meta description), «JSON-формат конфигурации umbot» → «Формат flow.json визуального редактора umbot».
    - Числовые расхождения сведены к одному канону: таймауты в README теперь описаны через реальные пороги фреймворка (2000/2900 мс, ориентир ~3 с) вместо невыводимой «более 2.5 секунд»; «первый ответ > 1 сек / 3 сек» в чеклистах заменён на измеренные «200–1000 мс на файл»; `Preload.ts` — «2–3 секунды» → «~3 секунды» с порогами. Из README убрано непроверяемое «80%+ покрытие тестами».
    - Пример `photosSaveMessagesPhoto` в JSDoc `VkRequest` исправлен на массив (`photo[0].owner_id`/`photo[0].id`) — сигнатура возвращает `IVkPhotosSave[]`; в `IVkApi` задокументирована семантика конверта `{response: ...}`.
    - Все 16 относительных `.md`-ссылок между гайдами переписаны в абсолютные URL сайта (инвариант пайплайна: относительные ссылки битые после публикации).
    - JSDoc-заголовки namespace-страниц (`export * as X` в `plugins/index.ts` + `SoundConstants`): 26 страниц API-справочника получили реальные первые предложения вместо шаблонного «Документация по X в фреймворке umbot» (meta description сайта); первые строки `voicePlatforms`/`botPlatforms` разведены (устранён тройной дубль description).
    - Мелкие правки: «долгой ответ» → «долгий» (GUIDE), «У Алисы/Марусии» → «Маруси», «и тд» → «и т.д.» и пропущенные запятые (build.ts), H1 GUIDE без бэктиков, TG_STYLE-опции кнопок Telegram задокументированы в GUIDE (Bot API 9.4+, CHANGELOG-фича ранее не попадала в гайды).
- **Документация**: `action()` вызывается фреймворком синхронно и не ожидается — примеры с `async action()` в JSDoc `BotController`, `UsersData` и `GUIDE.md` переписаны (асинхронная логика показана через `addCommand`/fire-and-forget), в JSDoc `action()` добавлено явное предупреждение.
- **Документация**: скрипт проверки и актуализации ссылок (`scripts/fix-doc.js`, `npm run doc:fix`) помечал корректные markdown-ссылки с якорем (вида `./GUIDE.md#раздел`) как битые — фрагмент `#...` не отсекался перед проверкой существования файла. Дополнительно якорь больше не теряется при переписывании ссылки на URL. Добавлены регресс-тесты в `tests/scripts/fix-doc.test.ts`.
- **[Критично] Ядро**: устранена циклическая зависимость `core ↔ controller`, из-за которой при стандартном импорте `from 'umbot'` константы `FALLBACK_COMMAND`, `WELCOME_INTENT_NAME`, `HELP_INTENT_NAME` захватывались как `undefined`. В результате fallback-команда `'*'`, `welcome_text` и `help_text` молча не работали. Константы вынесены в листовой модуль `core/constants.ts` без импортов, который всегда полностью инициализирован независимо от порядка загрузки. Добавлен регресс-тест `tests/Bot/fallbackConstants.test.ts`.
- **Middleware**: `requestId()` теперь корректно возвращает промис цепочки (`await next()`) — ранее middleware обрывал ожидание, из-за чего запрос мог обрабатываться параллельно с ответом, а ошибки в последующих middleware приводили к unhandled promise rejection.
- **Middleware**: при блокировке запроса middleware (`authGuard`, `maintenance`, `ipFilter`) ответ формируется через адаптер платформы из `ctx.text` — пользователь получает `deniedText` валидным ответом платформы вместо служебной строки.
- **Middleware**: `ipFilter` получает реальный IP клиента из сокета HTTP-запроса (`platformOptions.clientIp`, заполняется в `webhookHandle`) — ранее IP искался в теле запроса платформы и фильтрация не работала.
- **Env**: `ALISA_TOKEN` и `VK_SECRET_KEY` теперь реально читаются из окружения в `AppContext` (ранее были только объявлены в `IEnvConfig`).
- **CLI**: токен Алисы (`alisa_token`) больше не попадает в сгенерированный `src/config/{{name}}Params.ts` — удаляется вместе с остальными токенами перед сериализацией.
- **CLI**: `validate` проверяет целостность рёбер `edges` (существование `from`/`to`, допустимый `type`), по которым реально ходит генератор; циклы в графе допустимы.
- **CLI**: при генерации с `--prod` теперь создаётся `.dockerignore` (исключает `.env`, `node_modules`, логи из образа), а `Dockerfile` использует `npm ci` при наличии lock-файла — версии зависимостей совпадают между build и runtime стадиями.
- **CLI**: сгенерированный `deploy.yml` больше не передаёт секреты флагами `-e` (они видны в `ps` и истории docker) — используется `--env-file`; имя образа и контейнера подставляется из имени проекта; добавлены `npm ci`, шаг с тестами и `--restart unless-stopped`.
- **CLI**: генерация `.env` больше не пишет строки вида `TELEGRAM_TOKEN=undefined` — в файл попадают только переменные, для которых реально передано значение.
- **CLI**: `flowGenerator` для `database.type === 'mongo'` учитывает `config.host` из flow.json (ранее всегда подставлялся `localhost`); `bot.start()` берёт опциональные `hostname`/`port` из документа вместо захардкоженных `localhost:3000`.
- **CLI**: в шаблоне `package.json` команда `deploy` использует `pm2 startOrRestart` вместо `pm2 restart` — корректно работает и при первом запуске процесса.
- **CLI**: из шаблона `indexDevMin` убран неиспользуемый импорт `Bot`.
- **[Критично] CLI**: сгенерированный обработчик Yandex Cloud Function вызывал `bot.run()` напрямую, минуя проверку подлинности вебхука (`isCorrectQuery`) — деплой Telegram/VK с секретом вебхука принимал поддельные запросы на публичном эндпоинте. Обработчик переведён на `bot.webhookEvent()`, который передаёт заголовки запроса и отклоняет неверную подпись со статусом 401. Добавлены регресс-тесты в `tests/Bot/bot.test.ts` и `tests/cli/flowGenerator.test.ts`.
- **CLI**: `collectVarNames` теперь собирает имена переменных из `saveResponseTo` (результат HTTP-запроса) — ранее при использовании такой переменной в выражении `set_variable` генератор выпускал строковый литерал вместо `Number(ctrl.userData.<var>)`. Добавлен регресс-тест.
- **CLI**: `validate` теперь отклоняет циклы, состоящие только из исполняемых блоков (action/condition/response): они генерируются как рекурсивные вызовы и при выполнении переполняют стек. Циклы, проходящие через command/step, по-прежнему допустимы (навигация идёт через `thisIntentName`). Добавлены регресс-тесты.
- **Utils**: fallback для длинных строк в `similarText` возвращал 100% схожести при равных длинах и совпадении первых 50 символов, игнорируя полностью отличающиеся хвосты. Префиксная проверка заменена на сравнение нескольких окон, распределённых по всей длине строки. Добавлены регресс-тесты.
- **Middleware**: `destroyRateLimiter()` очищал только последний созданный инстанс `rateLimiter()` (модульные ссылки перезаписывались каждым новым вызовом) — интервалы и карты состояний более ранних инстансов утекали. Теперь все инстансы регистрируются в реестре, и destroy освобождает ресурсы каждого. Добавлен регресс-тест.
- **Документация**: `next-release.md` переименован в `migration-2x-to-3x.md` (содержимое — гайд миграции 2.x→3.0, старое название вводило в заблуждение); ссылки в README/FAQ/CHANGELOG обновлены.
- **Документация**: актуализированы `api-reference.md` и `GUIDE.md` — добавлены `addForm`/`removeForm`, `BotTest.simulate()`, исправлен пример кастомной `Model` (публичные абстрактные методы, допустимые типы правил), возвращаемые типы `Preload.loadImages/loadSounds`, версия фреймворка.
- **BotController**: асинхронная ошибка в callback шага теперь тоже выставляет безопасный fallback-текст, если обработчик не успел задать свой.
- **AppContext**: маскировка метаданных в `logError`/`logWarn` защищена try/catch — кидающий геттер в `meta` больше не ломает логирование и обработку запроса.
- **RateLimiter**: LRU-вытеснение при переполнении пропускает записи, которые прямо сейчас обрабатывают очередь, — больше не создаются две параллельные очереди на пользователя.
- **[Критично] Производительность/Логирование**: из асинхронной `saveData` убрана валидация через `JSON.parse`. Она никогда не блокировала запись (в отличие от `saveDataSync`), но для строк лога в режиме дозаписи (начинаются с `[timestamp]`) парс гарантированно падал и через `errorLogger` запускал бесконечный цикл самовоспроизводящихся ошибок с ростом `error.log` каждые ~200 мс. Для `saveFileData` тот же парс выполнял лишнюю полную десериализацию всей таблицы при каждом сохранении `FileAdapter`.
- **Производительность/Viber**: `ViberRequest.call()` сериализует тело запроса один раз и переиспользует строку как тело (`postInString`) — ранее `JSON.stringify` выполнялся дважды (для проверки размера 30 КБ и внутри `Request._getOptions`).
- **Производительность/Telegram**: `getContent` больше не инстанцирует `Card` (и вложенный `Buttons`) на каждый запрос — добавлена проверка `isCardInit()`, как в остальных адаптерах.
- **Viber**: добавлена обработка всех типов событий webhook: `subscribed`, `unsubscribed`, `delivered`, `seen`, `failed` — ранее они вызывали ошибку, так как `setQueryData` возвращал `false`. Теперь `subscribed`/`unsubscribed` логируются, а `delivered`/`seen`/`failed` корректно подтверждаются без ошибок.
- **Viber**: исправлена детекция платформы — `isPlatformOnQuery` больше не требует `message_token`, так как события `conversation_started`, `subscribed`, `unsubscribed` его не содержат.
- **Viber**: для не-текстовых сообщений (picture, video, file, location, contact, sticker) теперь сохраняется тип сообщения (`viberMessageType`) и дополнительные данные (`media`, `location`, `contact`, `sticker_id`) в `controller.payload`.
- **Viber**: `getQueryExample` теперь включает поле `timestamp`, чтобы пример проходил `isPlatformOnQuery`.
- **Viber**: `IViberContent.message_token` и `sender` сделаны опциональными в интерфейсе — для событий без этих полей.
- **Viber**: Rich Media карточки без кнопок получают нейтральный `ActionType: none` и больше не отправляют придуманный фреймворком текст от имени пользователя.
- **Viber**: исправлено обращение к `query.user.name` вместо `query.sender.name` в событии `conversation_started`.
- **Max**: базовый URL API обновлён с `platform-api.max.ru` на `platform-api2.max.ru` в соответствии с миграцией платформы (июнь 2026).
- **Max**: в `IMaxButton.type` добавлен тип `clipboard` — кнопка, копирующая текст из `payload` в буфер обмена пользователя.
- **Max**: исправлено значение `update_type` с `message_editing` на `message_edited` для соответствия документации платформы.
- **Max**: добавлена опциональная проверка подписи webhook через заголовок `X-Max-Bot-Api-Secret`. Включается передачей `secret` в `additionalPlatformOptions` конструктора адаптера.
- **Max**: при `skipAutoReply=true` больше не выполняются лишние HTTP-вызовы загрузки изображений и аудио (buttonProcessing, cardProcessing, soundProcessing).
- **Max**: добавлена поддержка групповых чатов и каналов — при наличии `chat_id` в webhook ответ отправляется в чат, а не в личный диалог.
- **AppContext**: кастомный `logger.error`/`logger.warn` теперь маскирует секреты в сообщении и вложенных `meta` по умолчанию; `maskSecrets: false` по-прежнему оставляет явный opt-out для отладки.
- **BotController**: синхронные исключения в callback шага теперь логируются и переводят диалог в безопасный ответ вместо падения обработки.
- **MongoAdapter**: при переподключении закрывается предыдущий `mongoClient`, а не новый клиент перед `connect()`.
- **Telegram**: multi-image карточки ограничены лимитом `sendMediaGroup` в 10 элементов; одиночное изображение после фильтрации отправляется через `sendPhoto` без повторного вызова `sendMediaGroup`.
- **CLI**: generated HTTP-запросы теперь используют `fetchWithTimeout`, корректно обрабатывают `catch (e: unknown)` и безопасно сериализуют JSON body с переменными `{{var}}`.
- **CLI**: `serverless.yml` больше не получает реальные значения токенов из `flow.json`, а использует ссылки на переменные окружения.
- **CLI**: `create from-flow` больше не перезаписывает непустую директорию без `--force`.
- **CLI Docker**: builder-stage устанавливает devDependencies, поэтому TypeScript-компилятор доступен при `npm run build`.
- **Bot**: исправлена потенциальная потеря данных: `#saveUserData` теперь обёрнут в `try/finally`, гарантируя сохранение даже при исключениях в бизнес-логике.
- **Bot**: ошибка подключения к БД теперь корректно возвращает `undefined` вместо нерабочего экземпляра адаптера.
- **VK**: исправлена обработка `message_event`: адаптер теперь использует `messages.sendMessageEventAnswer` с обязательными `event_id`, `user_id` и `peer_id`, а также валидирует размер `event_data`.
- **VK**: преобразователь кнопок больше не мутирует исходные универсальные кнопки и не переносит в VK посторонние поля из `options`.
- **VK**: payload кнопки проверяется после сериализации; значение больше 255 символов не обрезается до повреждённого JSON — кнопка пропускается с предупреждением.
- **VK**: исправлен поиск hash для кнопок `vkpay` при передаче строкового payload.
- **Telegram**: добавлена проверка длины `callback_data` (лимит 64 байта). При превышении кнопка пропускается с предупреждением, а идентификатор разработчика не изменяется.
- **Telegram**: исправлена обработка multi-image: при фильтрации и остатке менее 2 элементов изображение корректно отправляется через `sendPhoto`.
- **Viber**: исправлен лимит `Columns` в карточках с 7 на 6 (в соответствии с Viber Rich Media API).
- **Viber**: `ActionBody` в кнопках теперь использует `button.payload` (если он есть), а не всегда `button.title`.
- **Viber**: текст ограничивается документированными 7000 символами, запросы больше 30 КБ не отправляются, а обязательные поля текстового сообщения нельзя случайно перезаписать через `params`.
- **Viber**: универсальные опции кнопок фильтруются и сопоставляются с нативными действиями; обязательные поля webhook/Rich Media защищены от перезаписи, а видимое имя sender больше не придумывается фреймворком.
- **Viber**: событие `conversation_started` теперь корректно обрабатывается (ранее блокировалось условием `if (query.message)`).
- **Маруся**: добавлена строгая валидация размера payload (лимит 4096 байт) и state (лимит 3584 байта).
- **AppContext**: `JSON.stringify` в методах `logError`/`logWarn` обёрнут в `try/catch` для защиты от циклических ссылок в метаданных.
- **AppContext**: `setAppConfig` теперь выполняет глубокое слияние (deep merge) для поля `db`.
- **AppContext**: `setPlatformParams` больше не мутирует исходные интенты пользователя.
- **Max Card / Telegram Card**: добавлена проверка границ (bounds) для `images[0]` при `showOne=true`.
- **Max Sound**: исправлена потеря данных: при отсутствии `speech_kit_token` теперь возвращаются уже обработанные звуки вместо `null`.
- **SmartApp Card**: `buttonProcessing` теперь возвращает `null` вместо `[]`, если `isCard=true`, но валидной кнопки не найдено.
- **CLI**: исправлен краш при выполнении `add docker` / `add deploy` (проблема инициализации `#name` вне `createProject()`).
- **CLI**: исправлена генерация невалидного кода при пустых условиях в шаблонах.
- **CLI**: `tsconfig.json` в flow-генераторе обновлён с устаревшего `moduleResolution: 'node'` на `Node16`.
- **Utils**: `httpBuildQuery` теперь использует `encodeURIComponent` вместо `encodeURI` для корректного кодирования спецсимволов (`&`, `=`, `#`) в значениях параметров.
- **Utils**: `fwriteSync`/`fwrite` теперь гарантированно удаляют временные файлы при ошибке переименования.
- **Utils**: удаление временных файлов в модулях VK Sound и Telegram Sound обёрнуто в `try/catch`.
- **EnvConfig**: добавлена корректная обработка inline-комментариев (`#`) в `.env` файлах.
- **RateLimiter**: `destroyRateLimiter` теперь очищает очередь (drain) перед полным удалением.
- **UsersData**: исправлено несоответствие ключей между `RULES` и `ATTRS_LABEL` (`platformName` заменён на `platform`).
- **CLI**: `.gitignore` в шаблоне проекта всегда оказывался пустым, из-за чего `.env` с токенами коммитился в Git. Теперь читается из правильного пути `cli/template/.gitignore`.
- **HTTP**: `Request` по умолчанию не имел таймаута — вешал бота при зависании API платформы. Установлен безопасный default `2000 мс` (переопределяется через свойство `request.maxTimeQuery`).
- **CLI**: при формате `tokens` в `flow.json` в стиле `{telegram: {token: "..."}}` (как в README) процесс генерации падал с `TypeError`. Поддержаны оба формата — плоский и вложенный.
- **CLI**: текстовые шаблоны вида `{{user.name}}` не разворачивались (regex `\w+` не захватывал точку). Теперь поддержаны.
- **CLI**: `execFileSync(prettier)` без таймаута мог навсегда зависнуть при сломанном prettier. Добавлен `timeout: 30_000`.
- **CLI**: `CreateController.init()` ломался на Windows при путях с обратным слешом. Поддержаны оба разделителя.
- **Navigation**: `selectedElement('12')` возвращал первый элемент вместо двенадцатого (`+data[0][0]` вместо `+data[0]`).
- **Telegram**: webhook-проверка подписи использовала HMAC от тела, но Telegram шлёт `x-telegram-bot-api-secret-token` как plain string. Все webhook-запросы отклонялись. Теперь — прямое `timingSafeEqual` с опциональным `appConfig.tokens.telegram.webhookSecret`.
- **Telegram**: исправлен тип `photo` в `ITelegramResultContent` — заменён с одиночного `ITelegramPhoto` на массив `ITelegramPhoto[]` в соответствии с Telegram Bot API (`sendPhoto` возвращает `PhotoSize[]`). `file_id` теперь извлекается из последнего элемента массива (самое большое разрешение).
- **Telegram**: `ITelegramResult.result` сделан опциональным (`result?: ITelegramResultContent | null`) — при `ok: false` результат отсутствует.
- **Telegram**: добавлена обработка `edited_message` и `edited_channel_post` в `setQueryData()` (ранее определялись в `isPlatformOnQuery`, но не обрабатывались).
- **Telegram**: для reply-клавиатуры добавлен `resize_keyboard: true` для автоматического подгона размера под экран.
- **Telegram**: типизированы поля `edited_message` и `edited_channel_post` в `ITelegramContent` — заменены с `Record<string, unknown>` на `ITelegramMessage`.
- **Telegram**: в `ITelegramKeyboard` добавлены поля `resize_keyboard`, `one_time_keyboard`, `input_field_placeholder`.
- **Telegram**: добавлены юнит-тесты для `TelegramAdapter`, `Button.ts`, `Card.ts` и `Sound.ts`.
- **Telegram**: callback подтверждается отдельно от сообщения в чат: `controller.text` больше не дублируется как toast. Явный `platformOptions.callbackNotificationText` и прямой `answerCallbackQuery.text` ограничиваются 200 символами.
- **Viber**: одиночная карточка (одно изображение) молча не отправлялась — `cardProcessing` возвращал объект, а не массив.
- **Marusia**: `marusiaCreateAudio` отправлял `audio_meta` как `[object Object]` вместо JSON-строки — загрузка звуков не работала.
- **MongoDB**: повторный вызов `connect()` переиспользовал "закрытый" `MongoClient`, вызывая ошибку `MongoClient is closed`. Каждая попытка теперь создаёт новый клиент.
- **MongoDB**: при `update` поля со значением `undefined` сохранялись в BSON и затирали существующие поля. Теперь фильтруются перед `$set`.
- **Models**: `ImageTokens.selectOne()` и `SoundTokens.selectOne()` искали по первичному ключу (который `null` на новой модели) вместо задокументированного паттерна поиска по `path`. Кэш токенов платформ не работал.
- **CommandReg**: `removeCommand` удалял команду до обновления regexp-группы, из-за чего команда оставалась внутри группы и создавалась "пустая" запись с ключом `undefined`.
- **Bot**: `gracefulShutdown` падал при ошибке `close()`, не вызывая `process.exit(0)`. Добавлен `try/catch`.
- **Bot**: при невалидном webhook токене в лог-файл улетал весь `IncomingMessage` (IP, headers, sockets). Теперь только metadata.
- **AppContext**: `#errWarnLog` при непрерывном потоке ошибок рос без ограничений. Добавлен cap в 1000 записей с немедленным flush.
- **AppContext**: `setAppConfig({db: ...})` затирал вложенный `db.options`. Теперь выполняется глубокое слияние.
- **RateLimiter**: при переполнении `stateMap` новые пользователи проходили без ограничений (fail-open). Заменено на LRU eviction.
- **Utils**: `fwriteSync`/`fwrite` при параллельной записи одного файла конфликтовали на одном `.tmp` имени. Добавлен уникальный суффикс `{pid}.{timestamp}.{rand}.tmp`.
- **Utils**: `similarText` при длинных строках (>2000 суммарно) блокировал event loop из-за O(n·m). Добавлен быстрый fallback по префиксу/длине.
- **MAX**: лимит текста был 4096, но API отклоняет >4000. Исправлено.
- **VK**: `random_id` выходил за диапазон int32 (`Date.now()*1000`). Ограничен до `2^31-1`.
- **VK**: при отправке по `screen_name` в тело запроса уходил `peer_id=undefined`. Поле удаляется вместо присваивания.
- **VK**: `signatureName = 'x-vk-signature'` ломал webhook при заданном token — у VK другая модель безопасности (через `secret_key` в теле запроса), HTTP-подписи нет. Теперь `signatureName = undefined`, чтобы базовая HMAC-проверка не блокировала webhook.
- **VK**: добавлена опциональная проверка `secret` из тела запроса VK Callback API. Если задан `vk_secret_key` в конструкторе адаптера или переменная `VK_SECRET_KEY` в `.env`, адаптер сверяет поле `secret` каждого запроса с сохранённым значением. Не влияет на `confirmation` событие.
- **VK**: `getContent()` для `message_event` теперь при ошибке бизнес-логики отправляет `sendMessageEvent` с `show_snackbar` и текстом ошибки, а не пустое подтверждение.
- **Viber**: действие `message` без `sender` больше не приводит к `TypeError: Cannot read properties of undefined` — адаптер корректно отклоняет malformed-запрос через `platformOptions.error`.
- **AppContext**: маскировка метаданных логгера больше не помечает повторяющиеся (не циклические) объекты как `[Circular]` — маркер срабатывает только при реальной циклической ссылке.
- **AppContext**: overflow-flush `#errWarnLog` сбрасывает только переполненную очередь (`errors` или `warnings`), не затирая вторую и не прерывая debounce.
- **Платформы**: устранены несовместимости с актуальными API: MAX использует raw `X-Max-Bot-Api-Secret`, `user_id`/`chat_id` в query, двухшаговую загрузку файлов, матрицу inline-клавиатуры и `answers` для callback; VK отвечает в `peer_id` беседы; Viber не отправляет пустые сообщения на служебные события; Алиса ограничивает state 1 КБ; Telegram не включает HTML-разметку без явной настройки и отвечает на inline-запросы через `answerInlineQuery`.
- **Платформы**: Telegram направляет ответ на callback-кнопку в исходный чат, VK сериализует кнопки карусели плоским массивом, SmartApp использует массив `actions` и документированный формат `server_action`, а Viber не вызывает `send_message` с пустым текстом перед Rich Media.
- **Платформы**: устранены дополнительные несовместимости форматов: MAX соблюдает лимит 2 сообщения и 2 callback-ответа/с на диалог, ограничивает клавиатуру 30 рядами, отправляет изображения отдельными attachments и принимает служебные webhook-события; Viber передаёт обязательный sender и реальные метаданные файлов; SmartApp формирует актуальный `left_right_cell_view`; Алиса ограничивает `ImageGallery` десятью элементами и `BigImage.description` 1024 символами.
- **Платформы**: адаптеры больше не подставляют пользовательские реплики «Ответ готов», «Ответ бота», «Смотрите на экране», «Выберите действие», «Изображение» и «Открыть». Пустой ответ Алисы/Маруси сохраняется без подстановки и исключения с диагностическим предупреждением; Telegram/MAX не отправляют недопустимое пустое сообщение.
- **Алиса**: `start_account_linking` перенесён из недопустимого верхнего уровня в `response.directives`; ответ авторизации теперь содержит обычный `response` согласно протоколу.
- **MAX**: пустой callback корректно подтверждается через `POST /answers` без придуманного сообщения; webhook URL с нестандартным портом отклоняется до HTTP-запроса.
- **Card**: `addImage()` и `addOneImage()` больше не подставляют пробел в отсутствующие `title`/`description`; используются честные пустые строки.
- **Кнопки платформ**: циклические и иные несериализуемые payload больше не обрушивают обработку запроса; невалидная кнопка пропускается без подмены данных.
- **CLI**: генератор больше не вставляет произвольные значения flow в TypeScript: для `set_variable` доступна ограниченная арифметика, HTTP-методы и границы случайного числа валидируются, а пустой успешный HTTP-ответ корректно сохраняется как `null`.
- **CLI**: `hostname` и `port` из пользовательской конфигурации безопасно сериализуются перед вставкой в шаблон; CLI на JavaScript теперь проходит ESLint.
- **CLI**: зависимости генерируемых проектов зафиксированы, а `package-lock.json` больше не исключается из Git.
- **CLI**: версия `umbot` в flow-генераторе синхронизирована с production-шаблоном `3.1.0`.
- **CLI**: flow-генератор теперь нормализует имена блоков в безопасные TypeScript-идентификаторы, сохраняет HTTP-метод у запросов без body и корректно обрабатывает проверки `isSay*` по текущему вводу.
- **CLI**: Cloud Functions-проект больше не запускает локальный HTTP-сервер, использует compiled entrypoint `dist/index.handler` и актуальный runtime Node.js 22.
- **CLI**: шаблоны Docker и GitHub Actions больше не требуют отсутствующий `package-lock.json`; опасные перезаписи `.env`, Dockerfile и workflow требуют явный `--force`.
- **CLI**: `npx umbot create` больше не показывает предупреждение «не удалось отформатировать код» каждому пользователю: prettier не входит в зависимости публикуемого пакета, поэтому раньше вызов всегда завершался ошибкой. Теперь форматтер ищется как модуль (доступен в репозитории фреймворка или если prettier установлен в проекте пользователя), при отсутствии молча пропускается, а запуск идёт через `node` — заодно это чинит Windows, где Node ≥ 20.12 запрещает запуск `.cmd` без shell.
- **CLI**: `create from-flow` принимает flow.json в любом месте аргументов — флаги `--output`/`--usecloud` можно ставить как до, так и после пути к файлу (раньше путь обязан был идти сразу после `from-flow`).
- **CommandReg**: регистрация более 300 pattern-команд теперь поддерживает произвольные имена команд, включая дефис.
- **VK**: токен подтверждения вебхука (событие `confirmation`) читался только из опции конструктора `vk_confirmation_token` — настройка через `.env`/`process.env` (`VK_CONFIRMATION_TOKEN` → `tokens.vk.confirmation_token`) не работала, что особенно заметно при `fullPlatforms`, где опции адаптеру не передать. Добавлен запасной вариант из конфигурации. Регресс-тесты в `tests/Platforms/VK/adapter.test.ts`.
- **CLI**: сгенерированный `DefaultController` имел пустой `action()` — бот отвечал пустым сообщением на любую реплику. Шаблон приветствует на `welcome`, показывает справку на `help` и повторяет команду пользователя в остальных случаях; `defaultParams.js` заполняет `welcome_text`/`help_text` и явно задаёт интенты welcome/help.
- **CLI**: в шаблонах quiz-проекта кейс `welcome` не задавал текст — Алиса получала невалидный пустой ответ на первой реплике. Приветствие добавлено в `QuizController` и `quizParams.js`.
- **Dockerfile**: builder-стадия корневого Dockerfile выполняла `npm ci --only=production`, из-за чего `tsc` отсутствовал (TypeScript — опциональная peer-зависимость) и `npm run build` падал. Builder ставит полный набор зависимостей (`npm ci`), runtime-стадия — только production (`npm ci --omit=dev`).
- **Examples**: общий конфиг `examples/config/skillDefaultParam.ts` заменял встроенные интенты массивом без `help` — команда «помощь» попадала в fallback во всех примерах, где контроллер обрабатывал help-кейс. Welcome/help-интенты добавлены в массив.
- **Бенчмарки (um.js)**: исправлена методология и вердикты `benchmark/um.js` (npm run comparison): «Warm» не измеряется первым проходом после `forceGC()` (полный GC сбивает inline-кэши V8 — замер получался полухолодным и завышал отставание до 2×); прогоны clean/umbot чередуются (lockstep), память разделена на retain-утечки и транзиентный мусор (в вердикте участвует только retain). Вердикт переписан: время — главный показатель, память в пределах порога шума (±1 КБ) не влияет. Результаты до исправления методологии завышали отставание umbot в 2–5 раз. Подробности — в BENCHMARKS.md.
- **Бенчмарки (регресс, исправлен)**: при подключении честной проверки доступной памяти (запись выше про `bench`/`stress`) она попала в per-request hot path `stress-test.js` — `getAvailableMemoryMB()` вызывалась на каждый запрос (~170 мкс, RPS падал с десятков тысяч до ~7k). Полная проверка выполняется один раз до цикла, внутри итераций — только дешёвый порог `heapUsed > 3700`. Уровень восстановлен (~55k RPS последовательного режима).
- **Документация**: `src/docs/BENCHMARKS.md` — добавлена секция comparison с актуальными числами (итог −59% времени по сумме сценариев; umbot быстрее «чистой» реализации на 20–98% во всех сценариях с реальным числом команд), описание честной методологии замера и инструкция `UM_COMPARISON_ROUTER`. Секция «Результаты на реальном сервере» (VDS) не менялась — это отдельное окружение.
- **CommandReg**: команда, у которой все слоты — RegExp, а `isPattern` не задан, склеивалась в одно выражение (и в группу регулярок) с флагами `ium` вместо флагов самих слотов: `[/^да$/, /^нет$/]` начинал совпадать с «ну\nда». Теперь склейка идёт с общими флагами слотов, группа хранит свои флаги (`IGroupData.flags`) и принимает только команды с такими же; слоты с разными флагами проверяются по отдельности. Явный `isPattern: true` склеивает с `ium`, как и раньше.
- **FileAdapter**: `close()`/`destroy()` начинали финальную запись таблицы, не дождавшись уже выполняемой debounce-записи — при обратном порядке завершения старый снимок перезаписывал итоговый. Финальная запись стартует после завершения предыдущей.
- **Telegram, VK, Viber**: служебные апдейты без ответа (`my_chat_member`, `poll_answer`, `message_reply`, `delivered`, `seen` и т.п., а также некорректные события) подтверждаются сразу, до бизнес-логики. Раньше `skipAutoReply` глушил только отправку ответа, а middleware и fallback-команда отрабатывали на каждый такой апдейт — `delivered`/`seen`/`message_reply` приходят на каждое исходящее сообщение бота.
- **Telegram**: `sendPoll` сортирует `correct_option_ids` и убирает дубли — Bot API требует монотонно возрастающий список индексов.
- **Тесты**: `tests/Bot/regressions.test.ts` импортировал `src/preload` вместо `src/Preload` — на регистрозависимой ФС (Linux CI) набор не запускался.

### Документация и внутренние улучшения (Chore)

- **Готовность к TypeScript 7**: тесты, проверяющие компилируемость артефактов (`tests/examples/compile.test.ts`, `tests/cli/createController.test.ts`, `tests/cli/flowGenerator.test.ts`), переведены с программного API компилятора (`require('typescript')` — `createProgram`/`getPreEmitDiagnostics`) на вызов CLI `tsc` через новый хелпер `tests/helpers/typecheck.ts` (временный `extends`-конфиг с подменой `umbot` → `dist/`, без `baseUrl`). TS 7 — нативный компилятор без JS API, но CLI работает одинаково на всех мажорных версиях; тесты больше не зависят от способа публикации компилятора. `examples/tsconfig.json` и `examples/umbot-demo/tsconfig.json`: `moduleResolution` `node` (node10, удалён в TS 7) → `Node16`, `module: CommonJS` → `Node16` (проверено сборкой). Дополнительно тест конфигурации примеров теперь фиксирует отсутствие deprecated-опций (`node10`/`baseUrl`/`module: CommonJS`).
- **CLI**: генерируемые проекты поднимаются на актуальный тулчейн — TypeScript `5.9.3` → `6.0.3`, `@types/node` `20.19.43` → `24.13.4` (шаблон `package.json.text` и `flowGenerator.js`); Docker-образ `node:20-alpine` → `node:24-alpine` (Node 20 — EOL с апреля 2026), Node в `deploy.yml` `20` → `24`, `engines.node` `>=20.19.0` → `>=24.0.0` (соответствует Active LTS Krypton). Сгенерированные проекты проверяются типечеком через CLI tsc на новых версиях — все варианты шаблонов (`umbot create` и flow-генератор) собираются.

- **Стиль кода**: единообразие по всему `src/` и в тестах — 342 однострочных JSDoc-комментария (`/** текст */`) развёрнуты в многострочные блоки, 50 однострочных веток (`if (x) stmt;`, включая `while` и `else if`) обёрнуты в фигурные скобки. Правило закреплено в ESLint: `curly: ["error", "all"]` — однострочные ветки больше не пройдут проверку. Изменения чисто косметические: поведение и публичный API не затронуты, все 1489 тестов зелёные.

- **Скиллы**: все 11 скиллов `.agents/skills/umbot-*` прошли аудит «симулированной задачей с ловушками» (субагенты выполняли воркфлоу скилла в read-only режиме) и актуализированы по его итогам. Ключевое: `umbot-platform-add` приведён к событийному слою 3.1.0 (`supportedEvents`, `controller.eventType`, `getQueryExample`, `static isVoice()` для регистрации в `botPlatforms`/`voicePlatforms`, API-фасад, env-связка токенов, обязательные тесты eventType/serviceEvents; «на 4xx/5xx» → «на 5xx»); `umbot-release-prepare` — формат тега зафиксирован по имени релизной ветки (`v-X.Y.Z`), полный список хардкодов версии (включая пины в `cli/flowGenerator.js` и `cli/template/package.json.text`), smoke-тест CLI перенесён на after-publish (E404 до публикации), состав пакета по `files` (dist+cli), порядок «Миграция» для breaking-изменений, compare-ссылка и дата секции в CHANGELOG-аудите; `umbot-write-tests`/`umbot-fix-bug` — битые референсы исправлены (`getAlisaQuery`→`getContent` как в `tests/Bot/bot.test.ts`, `TEST_DATA_DIR`→реальный образец `tests/DbModel/fileAdapterSelect.test.ts`), импорты в сниппетах приведены к конвенции репо (`../../src`, не self-reference `'umbot'`), `res.response?.text`, `bot.close()` в afterEach, добавлен паттерн «перехват лога» для репортов «в логах ошибка»; `umbot-code-review` — ссылка «AGENTS.md 10.2» заменена на раздел 10 п.2, отражены eslint-overrides для `tests/`/`cli/` (правила промисов там отключены), правило HTTP 200 распространено на весь путь запроса (не только адаптеры), «await без try/catch» как отдельный случай; `umbot-core-engineer` — конвенция breaking-изменений CHANGELOG («Миграция с X.Y.z» + `[Breaking]`), новое архитектурное правило про лимит/вытеснение накапливаемого состояния (DoS через attacker-controlled `userId` на неподписанных платформах), стоп-условия переписаны на реальные механизмы; `umbot-add-middleware` — `deniedText` у `ipFilter`, stub логгера в тест-каркасе, шаг дедупликации middleware; `umbot-platform-code-update` — полный список API-классов (`YandexRequest`, `constants.ts`, `interfaces/`), правило «не версионировано в коде» вместо выдумывания версий, VK-механики (`user_ids`, кэш `getVkUserInfo`, `vk_load_user_info`, passthrough параметров, наследование `MarusiaRequest`), исправлена грамматика ключевого раздела; `umbot-platform-code-audit` и матрица AGENTS.md (раздел 9) — webhook-подпись MAX исправлена на `x-max-bot-api-secret` (Authorization — auth исходящих запросов, не подпись); `umbot-doc-audit` — снято противоречие «минимум 2 раунда», правило «репорты — гипотезы».

- **Безопасность (дока)**: проверка подписи вебхука повышена с необязательных сносок в платформенных разделах до обязательного шага настройки production. Новый раздел «Проверка подписи вебхука (обязательно для production)» в `configuration.md` — таблица «платформа → что задать», генерация секрета, полный пример для Telegram (setWebhook + webhookSecret), предупреждение о поведении без секрета (адаптер принимает любой запрос от имени любого пользователя) и ограничения голосовых платформ без подписи (Алиса/Маруся/SmartApp: `userId` — не аутентифицированная идентичность). Production-чеклисты в `getting-started.md` и `GUIDE.md` дополнены пунктом про секреты вебхука (сверка по предупреждению в логе при старте); в `getting-started.md` добавлен блок «Проверка подписи вебхука — обязательный шаг» с примером конфигурации. В `platform-integration.md` шаги настройки Telegram/VK/MAX переписаны: явная генерация секрета, включение «Секретного ключа» у VK, конструктор `new MaxAdapter(token, { secret })`, и для каждой платформы описано, какие запросы проходят без секрета. `.env`-примеры (`configuration.md`, `GUIDE.md`) дополнены `TELEGRAM_WEBHOOK_SECRET`/`MAX_WEBHOOK_SECRET` с пометкой, что фреймворк читает их только вручную через `process.env`.
- **Контракт API-фасада в одном месте**: канонические `IControllerApi` и новый `TApiMethod` живут в `src/controller/BotController.ts`; `TApiFacade`/`TApiMethod` из `umbot/plugins` стали алиасами этих типов (раньше контракт дублировался в `apiFacade.ts` и рассинхронизировался руками). Публичные имена и места экспорта не изменились — обратная совместимость сохранена.
- Исправлен JSDoc-заголовок `IMaxAudio` (копипаст от `IMaxCard`); `answerCallback` фасада документирует, что `showAlert` поддерживает только Telegram (VK/MAX игнорируют).
- Исправлены некорректные форматы дат в истории изменений (например, `2025-30-11` → `2025-11-30`).
- Внутренний тип `TPatternRegExp` заменён на `(string | RegExp)[]` в `api-reference.md`.
- Относительные импорты `../../src` заменены на алиас `umbot` в `next-release.md`.
- Исправлены неточности в JSDoc: описание `@param controller` (Alisa), неверное название платформы в Marusia Card, описание конструктора "ВКонтакте" → "Max" в MaxRequest, добавлены пропущенные описания `@param`.
- Удалено избыточное описание "кнопки для кнопок" в Alisa/Card.ts.
- Исправлено форматирование кода (4 блока) в `adapter/platformAdapter.md` и обновлена таблица производительности в `FAQ.md`.
- **CLI**: описание статистики `umbot stats --log <path>` больше не упоминает RPS — отчёт выводит число строк/ошибок/предупреждений, топ команд и p50/p95/p99 latency.
- **CI**: `deploy.yml` репозитория запускает контейнер через `--env-file` и `--restart unless-stopped` вместо передачи токенов флагами `-e` (видны в `ps` и истории docker).
- Добавлен корневой `.dockerignore`: `.env`, логи, тесты, примеры, scratch-файлы и каталоги IDE исключены из образа.
- Из devDependencies удалён deprecated-заглушка `@types/mongodb`; для тестов Mongo-адаптера добавлен реальный `mongodb` (для пользователей он остаётся опциональной peer-зависимостью).
- `.gitignore` закрывает каталог `.zcode` и рабочие scratch-файлы (`pr.md`, `pr2.md`, `project-all-in-one.txt`, `scr.js`, `pulldoc.sh`).
- Устранена циклическая ссылка в `middleware.md` (заменена на ссылку на `GUIDE.md`), скорректирована иерархия заголовков в `FAQ.md`.
- Тест `MarusiaRequest` обновлён для соответствия новой версии VK API, добавлены тесты на критический функционал.
- Перефразирована META-фраза в `GUIDE.md` (убраны прямые упоминания LLM), сокращена секция плагинов в `FAQ.md` с добавлением абсолютной ссылки на документацию.
- В `platform-integration.md` добавлен отсутствовавший раздел «Создание бота для Viber» (регистрация в Viber Admin Panel, токен, sender, регистрация вебхука) и ссылки на порталы разработчика: консоль Маруси, портал Сбера (SmartApp), dev.vk.com (VK).
- Обновлены `examples/README.md` и README отдельных примеров: актуальные команды запуска через общий пакет примеров.
- В `getting-started.md` добавлен раздел «Локальная отладка с реальными платформами (туннель)» — как пробросить локальный вебхук в интернет через ngrok/cloudflared. Исправлен snippet `await bot.test()` (в CommonJS-проектах top-level await не работает), уточнены пункты про `welcome_text` (бот отвечает placeholder-текстом по умолчанию, а не молчит) и FileAdapter (держит таблицу в памяти, но пишет в JSON-файл).
- В `GUIDE.md` в быстрый старт добавлен пропущенный `npm run build` перед `npm run start` (без него запуск падал: `start` выполняет собранный код из `dist/`). Аналогичный snippet в `FAQ.md` уже был корректен.
- Исправлены неточности `cli/README.md`: `--prod` не создаёт `.env` (только Dockerfile, `.dockerignore` и `deploy.yml`), дефолтный hostname — `0.0.0.0`, а не `localhost`, ссылка на визуальный редактор ведёт на flow.maxim-m.ru, в структуре from-flow-проекта указан `.gitignore`. Пустая служебная папка `cli/my-bot` удалена из публикуемого пакета.
- Аудит документации с позиции нового пользователя: в `GUIDE.md` исправлены некомпилируемые примеры (в том числе `YandexImageRequest` — порядок аргументов конструктора, `setOAuth` без дублирующего префикса, поля ответа `checkOutPlace`), таблица подписей вебхуков (у Маруси и SmartApp подписи нет), лимиты (state 1 КБ, кнопки, пороги таймаутов 2000/2900 мс), описание `setAppMode` (маскировка секретов включена всегда и не зависит от режима; prod-режим предупреждает, но не блокирует), возвращаемые типы `Preload`, эффекты TTS (`S_EFFECT_*` подставляются только для Алисы, сырой SSML проходит в Марусе и SmartApp). Актуализированы `configuration.md` (приоритет токенов, тихий подхват из `process.env`), `deployment.md` (serverless-обработчик через `webhookEvent`), `testing.md` (`simulate()` без реальных API-вызовов), `platform-integration.md` (секреты вебхуков Telegram/MAX, порог таймаута), `FAQ.md`, `json-format.md` (генерируемый код условий — `if`, а не `switch`; импорт `rand`; наборы платформ `fullPlatforms`/`voicePlatforms`/`botPlatforms`), `migration-2x-to-3x.md`, `middleware.md`, `performance-and-guarantees.md`.
- JSDoc: порядок диспетчера запросов (step → command → intent → fallback) перенесён с приватного метода на публичный `BotController.run()` с примером; в `Bot.run()` добавлено описание параметра `clientIp`; в `isCorrectQuery` базового адаптера исправлено описание проверки подписи (VK — сравнение `secret` из тела запроса, MAX — заголовок `x-max-bot-api-secret`, без подписи — только Алиса, Маруся и SmartApp).
- `examples/`: пути к хранилищу в общих конфигах указывали внутрь `dist/` и терялись при пересборке — теперь это `examples/data/` (создаётся автоматически, в `.gitignore`); `skills/json/UsersData.json` наполнен реальной структурой `FileAdapter` (ключ — `userId`, `meta`/`data` — JSON-строки) с описанием в README; в пример `standard` добавлен обработчик `save`, демонстрирующий сохранение `userData` через `FileAdapter`. Все примеры проверены запуском: консольный режим `BotTest` и HTTP-сервер в `game`.
- Повторный аудит документации (второй проход): в `api-reference.md` исправлены комментарий `maskSecrets` (маскировка включена всегда, а не только в `strict_prod`), ложное утверждение об индексах `UsersData` (фреймворк их не создаёт), пример с переприсваиванием `userData` (заменено на `Object.assign`) и добавлено предупреждение, что `Preload` молча не работает без токенов. В `http-client.md` примеры приведены к компилируемому виду (сужение типа `input`) и перевод на `simulate()` вместо блокирующего `bot.test()`. В `getting-started.md` устаревший `YANDEX_TOKEN` заменён на `ALISA_TOKEN` и уточнён пункт чеклиста про `intents` (переданный массив заменяет встроенные `welcome`/`help`). В `adapter/platformAdapter.md` убрано противоречие про автоподключение `rateLimiter`, уточнено, что `_timeLimitLog` нужно вызывать в своём `getContent`, и исправлены strict-касты в примере `setQueryData`. В `adapter/readme.md` и `adapter/dbAdapter.md` примеры плагинов и DB-адаптера приведены к реальному контракту (`getData`, абстрактные методы `_select/_insert/_update/_remove/isConnected`). В `BENCHMARKS.md` и `performance-and-guarantees.md` актуализированы режимы `bench`/`stress:lite` и убрано невоспроизводимое число «4500 запросов». В `cli/README.md` уточнены `--minimal` (конфиги генерируются) и флаг `--force` у `add env`; в help-выводе CLI `docker` исправлен с `docker-compose.yml` на `Dockerfile и .dockerignore`. Пример `game` переведён на `BotTest`, чтобы закомментированный консольный блок заводился без правок.
- Аудит с позиции конечного пользователя (третий проход): в `GUIDE.md` исправлены сигнатура `card.addButton()` (принимает объект `TButton`, а не два аргумента), `null` в payload `addLink` (заменён на `''` — `null` не проходит strict-типизацию), пример inline-режима Telegram (невалидный `telegramApi.call('answerInlineQuery', ...)` заменён на существующий `telegramApi.answerInlineQuery()` с примечанием, что `call()` принимает только метод и `chat_id`), описание payload Telegram (фреймворк сам парсит JSON-строку в объект), примеры i18n (сужение типа вместо небезопасного каста; регистрация через `createPlugin` вместо перезаписи `ctx.t`), поведение маскировки секретов (работает во всех режимах, а не только в prod) и поведение state Алисы при превышении 1 КБ (поле не отправляется, данные не очищаются). В список методов `Bot`, не возвращающих `this`, добавлен `webhookEvent`.
- `testing.md`: во все примеры с `BotTest` добавлен `bot.use(fullPlatforms)` — без зарегистрированной платформы `test()`/`run()` бросают ошибку, и примеры из документации падали при копировании.
- `FAQ.md`: рецепт одновременной работы с БД и локальным хранилищем сделан null-безопасным — `Object.assign(cBot.state, ...)` бросал `TypeError` на чат-платформах (Telegram, VK, Viber, Max), где `state` равен `null`; заменено на `cBot.state = { ...cBot.state, ... }` с пояснением.
- `api-reference.md`: добавлены отсутствовавшие публичные API — метод `Bot.webhookEvent()`, четвёртый параметр `clientIp` у `Bot.run()`, свойство `BotController.appType`, методы `setAppContext`/`clearStoreData`/`isButtonsInit`/`isCardInit`/`isSoundInit`/`isNluInit`, `Buttons.remove()`/`isRemove` и полный раздел компонента `Nlu` (getFio/getGeo/getDateTime/getNumber/isIntentConfirm и статические getLink/getPhone/getEMail).
- `configuration.md`: уточнено поведение `strict_prod` — опасные RegExp не бросают исключение и не отменяют регистрацию команды, а отключаются (исключаются из слотов) с ошибкой в логах; в `dev`/`prod` используются как есть.
- `middleware.md`: rateLimiter использует фиксированное 1-секундное окно (счётчик сбрасывается каждую секунду), а не скользящее.
- `cli/README.md`: задокументировано, что CLI заменяет все не-буквенно-цифровые символы имени проекта на `_` (`my-bot` → `my_bot`), поэтому рекомендация kebab-case заменена на snake_case.
- JSDoc `IAppContext` (intents): описание ReDoS-проверки по режимам приведено к коду — в `prod` опасные выражения не фильтруются (фильтрация только в `strict_prod`).
- **Полный аудит документации (MD + JSDoc)**: исправлены все расхождения «дока ↔ код», найденные сверкой каждого утверждения с исходниками. Ключевое:
    - JSDoc: `events.ts` — событие `'message'` вызывается для всех текстовых запросов на любой платформе (без фильтрации по `supportedEvents`; фильтр — только предупреждение при регистрации); `Bot.setAppMode('dev')` — ReDoS-проверка не «отключена», а логируется без отклонения (отклонение — только `strict_prod`); `tryParse` возвращает `null` при null-входе; `IViberApi.min_api_version` — дефолт 7, а не 1; поле `mss` помечено устаревшим (в API Viber — `mcc`, TODO на переименование); `YandexRequest.call()` при ошибке API возвращает объект с `error`, а не null; `MaxRequest.messagesSend` — задокументирован 4-й параметр `recipientType`; примеры `IVkUploadServer`/`IVkPhotosSave`/`IVkDocSave` переписаны плоско (не компилировались); `isNluInit()` — `setThisUser` не создаёт объект Nlu; `SmartAppAdapter.getLocalStorage` возвращает данные либо `{}`; `IVkButton._group` и `IViberContent.user` описаны по фактической реализации. Массово добавлены недостающие `@returns`/`@example` у публичных методов (db-адаптеры, платформенные адаптеры, Bot/AppContext/BotTest), исправлены опечатки и битые `{@link}`/`@see`, устранены обе жалобы typedoc (`TEventType` в `pUtils`, `@internal` у `CommandReg`).
    - MD: `middleware.md` — порядок «глобальные → платформенные» переписан честно (код после `next()` глобальных middleware выполняется после платформенной цепочки, в обратном порядке) и `limit: 0` тоже отключает rateLimiter; `api-reference.md` — Viber `can()` возвращает `false` (не `✓`), `run()` — все параметры с дефолтами, `selectedElement` ищет только на текущей странице, `removeImages/removeSounds` реализованы только для Алисы/Маруси, добавлено предупреждение о молчаливой незарегистрации команды с пустыми слотами; `json-format.md` — «генерируемый код» http-паттерна приведён к реальному генератору (текст + JSON.parse с fallback, headers), `isEmpty` генерирует `!a`, суффиксы коллизий имён; `GUIDE.md` — Express-примеры без `express.json()` (конфликтует с `webhookHandle`), `isEnd` поддерживают только голосовые платформы, Viber `tts` при пустом `text` уходит как текст, `numberPage` требует цифру, реальное сообщение TS-ошибки, согласованы пороги FileAdapter (250/270 МБ) и таймауты (~3 c), лимит кнопок MAX (30 в реализации против 7×30 платформы); `platform-integration.md`/`platformAdapter.md` — примеры кастомного адаптера дополнены импортами (`BotController`, `Text`), `chatId`/`eventId` хранятся в `platformOptions.requestData`, `console.error` заменён на `logError`; `README.md` — MAX включён в перечень чат-платформ.
    - Examples: «живая сессия» `umbot-demo` — цена второго заказа исправлена (180 ₽, каждый 5-й бесплатный), в таблицу env добавлен `SMARTAPP_TOKEN`; лишняя кавычка в заголовке карточки `addCommand`; в `skillAuthParam`/`skillGameParam` добавлены TODO о недостижимых welcome/help-кейсах (массив intents заменяет встроенные).

## [3.0.14] - 2026-06-28

### Исправлено

- **Payload кнопок (Telegram, VK)**: payload нажатой кнопки приходил в обработчик сырой строкой JSON (`'{"command":"buy"}'`). Добавлена утилита `tryParse` (доступна в `pUtils`): строка, начинающаяся с `{`/`[`, автоматически разбирается в объект, всё остальное возвращается как есть. `controller.payload` у Telegram (`callback_query.data`) и VK (`message.payload`, `message_event.payload`) теперь содержит объект — без ручного `JSON.parse` в прикладном коде.
- **Базовые команды**: `welcome`/`help`, явно зарегистрированные через `addCommand`, не вызывались — интент всегда подставлял текст по умолчанию (`welcome_text`/`help_text`). Теперь при наличии зарегистрированной команды выполняется её обработчик.
- **Telegram**: кнопка с `payload` и без `url` уходила пользователю как reply-кнопка без действия. Теперь она отправляется inline-кнопкой с `callback_data`; за reply-кнопками закреплены опции `request_contact`/`request_location`.
- **Документация**: добавлена подробная инструкция по написанию приложений (`GUIDE.md`); ссылки на опубликованную документацию приведены к актуальному формату путей (для корректировки добавлена утилита `scripts/fix-doc.js`).

## [3.0.13] - 2026-06-20

### Добавлено

- Экспорт констант `EMPTY_QUERY_ERROR` и `EMPTY_CONTEXT_ERROR` из `umbot/plugins`. Тексты ошибок пустого запроса/контекста объявлены в `BasePlatform` ещё в 3.0.12, но не были доступны потребителям для типизации и обработки.

## [3.0.12] - 2026-06-12

### Изменено

- **Middleware**: цепочка выполняется до вызова бизнес-логики и загрузки данных пользователя из БД. Остановка в middleware больше не приводит к отправке ответа платформе — обработка запроса прерывается сразу (раньше контроллер при этом выполнялся, и пользователю уходил ответ).
- **Производительность**: оптимизация внутренней логики фреймворка. Поиск активного шага выполняется прямым обращением по ключу вместо перебора всех шагов (с fallback-поиском по интентам запроса); утилита `keysCount` (подсчёт ключей без аллокации массива `Object.keys`) — в горячих местах (`Bot`, `FileAdapter`, `VkRequest`, `MaxRequest`); шаблоны маскирования секретов вынесены в константу уровня модуля; для FormData убрана лишняя сериализация тела запроса.
- **Регулярные выражения**: задержка отложенной компиляции групп уменьшена со 100 мс до 35 мс. Внутренние таймеры (debounce регулярок, ленивое сохранение `FileAdapter`, задержки между запросами к Yandex API) помечены `.unref()` — не задерживают завершение процесса.
- **Алиса**: пустой `request.nlu` не инициализирует NLU; `_initTTS` может работать синхронно (тип `void | Promise<void>`) — лишний промис не создаётся.
- **Типы**: `payload` в интерфейсах Алисы и Маруси уточнён с `object` до `Record<string, unknown> | string`; `bot.getBotController()` помечен `@private` — метод внутренний, в прикладном коде использовать нельзя.
- **Бенчмарки**: новый тест сравнения производительности с «лёгкой реализацией» без универсального каркаса (`npm run cleancomp`).
- **CI**: `release-package.yml` переведён на Node 24 и `setup-node@v6`.

### Исправлено

- **Маруся**: `session.user_id` может прийти числом — идентификатор пользователя теперь приводится к строке, иначе ключи записей БД рассинхронизировались между запросами.

## [3.0.11] - 2026-05-30

Техническая публикация через несколько минут после 3.0.10, без изменений в коде фреймворка (файлы `dist` идентичны 3.0.10). Единственное изменение: в cli-шаблоне `package.json` генерируемых проектов в `keywords` добавлялось имя проекта (`{{name}} project`; в 3.0.12 от этой правки отказались).

## [3.0.10] - 2026-05-30

### Добавлено

- **Health-check**: `GET /health` у вебхук-сервера отвечает `{"status":"ok"}` — для балансировщиков и оркестраторов.
- **CLI**: шаблон `package.json` генерируемых проектов получил команду `update` (`npm update umbot`); Node.js из коробки повышен до 24-й (`@types/node`, `deploy.yml`).

### Изменено

- **Асинхронность адаптеров**: `destroy()` плагинов и БД-адаптеров, `soundProcessing` платформ поддерживают `Promise` — асинхронные операции больше не порождают плавающих промисов, ошибки логируются. `Model.destroy()` возвращает промис закрытия адаптера. Временные TTS-файлы удаляются с `await` (Telegram/Max/VK).
- **rateLimiter**: ошибки обработки очереди перехватываются и логируются; микрооптимизации горячего пути.
- **ESLint**: добавлены новые правила (код фреймворка им соответствует).

### Исправлено

- **Модели картинок/звуков**: `TABLE_NAME` у `ImageTokens`/`SoundTokens` был полем экземпляра, а не статическим полем класса — данные токенов могли не сохраняться.
- **Вебхук-подпись**: `isCorrectQuery` получает исходное тело запроса, а не объект после `JSON.parse` — HMAC-подпись считается по байтам, как их прислала платформа (повторная сериализация объекта меняла формат и «ломала» подпись).
- **VK**: `userCommand` из payload callback-кнопки (`message_event`) приводится к нижнему регистру, как и для текстовых сообщений — команды теперь находятся и по нажатию кнопки.

## [3.0.9] - 2026-05-20

### Исправлено

- Возвращён фикс стабильности из 3.0.6, потерянный при выпуске 3.0.7: доступ к токенам платформы через опциональную цепочку (`BasePlatform.isCorrectQuery`, запросы изображений/звуков Алисы), `init()` гарантирует существование объекта токенов платформы — мультиплатформенный бот без токена одной из платформ больше не падает при инициализации и проверке подписи.

## [3.0.8] - 2026-05-20

### Исправлено

- **Хранилище/шаги диалога (#76)**: сброс активного шага не пробрасывался в состояние сессии — при завершении диалога `oldIntentName` удалялся (`delete`) из `userData`/`state`, но у голосовых платформ отсутствие поля означает «оставить как было», поэтому записанный шаг восстанавливался при повторном входе в навык. Теперь вместо удаления явно записывается `null`.

## [3.0.7] - 2026-05-20

### Добавлено

- **Шаги (#73)**: обработчик шага может вернуть `false` — шаг считается не подлежащим обработке, и запрос идёт дальше по обычной цепочке команд. Актуально, когда пользователь вернулся в диалог, а активный шаг уже неактуален (например, начатая игра давно завершена).

### Изменено

- **CLI**: шаблон `package.json` генерируемых проектов дополнен командами `start:pm2` и `deploy` (pm2).

### Исправлено

- **CLI**: генерируемые приложения по умолчанию слушали только `localhost` — на сервере и в контейнере бот был недоступен с других интерфейсов. Значение по умолчанию изменено на `0.0.0.0`.
- Опечатки в JSDoc и шаблонах.

## [3.0.6] - 2026-05-16

### Исправлено

- **Стабильность (#75)**: обращение к токенам платформы без проверки (`appConfig.tokens[platform].token`) роняло работу адаптера, когда токен одной из платформ не задан — падали и `isCorrectQuery`, и загрузка изображений/звуков Алисы. `BasePlatform.init()` теперь гарантирует объект токенов платформы, доступы переведены на опциональную цепочку.

## [3.0.5] - 2026-05-16

### Исправлено

- **`bot.start()` (#74)**: повторный вызов `start()` выполнял полный `close()` — вычищал плагины и закрывал `AppContext` (включая подключения к БД). Теперь при повторном запуске закрывается только предыдущий HTTP-сервер; плагины и контекст приложения сохраняются.

## [3.0.4] - 2026-04-10

### Исправлено

- **`AppContext` из npm**: в опубликованных типах пакета вместо декларации класса оставались только экспортируемые константы — импорт типов `AppContext` из `umbot` ломал компиляцию TypeScript-проектов потребителей. Декларация класса восстановлена.
- **Request**: объекты в дополнительных полях FormData-запросов сериализуются в JSON вместо `"[object Object]"`.

### Изменено

- **README**: добавлен раздел про единый API с учётом специфики платформ (голосовые — полный функционал, чаты — необходимый набор); рекомендации по `re2` и выбору БД переехали из README в документацию.
- Доработана автодокументация: JSDoc `AppContext`, обзор работы `BotController.run()` и другие описания.

## [3.0.3] - 2026-04-07

### Добавлено

- **Логгер**: опция `maskSecrets: false` в `Bot.setLogger()` — отключение автоматической маскировки секретов в логах. Предназначена только для безопасных окружений (локальная отладка): в production маскировку отключать нельзя.

### Исправлено

- Опечатки и неточности в cli-инструменте и шаблонах генерируемых проектов.

## [3.0.2] - 2026-04-06

### Изменено

- Чтение `.env`-файлов переведено на штатную утилиту работы с файлами (`freadSync`) вместо прямого `fs.readFileSync` — единый формат ошибок чтения.

## [3.0.1] - 2026-04-06

### Изменено

- **CLI / TypeScript 6**: шаблон `tsconfig.json` генерируемых проектов переведён на `module: Node16` и `moduleResolution: Node16` (`rootDir: ./src`) — проекты корректно собираются под TypeScript 6.0. Шаблонный `package.json` хранится как `package.json.text`, чтобы директория шаблонов не считалась вложенным npm-пакетом.
- **Автодокументация**: примеры в JSDoc массово приведены к безопасным URL (`example.com` → `http://localhost`).
- Мелкая оптимизация кода.

## [3.0.0] - 2026-04-05

### Добавлено

- Возможность отправить ответ пользователю программно (`bot.send(userId, controllerOrText, platform)`) — бот инициирует
  диалог сам. Актуально для рассылок. Важно учитывать, что не все платформы поддерживают данный функционал: на
  неподдерживающих вернётся `false`.
- Добавлена возможность добавить шаги (`bot.addStep(name, slots, cb)`). Шаги — механика, основанная на
  `controller.oldIntentName`: если значение соответствует зарегистрированному шагу, выполнится его обработчик.
- Добавлена возможность переопределить ответ сервера. Для этого в 3 аргумент `bot.start()` можно передать свой обработчик
- Добавлена возможность указать свою реализацию для обработки регулярного выражения (плагин `plugins.regExp`).
  Функционал реализуется за счет использования плагинов.
- Добавлена возможность указать кастомный NLU-провайдер (плагин `plugins.nlu`). Функционал реализуется за счет
  использования плагинов.
- Добавлена возможность указать i18n (плагин `plugins.i18n`). Функционал реализуется за счет использования плагинов.
- Добавлена возможность повлиять на обработку регулярных выражений (`commandReg.setCommandGroupMode('auto' | 'group' | 'no-group')`).
  По умолчанию регулярные выражения собираются в группу при количестве команд более 300. Сейчас появилась возможность
  отключить группировку, либо включить группировку принудительно.
- В состав библиотеки добавлена middleware `rateLimiter()`, которая ограничивает количество запросов пользователя к
  платформе. Подключается через `bot.use(rateLimiter())`.
- Добавлена возможность указать кастомный обработчик для определения типа платформы (`bot.setPlatformResolver(fn)`)
- Добавлена механика проверки корректности запроса от платформы (`isCorrectQuery`), а именно проверяется токен
- Добавлена поддержка typescript 6

### Обновлено

- Вся работа с платформами настраивается через адаптеры. Подробнее о переводе на новое
  API смотрите [тут](/src/docs/migration-2x-to-3x.md)
- Работа с базами данных настраивается через адаптеры. Подробнее о переводе на новое API
  смотрите [тут](/src/docs/migration-2x-to-3x.md)
- Поменялось API для основных классов. Подробнее о переводе на новое API смотрите [тут](/src/docs/migration-2x-to-3x.md)
- Для обработчиков команд, добавлена возможность асинхронного выполнения. Критично для сценариев, когда в самой команде
  необходимо выполнить асинхронную операцию.
- Произошел переход на минимальную версию node.js 20
- В Preload добавлена возможность загрузки ресурсов для Telegram. Для этого необходимо указать пользователя, которому
  будут отправлены данные.
- Cli инструменты, согласно новому API
- Доработана логика работы файловой БД, а именно появились предупреждения при достижении определенного размера базы, а
  также принудительное сохранение данных
- Доработана логика работы state и userData. Ранее сохранялось только 1 из значений. Сейчас есть возможность сохранять
  состояние приложения в 2 состояния одновременно.
- В `BotController` удален `store`, так как его логика работы аналогична `state`
- Доработана логика поиска нужной команды. До 3.0, команды обходились последовательно, начиная с 3.0.0 логика поиска
  была оптимизирована. С начала происходит поиск по полному сходству запроса пользователя, с зарегистрированной
  командой, и в случае если команда не была найдена, происходит последовательный обход.
- При добавлении команд с одинаковыми активационными фразами, будет выведена ошибка в лог-файл.
- Удалена зависимость от `shx`, вместо библиотеки используется свое решение по очистке директории.
- Вся работа со звуками, и различными эффектами перенесена в SoundConstants. Ранее, для добавления различные эффектов,
  требовалось обращаться к нужной константе из платформы.
- Оптимизирована логика обработки tts. Сейчас дополнительная обработка tts происходит тогда, когда это действительно
  необходимо.
- В `Buttons` удалены поля `btns` и `links`, вместо них стоит использовать `addBtn` и `addLink`.
- Произведена внутренняя оптимизация логики фреймворка, благодаря чему удалось получить увеличение производительности
  при поиске команд в худшем случае в среднем на 10%. Также улучшена общая производительность обработки команд в среднем
  на 15-22% для текстовых команд и на 10-18% для регулярных выражений. Потребление памяти уменьшилось на 20-25%.
- Поправлена логика работы моделей. Ранее для доступа к состоянию использовался proxy, сейчас данный функционал должен
  реализовывать сам разработчик модели, добавляя set/get, записывая все данные в state.
- Для mongoDB количество одновременных подключений по умолчанию изменено с 1 до 50.
- Удален класс `Button`, вместо него работа происходит с методами `getButton` и `getLinkButton` для получения информации
  о кнопке, и `IButtonType` для типизации. Константы `B_LINK` и `B_BTNS` перенесены в `Buttons`. Также удален класс
  `Image`, вместо него необходимо использовать `getImage` для получения данных для изображения, и `IImageType`, для
  типизации. Подобное решение было принято в целях оптимизации.

### Исправлено

- Ошибки и неточности в документации
- Места с избыточной логикой, за счет чего удалось добиться небольшого повышения производительности
- Ошибка по которой данные загруженные из env могли не применяться.
- Ошибка, когда обработка схожести текста могла отработать некорректно.
- Ошибка, когда мог отправиться некорректный запрос в платформу
- Ошибки в генерации шаблонов через cli
- Доработан вывод для бенчмарка с командами
- Исправлены неточности стресс теста
- Ошибка, когда предыдущий интент(oldIntentName) не сбрасывался при последующих запросах

## [2.2.x] - 2025-11-30

### Добавлено

- Возможность в logger указать метрику (`setLogger({ metric: (name, value, labels) => {...} })`).
- Возможность указать кастомный обработчик команд (`bot.setCustomCommandResolver((userCommand, commands) => {...})`)
- Автоопределение типа приложения на основе запроса
- Метод для задания режима работы приложения `bot.setAppMode('dev' | 'prod' | 'strict_prod')`
- stress test для проверки фреймворка под нагрузкой
- Добавлена поддержка re2 для обработки регулярных выражений (`npm install re2` — фреймворк подхватывает пакет сам).
  Благодаря этому потребление памяти может сократиться, а также время обработки регулярных выражений ускорится
  примерно в 2-6 раз
- Добавлено предупреждение при добавлении большого количества команд

### Обновлено

- Ошибки во время работы приложения записываются как ошибки, а не как обычные логи
- Оптимизирована логика поиска нужного текста
- Поиск опасных регулярных выражений(ReDos) в интентах
- Сохранение логов стало асинхронной операцией
- Произведена оптимизация работы
- Поправлены шаблоны навыков в cli
- Удалены устаревшие методы
- Метод bot.initBotController принимает класс на BotController. Поддержка передачи инстанса осталась, но будет удалена в
  следующих обновлениях
- Удалена возможность указать тип приложения через get параметры.
- Более детальные логи при получении ошибки во время обращения к платформе
- Оптимизирована работа с регулярными выражениями
- Оптимизирована работа с файловой базой данных. Запись происходит асинхронно, и не так часто как ранее. Раньше запись
  происходила на каждое сохранение, сейчас данные из базы хранятся в памяти, и запись происходит через 500мс после
  бездействия.
- Доработан механизм поиска значений в файловой бд, теперь если идет поиск по ключу и данного ключа нет, поиск
  отрабатывает за O(1), а не за O(n), также если поиск идет только по ключу, то поиск также будет составлять O(1)
- Для удобства, константа FALLBACK_COMMAND стала иметь значение "\*", данный подход позволяет просто указать
  `bot.addCommand("\*",[], () => {...})`, чтобы указать команду для действия, когда нужная команда не была найдена
- Повышена производительность
- В `botController`, `isSend` переименован в `skipAutoReply`

### Исправлено

- Архитектурная проблема, из-за которой приложение могло работать некорректно под нагрузкой
- Ошибки с некорректной отправкой запроса к платформе
- Ошибка когда benchmark мог упасть, также доработан вывод результата
- Ошибка когда логи могли не сохраняться
- Ошибка с некорректной записью и чтением результатов из файловой бд
- При завершении работы приложения, сбрасываются все команды и происходит отключение от бд
- Ошибка в benchmark из-за чего он показывал результат лучше, чем есть на самом деле. Особенно на регулярных выражениях
- Ошибка с некорректным сбросом подключения к бд
- Проблема, когда при относительно большой файловой бд(более 10000 записей), время обработки могло сильно просесть.
- Исправлена ошибка, когда запросы не отправлялись в некоторые платформы

## [2.1.0] - 2025-10-19

### Добавлено

- Добавлена поддержка MAX (адаптер `new MaxAdapter(token)`)
- Метод `Text.textReplace(key, value, text)` для корректной замены текста по ключу (значение может быть массивом —
  подставится случайный элемент)
- Добавлено ограничение для кэширования регулярных выражений
- Добавлена возможность задать свою функцию для логирования (`Bot.setLogger(...)`)
- Добавлен класс `Preload`, который позволяет загрузить необходимые ресурсы до запуска навыка
  (`new Preload().loadImages(...)` / `loadSounds(...)`).
- Добавлена возможность указать свой httpClient (`bot.getAppContext().httpClient = ...`).
- Добавлена возможность в slots добавить честное регулярное выражение (`addCommand('cmd', [/привет/i], cb)`)
- Добавлен benchmark для проверки производительности (`npm run bench`)
- В platformParams добавлено поле `empty_text` — этот текст выводится пользователю, если нужная команда не была
  найдена.
- Если в addCommand передать название команды как FALLBACK_COMMAND (`'*'`), то команда будет выполнена в случае, если не
  получится найти команду для обработки
- Добавлена поддержка middleware (`bot.use(...)`)

### Обновлено

- Механика настройки приложения. Происходит отказ от использования глобального `mmApp` в пользу готовых механизмов в
  `Bot.setLogger`
- Методы initConfig и initParams помечены как deprecated. Вместо них стоит использовать setAppConfig и
  setPlatformParams.
- Небольшая оптимизация производительности
- Написано чуть больше тестов, за счет чего поднялся coverage
- Обновился eslint до актуальной версии
- BotController не обязательно задавать, если все можно сделать за счет `bot.addCommand`
- При записи логов в файл, все секреты маскируются

### Исправлено

- Ошибка при указании в tts нескольких звуков
- Ошибки некорректной работы некоторых приложений
- Ошибки в cli
- Исправлена ошибка, когда поиск по регулярному выражению мог возвращать некорректный результат
- Ошибки с некорректным отображением документации

## [2.0.0] - 2025-05-08

### Добавлено

- prettier для стилизации
- Добавлен eslint
- В cli добавлена возможность сгенерировать .env файл
- Добавлено форматирование сгенерированного cli проекта через prettier

### Обновлено

- Удалена поддержка старого подключения через micro
- Работа некоторых компонентов
- Поменялась логика работы модели
- Поменялась utm метка в ссылках по умолчанию. Раньше было Yandex_Alisa стало umBot
- Произведена оптимизация работы некоторых утилит
- Поменялось API для методов работы с файлами
- Добавлена новая логика для регистрации обрабатываем команд. Сейчас помимо добавления команды в mmApp.setParam, можно
  передать ее в mmApp.addCommand(commandName: string, slots: string, cb: (userCommand: string, botController?:
  BotController) => {})
- Для удаления ранее добавленной команды можно вызвать mmApp.removeCommand(commandName: string)

### Исправлено

- Документация некоторых компонентов
- Опечатки в документации
- Ошибки в cli

## [1.1.8] - 2022-10-06

### Добавлено

- Unit тесты на jest
- В консольный скрипт добавлена возможность указывать режим работы приложения.
- Добавлена возможность указывать режим работы для приложения. Режим разработки отличается от релизного тем, что в нем
  выводятся все сообщения об ошибках в консоль.

### Обновлено

- Структура проекта. Некоторые классы и компоненты переехали в другие каталоги
  ** `Text` из components переехал в `utils`
  ** Компоненты отвечающие за обработку и возврат ответа для конкретной платформы приложения переехали в `platforms`
- Найденные в ходе перемещения ошибки
- Метод `nav` в `components:Navigation `переименован в `getPageElements`
- Описание некоторых опций

### Исправлено

- Циклические зависимость
- Документация некоторых компонентов
- При установке через npm некорректно работала подсветка и подсказки
- Удалено все лишнее из `dist`
- Большие методы разбиты на более мелкие
- Поправлена автодокументация

## [1.1.6] - 2022-08-14

### Добавлено

- Проект добавлен в `npm`

### Обновлено

- Немного изменено имя

## [1.1.5] - 2022-08-14

### Исправлено

- Произведен мини рефакторинг проекта
- Недочеты в консольном скрипте

### Добавлено

- Возможность оценить приложение
- Новое состояние на приложении, которое позволяет понять какое действие было выполнено пользователем BotController:
  userEvents
- В BotController удалено состояние isAuthSuccess, место него стоит использовать userEvents.auth.status
- Добавлена проверка на установку логики приложения

### Обновлено

- Название некоторых методов, которые в название содержали нижнее подчеркивание
- Описание некоторых классов и их свойств

## [1.1.4] - 2022-05-01

### Исправлено

- Произведен рефакторинг проекта, лишнее удалено
- Ошибки с типизацией
- Ошибки с производительностью
- Ошибки стабильности

### Добавлено

- Новые интерфейсы
- Новые правила сборки проекта, из-за чего возросла надежность кода
- Новый класс для тестирования приложения. Основной класс запуска приложения, не запускает тестирование.

### Обновлено

- Логика поиска почты. Стало работать быстрее
- Описание некоторых классов и их свойств

## [1.1.3] - 2021-07-17

### Исправлено

- Незначительные ошибки и опечатки
- Произведена оптимизация кода

### Добавлено

- В Text добавлен новый метод isUrl
- Метод run в случае ошибки кидает исключение
- В Request добавлен метод для загрузки файлов на сервер
- run в случае ошибки кидает исключение
- Поддержка локального хранилища в Марусе
- Загрузка изображений и звуков в Марусе

## [1.1.2] - 2021-04-01

### Исправлено

- Ошибка с сохранением данных в SmartApp

## [1.1.1] - 2021-03-30

### Добавлено

- Отслеживание изменений(Changelog)
- Новые Unit тесты
- Консольный скрипт для автоматического создания проекта
- Для карточек добавлена возможность задавать свой шаблон
- Демка, демонстрирующая возможность указания своего обработчика для подключения к бд
- Для Сбер SmartApp была добавлена возможность локального хранения данных(данные сохраняются в бд Сбера)

### Исправлено

- Места, влияющие на производительность
- Интерфейс IModelRes перенесен в интерфейсы
- Поправлена ошибка при работе SmartApp
- Поправлена ошибка с сохранением данных в файл

### Обновлено

- Поменялось логика отображения карточки для SmartApp
- Изменилось имя для проекта для smartApp было smart-app стало smart_app

## [1.1] - 2021-02-23

Более стабильная версия, с исправлением большей части ошибок

### Добавлено

- Возможность задания своего контроллера для подключения в бд.
- Вспомогательный класс, хранящий данные для запросов к базе данных. Который также способен перевести строку в
  корректный для запроса формат

### Обновлено

- Изменена логика работы с базой данных
- При работе с текстом, а именно с поиском вхождения, поменялась логика поиска. Раньше поиск осуществлялся через
  регулярное выражение, в текущей версии через indexOf

### Исправлено

- Все найденные ошибки и замечания

## [1.0] - 2020-12-25

Появление стабильной версии, которую можно использовать в проекте

### Добавлено

- Поддержка Маруси и Сбер SmartApp
- Документация
- Unit тесты

### Исправлено

- Найденные ошибки

## [0.9-beta] - 2020-11-04

Создание бета-версии инструмента

[3.1.0]: https://github.com/max36895/umbot/compare/a89231e...v-3.1.0
[3.0.14]: https://github.com/max36895/umbot/compare/83d4f92...a89231e
[3.0.13]: https://github.com/max36895/umbot/compare/3.0.12...83d4f92
[3.0.12]: https://github.com/max36895/umbot/compare/3.0.11...3.0.12
[3.0.11]: https://github.com/max36895/umbot/compare/150fd6a...3.0.11
[3.0.10]: https://github.com/max36895/umbot/compare/7ac593d...150fd6a
[3.0.9]: https://github.com/max36895/umbot/compare/bebbb6f...7ac593d
[3.0.8]: https://github.com/max36895/umbot/compare/b3f2728...bebbb6f
[3.0.7]: https://github.com/max36895/umbot/compare/c82a196...b3f2728
[3.0.6]: https://github.com/max36895/umbot/compare/c180b80...c82a196
[3.0.5]: https://github.com/max36895/umbot/compare/7f54c02...c180b80
[3.0.4]: https://github.com/max36895/umbot/compare/67ad827...7f54c02
[3.0.3]: https://github.com/max36895/umbot/compare/75d2f08...67ad827
[3.0.2]: https://github.com/max36895/umbot/compare/bd8b55b...75d2f08
[3.0.1]: https://github.com/max36895/umbot/compare/369776c...bd8b55b
[3.0.0]: https://github.com/max36895/umbot/compare/v2.2.x...369776c
[2.2.x]: https://github.com/max36895/umbot/compare/v2.1.0...v2.2.x
[2.1.0]: https://github.com/max36895/umbot/compare/2.0.0...v2.1.0
[2.0.0]: https://github.com/max36895/umbot/compare/1.1.8...2.0.0
[1.1.8]: https://github.com/max36895/umbot/compare/656ce54...1.1.8
[1.1.6]: https://github.com/max36895/umbot/compare/1.1.5...656ce54
[1.1.5]: https://github.com/max36895/umbot/compare/1036441...1.1.5
[1.1.4]: https://github.com/max36895/umbot/compare/8ca7440...1036441
[1.1.3]: https://github.com/max36895/umbot/compare/1.1.2...8ca7440
[1.1.2]: https://github.com/max36895/umbot/compare/fcb3e32...1.1.2
[1.1.1]: https://github.com/max36895/umbot/compare/1.1...fcb3e32
[1.1]: https://github.com/max36895/umbot/compare/1.0...1.1
[1.0]: https://github.com/max36895/umbot/compare/0.9...1.0
[0.9-beta]: https://github.com/max36895/umbot/releases/tag/0.9
