Metadata-Version: 2.5
Name: hermes-vk-community
Version: 1.1.4
Summary: VK Community Long Poll platform plugin for Hermes Agent
Author: Hermes VK Community contributors
License: MIT
Requires-Python: >=3.11
Requires-Dist: aiohttp>=3.14.5
Requires-Dist: aiosqlite==0.22.1
Requires-Dist: filetype==1.2.0
Requires-Dist: hermes-agent>=0
Requires-Dist: markdown-it-py<5,>=4.2.0
Requires-Dist: packaging<27,>=26.0
Requires-Dist: pillow<14,>=12.2
Requires-Dist: pydantic-settings<3,>=2.15.0
Requires-Dist: pydantic<3,>=2.13.4
Requires-Dist: tenacity==9.1.4
Description-Content-Type: text/markdown

# Hermes VK Community

Плагин платформы [Hermes Agent](https://github.com/NousResearch/hermes-agent),
который подключает личного агента к сообщениям бота сообщества VK через
Community Long Poll API.

Голосовые сообщения загружаются в стандартный STT pipeline Hermes. Агент
получает готовую транскрипцию, но не получает вдобавок путь к исходному
аудиофайлу, поэтому не пытается распознавать уже обработанное голосовое повторно.

Минимальная поддерживаемая версия Hermes: `>=0.18.2`. Совместимость с новыми версиями проверяется по API-контракту при загрузке плагина.

Рабочее окружение и основной CI используют Python 3.14 и фиксированный Git SHA
актуального Hermes. У Git-установок Hermes metadata-версия может быть `0.0.0`;
в этом случае плагин проверяет совместимость по API-контракту. Старые релизы
Hermes `0.18.2`, `0.19.0` и `0.21.5` проверяются отдельно на Python 3.11/3.13.

Широкая package-зависимость `hermes-agent>=0` допускает Git-host с metadata
`0.0.0`. Compatibility gate отклоняет версионированные релизы ниже `0.18.2`
и несовместимые API до регистрации платформы.

Supply-chain gate проверяет все PyPI-зависимости lock-файла без исключений
advisory. Сам Git checkout Hermes не сопоставляется с релизом PyPI. Только в
окружении разработки обновлены upstream-пины PyJWT, Pydantic, packaging,
certifi и Pillow (до исправленных `2026.10.19` и `13.0.0` соответственно);
установка плагина не меняет политику зависимостей рабочего Hermes.
Плагин допускает Pillow 12 для совместимости с жёсткими пинами старых Hermes;
проверяемое на уязвимости окружение использует Pillow 13 через workspace override.

## Быстрая настройка

Установить плагин можно из PyPI:

```bash
pip install hermes-vk-community
hermes plugins enable vk-community
```

Либо напрямую из подкаталога GitHub-репозитория:

```bash
hermes plugins install shkarupa-alex/hermes-plugins/packages/hermes-vk-community --enable
```

Во втором варианте Hermes клонирует только каталог плагина. При первой
фактической загрузке плагин проверяет зависимости из `pyproject.toml` и при
необходимости устанавливает их через `uv` в то же виртуальное окружение, где
работает Hermes. Повторная загрузка ничего не переустанавливает. Автоустановку
можно запретить штатной настройкой Hermes `security.allow_lazy_installs: false`
или переменной `HERMES_DISABLE_LAZY_INSTALLS=1`; тогда сообщение об ошибке
покажет точную команду для ручной установки зависимостей.

После установки запустите мастер:

```bash
hermes gateway setup
```

В списке платформ выберите **VK Community**. Тот же мастер можно запустить
напрямую:

```bash
hermes vk setup
```

Мастер:

1. расскажет, какие настройки включить в сообществе и какие права выдать ключу;
2. примет ключ без отображения введённых символов;
3. примет обычные ссылки на сообщество и профили пользователей;
4. сам получит числовые VK ID;
5. с подтверждением сделает сообщество приватным и включит сообщения;
6. включит Community Long Poll `5.199` и событие входящего сообщения;
7. проверит ключ, доступ к сообществу, пользователей и итоговые настройки;
8. сохранит секрет и конфигурацию в активный профиль Hermes.

Если проверка не пройдёт, мастер не сохраняет локальную конфигурацию. Уже
подтверждённые изменения сообщества могут быть применены VK до последующей
ошибки проверки; повторный запуск безопасен, потому что настройки идемпотентны.

## Создание сообщества и ключа

Подойдёт в том числе закрытое сообщество, в котором состоите только вы.

1. [Создайте сообщество VK](https://vk.com/groups) или откройте его управление.
2. Включите **Сообщения сообщества**.
3. Настройки Long Poll можно оставить мастеру. При ручной настройке откройте
   **Управление → Настройки → Работа с API → Long Poll API**, включите Long
   Poll, выберите API `5.199` и отметьте только событие входящего сообщения
   (`message_new`). Другие типы событий текущему адаптеру не нужны.
4. Там же откройте **Ключи доступа** и создайте ключ сообщества.
5. Выберите права:
   - управление сообществом;
   - сообщения сообщества;
   - фотографии сообщества;
   - документы сообщества.

Права на истории, стену, товары и заказы плагину не нужны. Права на фотографии
и документы используются реальными upload-flow: фото, обычные документы и
голосовые сообщения/TTS отправляются как нативные вложения VK. Для голоса
плагин преобразует исходное аудио в OGG/Opus через `ffmpeg`; если VK отклонит
voice-flow, безопасный fallback отправляет ZIP-документ без потери исходных байт.

Официальная отправная точка: [боты VK — начало работы](https://dev.vk.com/ru/api/bots/getting-started).

## Где взять ID

Вручную извлекать числовые ID не обязательно: мастер принимает ссылки.

- `https://vk.com/club240186772` → ID сообщества `240186772`;
- `https://vk.com/id7750207` → ID пользователя `7750207`;
- `https://vk.com/shkarupa.alex` → мастер запросит числовой ID через VK API;
- короткая ссылка сообщества с собственным именем тоже разрешается через API.

Можно указать несколько разрешённых профилей через запятую. Пустой список
запрещён: доступ к личному агенту закрыт по умолчанию.

Мастер всегда спрашивает этот список при первой полной настройке. Если токен
уже сохранился после прерванного запуска, а `platforms.vk` отсутствует или
неполон, следующий `hermes vk setup` не примет наличие токена за готовую
конфигурацию: он продолжит восстановление и снова запросит сообщество и
разрешённые профили.

## Где хранится ключ

Как и Telegram-токен в Hermes, ключ VK хранится под именем
`VK_COMMUNITY_TOKEN` в `.env` **активного профиля Hermes**, а не в YAML:

- профиль по умолчанию: обычно `~/.hermes/.env`;
- именованный профиль: его собственный каталог профиля и собственный `.env`.

Hermes устанавливает секретный scope конкретного профиля перед запуском
адаптера. Плагин читает ключ только через этот scope, поэтому при нескольких
профилях ключ одного профиля не подменит ключ другого.

Несекретные значения мастер записывает в `config.yaml` активного профиля:

```yaml
platforms:
  vk:
    enabled: true
    group_id: 240186772
    allowed_user_ids:
      - 7750207
    typing_indicator: true
```

Ключ, помещённый в `.env` этого репозитория, предназначен только для локальных
тестов разработчика. Он не переносится в профиль Hermes автоматически. При
обычной установке вводите ключ в мастере — тот использует штатную атомарную
запись секретов Hermes.

## Разработка без установленного пользователем Hermes

Глобально или в пользовательскую установку Hermes ничего ставить не требуется.
`uv` создаёт изолированное окружение проекта и устанавливает совместимую версию
Hermes как зависимость плагина:

```bash
python3 tools/sync_workspace.py
uv run --no-sync pytest packages/hermes-vk-community/tests
uv run --no-sync ruff check packages/hermes-vk-community
uv run --no-sync pyright packages/hermes-vk-community
```

То есть тесты можно запускать до установки Hermes пользователем. Полностью без
кода Hermes проверить платформенный плагин нельзя: его контракт содержит классы
адаптеров, реестр платформ и профильное хранилище секретов. Изолированная `.venv`
решает это без изменения рабочей установки пользователя.

Для реальной end-to-end проверки всё равно нужны сеть, сообщество и тестовое
сообщение. Мастер уже выполняет безопасную часть live-проверки: API, права,
разрешение ID и получение Long Poll server.

## Ручная настройка

Если мастер использовать нельзя, добавьте `VK_COMMUNITY_TOKEN` в `.env`
активного профиля и внесите показанный выше блок `platforms.vk` в его
`config.yaml`. Не помещайте токен в YAML: валидатор плагина намеренно отклоняет
такую конфигурацию.

После изменения перезапустите gateway и напишите со своего разрешённого профиля
в сообщения сообщества:

```bash
hermes gateway restart
```

Hermes передаёт адаптеру Markdown. Live-проверенный профиль API `5.199`
использует Unicode codepoint offsets и компилирует
`bold`/`italic`/`underline`/`url` в `format_data`; заголовки становятся жирными. Цитаты
передаются как `▎` плюс курсив, списки — как Unicode-маркеры с отступами.
Markdown-таблицы всегда рендерятся в JPEG и отправляются фотографиями. Поэтому
смешанный ответ может стать упорядоченной серией сообщений: текст, таблица,
следующий текст. Отдельная Markdown-картинка аналогично становится фото между
текстовыми сообщениями. HTML и исходный Markdown в VK отправлять нельзя — клиент
показывает их буквально.

```bash
hermes vk probe-formatting --peer-id <PRIVATE_TEST_PEER_ID> \
  --output vk-formatting-format-data-probe.json
```

`auto` использует проверенный rich-профиль; явный `rich` доступен для API
`5.199`, а `plain` оставлен как детерминированный fallback. Unknown profile/API
по-прежнему переключает `auto` в plain и отклоняет явный rich. Probe проверяет
send/edit, Unicode offsets и readback, после чего best-effort удаляет тестовые
сообщения. Подробности и текущий проверенный профиль записаны в
[`docs/vk-rich-text-compatibility.md`](docs/vk-rich-text-compatibility.md).

Статус «печатает» отправляется через `messages.setActivity`, если
`typing_indicator: true`; live-тест подтвердил и ответ API, и отображение в
клиенте VK.

Release live-матрица дополнительно проверяет DM reply на реальное входящее
сообщение, pinned media download, `format_data`, inline keyboard, photo upload
и восстановление подготовленного outbox после закрытия/повторного открытия
SQLite. Для запуска нужны только `VK_COMMUNITY_TOKEN`, `VK_GROUP_ID` и
`VK_TEST_PEER_ID`; тестовый пользователь должен предварительно прислать фото.

## Интерактивность, длинные ответы и pairing

Clarify-вопросы, подтверждения опасных команд и slash-confirm показываются как
одноразовые VK-клавиатуры. Payload — случайный opaque nonce, связанный в памяти
с разрешённым user ID, peer ID, сессией, допустимым действием и сроком жизни.
Повторный, просроченный или скопированный payload отклоняется до Hermes.

Длинные ответы и финализация streaming-preview поддерживают продолжения.
Доставленные части не отправляются повторно после timeout/частичной ошибки;
outbox сохраняет `random_id` и wire payload. После рестарта безопасные
`prepared`-строки досылаются, а неоднозначные `sending` переводятся в
`delivery_unknown` и доступны через `hermes vk doctor --delivery-unknown`.
Ошибка VK `914` уменьшает рабочий лимит вплоть до 256 символов и кеширует его.

Опциональный pairing включается в YAML:

```yaml
platforms:
  vk:
    pairing:
      enabled: true
      code_ttl_seconds: 600
```

Создайте одноразовый код командой `hermes vk pair` и отправьте его боту только
текстом. В SQLite хранится SHA-256, а не сам код; вложения, forwards и payload
от непривязанного пользователя не загружаются.

## Диагностика

`hermes vk doctor` проверяет discovery/enabled, secret scope, конфликты политики,
identity сообщества, Community Long Poll, SQLite migration/schema, platform
lock, formatting profile и media flow. Флаги `--inflight` и
`--delivery-unknown` выводят только bounded идентификаторы и ошибки без текста
сообщений и секретов.


## Отчёты cron в VK

VK зарегистрирован как штатная платформа доставки cron. В списке адресатов
появляется `vk`, если плагин включён, а токен и `platforms.vk` настроены в
активном профиле. Доставка cron требует актуальный Git-host Hermes с контрактом
безопасной частичной доставки. В Hermes `0.18.2`, `0.19.0` и `0.21.5` плагин
поддерживает обычный чат, но доставка cron в VK не поддерживается и не
регистрируется: старый host может повторить отчёт после потери ответа VK.
Не используйте в нём вручную заданные `vk:ID` или VK `origin` для cron.
Для конкретного адресата на поддерживаемом host задайте `deliver="vk:456"`, где
`456` — числовой ID разрешённого пользователя из `allowed_user_ids` или
пользователя, авторизованного через pairing при включённом `pairing.enabled`.

Чтобы использовать короткое `deliver="vk"`, отправьте боту `/sethome` в нужном
личном диалоге или сохраните в `config.yaml`:

```yaml
platforms:
  vk:
    enabled: true
    group_id: 123
    allowed_user_ids: [456]
    home_channel:
      platform: vk
      chat_id: "456"
      name: "Отчёты VK"
```

Hermes также принимает `VK_HOME_CHANNEL=456` в `.env` этого профиля;
переменная переопределяет адресата из YAML. `deliver="origin"` возвращает
отчёт в исходный диалог VK, а `deliver="all"` включает настроенный домашний
диалог VK. Бот должен иметь право писать пользователю; пользователь сначала
открывает личный диалог с сообществом. Темы и групповые беседы не поддерживаются.

Доставка использует тот же Markdown-renderer, разбиение сообщений, отправку
вложений и durable outbox, что и gateway. Hermes передаёт полный отчёт адаптеру:
нарезка внутри адаптера сохраняет общий результат и ID всех доставленных частей,
в том числе для отчётов длиннее 4096 символов. YAML-allowlist и активные pairing-grants
проверяются при отправке и через работающий gateway, и из отдельного cron-процесса. Отдельный
sender не запускает второй Long Poll receiver. Токен берётся из secret scope
активного профиля.

Если часть текста уже отправлена, а дальнейшая доставка или вложение не удались,
standalone sender сохраняет ID доставленных сообщений и возвращает `warnings`.
Hermes отмечает задачу ошибкой, но не ставит уже видимый текст в очередь на
повтор после переподключения. Неопределённый результат запроса тоже возвращается
с предупреждением и не вызывает отправку всего отчёта заново.
Подтверждённые ID сохраняются и при ошибках записи outbox: недоступная
диагностика SQLite логируется, а cron получает результат частичной доставки
и не запускает fallback с новой отправкой уже доставленного префикса.

При отмене подготовки outbox storage пытается завершить cleanup не более трёх раз.
Ожидание SQLite writer lock с обычным `busy_timeout=5000` занимает до примерно
15 секунд; затем ошибка записывается в лог, соединение откатывается, lock
хранилища освобождается, а исходный `CancelledError` сохраняется. Если другой
процесс продолжает держать базу на запись, outbox остаётся без изменений:
закоммиченные `prepared`-строки могут восстановиться после снятия блокировки
или рестарта. Это компромисс для завершения остановки gateway при длительной
внешней блокировке базы. Recovery повторно проверяет актуальный allowlist и
pairing перед каждым чанком; очередь отозванного адресата завершается как
`failed`, без отправки, а восстановление продолжается для разрешённых адресатов.

После подготовки отменённая отправка дожидается терминализации outbox даже при
повторном `cancel()`, в том числе при ожидании writer в том же storage.
Это ожидание ограничено 15 секундами; уже запущенный SQLite-запрос сначала
завершает rollback. Если deadline истёк, ошибка логируется, исходная отмена
сохраняется, а не изменённые строки могут восстановиться по описанной выше политике.
При перенарезке cleanup проверяет владение записью атомарно: отмена старого
sender не завершает чужие replacement-чанки, но завершает собственные даже
после commit. Это правило действует и при startup recovery.

Если явно включены `attach_to_session` у cron-задачи или `cron.mirror_delivery`,
Hermes сохраняет в историю исходный текст отчёта даже при `warnings` о частичной
доставке. Эта запись не подтверждает доставку всех частей; проверяйте статус
задачи и предупреждения. Зеркалирование cron по умолчанию выключено, а контракт
standalone sender не позволяет плагину заменить зеркалируемый текст.

В актуальном Hermes отдельный cron-процесс отправляет вложения в VK только
вместе с непустым текстом. Если `cron.wrap_response: false`, добавьте к
`MEDIA:/path/to/report.csv` текст отчёта: результат только из `MEDIA:` Hermes
отклоняет до вызова плагина. При штатной обёртке отчёта текст добавляет Hermes.
