# Задание A11 (Antigravity Pro, этот ПК): интеграция с Hermes и учётные данные ## Дата поступления 2026-08-23 ## База Проверочный HEAD на момент выдачи: **`cdfd9f1`**. ## Ветка `antigravity/hermes-integration` ## Кому Тяжёлая ветка работ: разбор чужого кода, обратная разработка эндпоинтов, проектные решения. Механических правок здесь нет. --- ## Порядок работы с git **В начале — обновить локальную копию:** ``` cd <каталог репозитория>; git fetch origin --prune; git status git checkout main; git pull --ff-only origin main git checkout -b antigravity/hermes-integration ``` Зафиксировать фактический `BASE_SHA` через `git rev-parse --short HEAD`. Не считать `cdfd9f1` актуальным автоматически — параллельно идёт задание A12. **Ветку отправить в `origin` сразу после первого коммита**, не дожидаясь готовности: ``` git push -u origin antigravity/hermes-integration ``` **В конце — обязательный push:** ``` git push origin antigravity/hermes-integration ``` Работа, которой нет в `origin`, для проекта не существует: проверка идёт исполнением, а не по отчёту. --- ## Параллельная работа: строгая граница по файлам Одновременно выполняется **A12** вторым исполнителем. **Ваши файлы:** ``` src/antigravity_provider/hermes_plugin.py src/antigravity_provider/router/router_engine.py src/antigravity_provider/router/codex_oauth.py src/antigravity_provider/router/profile_manager.py src/antigravity_provider/router/quota_collector.py src/antigravity_provider/router/adapters/** src/antigravity_provider/agy_subprocess.py docs/UI_STATE_CONTRACT.md tests/test_integration_*.py (новые файлы) ``` **Не ваши** (зона A12): `router/ui/**`, `router/model_discovery.py`, `router/model_discovery_service.py`, `router/router_config.py`, `installer/**`, `tests/test_ui_*.py`. Понадобился чужой файл — скажите, будет заказан. Молча не трогать: в проекте уже был случай, когда два исполнителя переписали один файл и слияние дало конфликт в двух местах. --- ## Что принято по A9 Проверено исполнением на живой машине владельца — работа хорошая: - **миграция конфигурации работает**: 16 → 22 профиля, `claude` и `grok` получили по три слота, `find_free_slot` возвращает существующие профили по всем пяти провайдерам. Это снимает корень жалобы «при подключении грока ошибка»; - резервная копия `router_profiles.yaml.bak_` создаётся, **десять профилей antigravity не изменены ни в одном поле**, комментарии не потеряны; - квоты `grok` и `opencode-go` честно отдают `None` с причиной вместо правдоподобных чисел; - флаг `/repair` и `/reinstall` задействован, предупреждение CS0219 исчезло; - граница зоны Codex не нарушена, правка плагина `2d62d39` сохранена. **Исправлено ревьюером при слиянии** — переделывать не нужно, но знать полезно: 1. Служба обнаружения моделей была **недостижима**. Создан `model_discovery_service.py`, а интерфейс импортирует `model_discovery`. Импорт обёрнут в `except ImportError`, поэтому расхождение не давало ошибки — выбор моделей просто оставался пустым навсегда. Добавлена согласованная точка входа. 2. Служба отдаёт `discovered_at`, каталог искал `fetched_at`. Сведено. 3. Тест `test_ui_routing_graph.py` закреплял выдуманный список `grok-3`; вы верно убрали литералы, и тест начал падать. Приведён к честному поведению. Урок на будущее: **защитный `except ImportError` прячет несобранную интеграцию.** Когда пишете модуль для чужого потребителя — согласуйте путь и проверьте импорт исполнением, а не глазами. Два числа из отчёта A9, которые не сошлись: «218 passed, 29 skipped» против **307 passed, 2 skipped** на слитом `main`, и «Release Gate 7/7 PASSED» против **FAILED** в моём замере. Двадцать девять пропусков означают, что UI-тесты у вас не выполнялись. Прогон обязателен в окружении **с** `customtkinter`, `pillow`, `psutil`. --- ## P0-1. Hub перехватывает каждый вызов Hermes и назначает ему роль `orchestrator` Самое важное в задании. Владелец сообщил: «зашёл в Гермеса, а там наш хаб не работает, основной оркестратор не выбрался», и заключил, что Hub к Hermes не привязан. Заключение неверное, положение хуже: **Hub привязан и активно ломал Hermes.** Установлено разбором кода Hermes и его журналов: 1. Плагин регистрируется штатно. `hermes_cli/plugins.py:4789` берёт `register` у модуля, корневой `__init__.py` его экспортирует, `ctx.register_middleware("llm_execution", …)` — допустимое имя (`hermes_cli/middleware.py:23`). Перехватчик **срабатывает на каждом обращении к модели**, это видно в трейсбеке `agent.log`. 2. **Hermes не передаёт роль.** `agent/conversation_loop.py:2950` передаёт `task_id`, `turn_id`, `api_request_id`, `session_id`, `platform`, `model`, `provider`, `base_url`, `api_mode`, `api_call_count`. Ключа `role` нет. 3. `resolve_role` доходит до последней строки и возвращает `config.default_role`. **Каждый вызов Hermes идёт как `orchestrator`.** 4. Цепочка `orchestrator` у владельца исчерпана целиком: ``` ag-orch-fallback skipped_unhealthy codex-orch 429: Your account is not active, please check your billing details opengo-3 No API key found for OpenCode Go profile 'opengo-3' ag-w1, ag-w3 Antigravity error: agy error: authentication failed or timed out ``` 5. Роутер возвращал «⚠️ Hermes Router Failover Exhausted» **как ответ ассистента**, и Hermes показывал это вместо ответа модели, хотя его собственный провайдер работал. Следствие устранено ревьюером (`2d62d39`): при `router_error` вызов уходит вниз через `next_call`, отказ пишется в журнал уровнем `warning`. Закрыто тестом `tests/test_plugin_passthrough.py`. **Правку не откатывать.** Принцип, который она закрепляет: **плагин может улучшить маршрутизацию, но не имеет права сделать Hermes хуже, чем без него.** **От вас — причина.** Сейчас Hub на каждом вызове Hermes сначала пробует цепочку `orchestrator`: лишняя задержка и расход квоты не той роли даже там, где пропуск сработал верно. 1. **Определить роль честно.** Разобрать, что из переданного Hermes пригодно как признак: `task_id`, `session_id`, `platform`, `model`, `provider`. Надёжного признака нет — **не угадывать**. Эвристика в `resolve_role`, ищущая в системном сообщении подстроки «developer», «coding agent», «review agent», — это гадание по тексту промпта, и оно тоже подлежит пересмотру. 2. **Не претендовать на вызов без роли.** Роль не определена достоверно — пропускать вниз сразу, не тратя попыток. Роль по умолчанию для внешнего перехвата — неверная модель поведения. **Тесты:** вызов без определяемой роли уходит вниз, не тратя попыток роутера; вызов с определённой ролью маршрутизируется; отказ цепочки никогда не возвращается как ответ ассистента. ## P0-2. Граница между учётными системами Hub и Hermes Владелец прав по существу: Hub профилями Hermes не управляет. Hermes ведёт собственные профили в каталоге `profiles` своего домашнего каталога: ``` agy-01 … agy-06, worker-fast, worker-research, worker-review, worker-code, worker-code-2, deepseek ``` и настраивает `delegate_task` отдельно (`max_concurrent_children=3`, `provider=opencode-go`, `model=kimi-k2.7-code`). Профили Hub — `ag-w1`, `ag-orch-fallback`, `codex-orch`, `opengo-*` — **другое множество идентификаторов**. Один и тот же аккаунт Google живёт в двух учётных системах под разными именами. **Требуется:** 1. Раздел в `docs/UI_STATE_CONTRACT.md`: что Hub видит от Hermes, чего не видит, чем управляет и чем не управляет. Без этого интерфейс, показывающий «команду агентов», вводит владельца в заблуждение — он видит роли, которых Hermes не спрашивает. 2. **Варианты связывания профилей с оценкой цены каждого:** сопоставление по идентичности аккаунта (email из `id_token`), чтение профилей Hermes как источника, либо явная таблица соответствия. **Решение принимает владелец — вам подготовить варианты, не реализовывать молча.** ## P0-3. Codex: обновление токена и безопасное переключение аккаунта Владелец прислал, как это делает Cockpit Tools, и просит так же: ``` 1. Прочитать данные аккаунта access_token · id_token · refresh_token 2. Проверить access_token действителен до 28.08, обновление не требуется 3. Проверить id_token истёк 5 дней назад — нужно обновление 4. Обновить данные входа полный набор обновлён и сохранён 5. Остановить прежний процесс безопасная остановка ChatGPT/Codex и app-server 6. Записать данные клиента 7. Синхронизировать настройки 8. Запустить клиент Codex ``` Ключевое: токены проверяются **по отдельности**, и клиент останавливается **до** подмены учётных данных. В отчёте A9 упомянуты токены Codex, но проверить это исполнением не удалось: у профилей Codex на машине владельца нет авторизации. Поэтому пункт остаётся открытым и должен быть закрыт **тестами**, а не только кодом: 1. Обновление токена по `refresh_token` с сохранением полного набора и понятной ошибкой, когда `refresh_token` отсутствует или отвергнут. 2. Раздельная проверка срока `access_token` и `id_token` с запасом по времени; в статусе профиля видно, что именно просрочено. 3. Переключение аккаунта как наблюдаемая последовательность: остановка клиента → запись учётных данных → синхронизация → запуск. Каждый шаг сообщает о себе, чтобы интерфейс (зона A12) показал прогресс. 4. Сбой на любом шаге не оставляет промежуточного состояния: либо переключено полностью, либо возврат к прежнему. **Тесты:** просроченный `access_token` при живом `refresh_token` обновляется без повторного входа; отсутствие `refresh_token` даёт понятную ошибку, а не молчаливый провал; прерывание на середине не оставляет смешанных учётных данных. ## P0-4. Обнаружение моделей Antigravity устроено неверно Служба кэширования из A9 работает, но зонд для Antigravity не возвращает ничего. Проверено: ``` discover_models failed: agy.exe -p x --model __invalid_probe__ ... timed out after 20 seconds обнаружено моделей: 0 ``` `agy_subprocess.py:122` запускает `agy` с **заведомо неверной моделью** и разбирает текст ошибки, надеясь выудить из неё список. Это отказало: команда просто виснет на 20 секунд. При этом у CLI есть штатная команда. Проверено вручную — `agy models` возвращает четырнадцать настоящих моделей, разделитель табуляция: ``` gemini-3.7-flash-high Gemini 3.7 Flash (High) gemini-3.1-pro-high Gemini 3.1 Pro (High) claude-sonnet-4-6 Claude Sonnet 4.6 (Thinking) gpt-oss-120b-medium GPT-OSS 120B (Medium) ``` **Требуется** заменить зонд на `agy models` с разбором табулированного вывода. Осторожно с таймаутом: **та же команда в одном прогоне отвечает за 40 секунд, а в следующем висит больше двух минут.** Это замерено, не предположение. Жёсткий таймаут обязателен, при его срабатывании прежний кэш сохраняется. Заодно убрать выдуманный запасной список в `antigravity_adapter.py:144`: ```python return list(profile.preferred_models or ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-thinking"]) ``` Моделей `gemini-2.5-*` у провайдера **не существует** — настоящие начинаются с `gemini-3`. Это тот же класс дефекта, из-за которого в конфигурацию владельца попал несуществующий `gemini-3.7-flash`, стоящий сейчас у роли `orchestrator` как `default_model`. Нет обнаруженного списка — возвращать пустой. **Тест:** зонд возвращает непустой список на подготовленном выводе `agy models`; при таймауте прежний кэш не затирается; запасного литерала в адаптере нет. ## P1-5. Квоты: `source` не должен опережать данные Мелочь, но она из того же семейства, с которым боремся весь проект: ``` opencode-go:opengo-1 source=provider_api Общий 5 часов / Недельный / Месячный: remaining=None ``` `source="provider_api"` заявляет измерение, которого не было — все корзины пусты. Либо `source="baseline"`, когда чисел нет, либо отдельное поле, различающее «провайдер ответил, но лимитов не даёт» и «провайдер не ответил». Причина уже пишется правильно, расходится только `source`. --- ## Что принято и переделке не подлежит Не откатывать при слиянии: - живые квоты Antigravity (`retrieveUserQuotaSummary` с обновлением токена при 401): шесть аккаунтов владельца отдают разные измеренные числа; - правка плагина `2d62d39` и тест `tests/test_plugin_passthrough.py`; - `_finish` мастера: `destroy()` выполняется всегда; - исправление `is_expired` в `do_test_profile`; - миграция конфигурации из A9 и точка входа `model_discovery.py`. ## Ограничения - Строгая граница по файлам — см. выше. - Никаких чисел, идентификаторов и названий моделей без измерения. - Сеть и подпроцессы — не в UI-потоке. - Тег `v0.1.1` не создавать. ## Критерии приёмки 1. Ветка в `origin`. Ни один файл зоны A12 не изменён. 2. Вызов без достоверно определённой роли уходит вниз, не тратя попыток роутера; отказ цепочки никогда не возвращается как ответ ассистента; правка `2d62d39` сохранена. 3. Граница между учётными системами Hub и Hermes описана в контракте; варианты связывания профилей поданы с ценой каждого, без односторонней реализации. 4. Токен Codex обновляется по `refresh_token`; переключение останавливает клиент до подмены учётных данных и не оставляет промежуточного состояния; закрыто тестами. 5. `agy models` используется как зонд; обнаружение возвращает непустой список; при таймауте кэш сохраняется; выдуманного запасного списка в адаптере нет. 6. `source` квоты не заявляет измерения там, где чисел нет. 7. Прогон **в окружении с UI-зависимостями**; число пропусков объяснено. На `main` набор даёт 307 passed, 2 skipped. 8. `ruff check .` чисто. Релизный гейт: назвать состояние до и после, объяснить расхождение с замером ревьюера; ухудшать нельзя. 9. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`. ## Порядок сдачи Передать точный `FINAL_COMMIT_SHA`. **Сдано только после появления ветки в `origin`.**