hermes-hub/agents/inbox/2026-08-23-A11-antigravity-pro-integration.md
Hermes Team 78cd826624 docs(tasks): A11 и A12 — разделение работ между двумя исполнителями
A11 (Pro, тяжёлая ветка): интеграция с Hermes — перехват без роли,
граница между учётными системами Hub и Hermes, токены Codex и безопасное
переключение аккаунта, замена зонда обнаружения моделей на agy models,
устранение выдуманного запасного списка в адаптере.

A12 (Flash, быстрая ветка): компактные карточки аккаунтов, разбор
дублирующих разделов «Провайдеры»/«Квоты», причина у каждого Н/Д, тест
на достижимость службы обнаружения моделей, живое подключение Grok.

Заданы строгие непересекающиеся списки файлов, чтобы параллельная работа
не дала конфликтов. В A12 порядок работы с git расписан пошагово: pull в
начале, push ветки сразу после первого коммита, обязательная проверка
финального push через git log origin/<ветка>.

A10 удалено — заменено этой парой.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:00:36 +07:00

21 KiB
Raw Blame History

Задание 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_<ts> создаётся, десять профилей 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
  1. Роутер возвращал «⚠️ 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:

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.