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>
21 KiB
Задание 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сохранена.
Исправлено ревьюером при слиянии — переделывать не нужно, но знать полезно:
- Служба обнаружения моделей была недостижима. Создан
model_discovery_service.py, а интерфейс импортируетmodel_discovery. Импорт обёрнут вexcept ImportError, поэтому расхождение не давало ошибки — выбор моделей просто оставался пустым навсегда. Добавлена согласованная точка входа. - Служба отдаёт
discovered_at, каталог искалfetched_at. Сведено. - Тест
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 и его журналов:
-
Плагин регистрируется штатно.
hermes_cli/plugins.py:4789берётregisterу модуля, корневой__init__.pyего экспортирует,ctx.register_middleware("llm_execution", …)— допустимое имя (hermes_cli/middleware.py:23). Перехватчик срабатывает на каждом обращении к модели, это видно в трейсбекеagent.log. -
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нет. -
resolve_roleдоходит до последней строки и возвращаетconfig.default_role. Каждый вызов Hermes идёт какorchestrator. -
Цепочка
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
- Роутер возвращал «⚠️ Hermes Router Failover Exhausted» как ответ ассистента, и Hermes показывал это вместо ответа модели, хотя его собственный провайдер работал.
Следствие устранено ревьюером (2d62d39): при router_error вызов уходит вниз через next_call, отказ пишется в журнал уровнем warning. Закрыто тестом tests/test_plugin_passthrough.py. Правку не откатывать.
Принцип, который она закрепляет: плагин может улучшить маршрутизацию, но не имеет права сделать Hermes хуже, чем без него.
От вас — причина. Сейчас Hub на каждом вызове Hermes сначала пробует цепочку orchestrator: лишняя задержка и расход квоты не той роли даже там, где пропуск сработал верно.
-
Определить роль честно. Разобрать, что из переданного Hermes пригодно как признак:
task_id,session_id,platform,model,provider. Надёжного признака нет — не угадывать. Эвристика вresolve_role, ищущая в системном сообщении подстроки «developer», «coding agent», «review agent», — это гадание по тексту промпта, и оно тоже подлежит пересмотру. -
Не претендовать на вызов без роли. Роль не определена достоверно — пропускать вниз сразу, не тратя попыток. Роль по умолчанию для внешнего перехвата — неверная модель поведения.
Тесты: вызов без определяемой роли уходит вниз, не тратя попыток роутера; вызов с определённой ролью маршрутизируется; отказ цепочки никогда не возвращается как ответ ассистента.
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 живёт в двух учётных системах под разными именами.
Требуется:
-
Раздел в
docs/UI_STATE_CONTRACT.md: что Hub видит от Hermes, чего не видит, чем управляет и чем не управляет. Без этого интерфейс, показывающий «команду агентов», вводит владельца в заблуждение — он видит роли, которых Hermes не спрашивает. -
Варианты связывания профилей с оценкой цены каждого: сопоставление по идентичности аккаунта (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 на машине владельца нет авторизации. Поэтому пункт остаётся открытым и должен быть закрыт тестами, а не только кодом:
- Обновление токена по
refresh_tokenс сохранением полного набора и понятной ошибкой, когдаrefresh_tokenотсутствует или отвергнут. - Раздельная проверка срока
access_tokenиid_tokenс запасом по времени; в статусе профиля видно, что именно просрочено. - Переключение аккаунта как наблюдаемая последовательность: остановка клиента → запись учётных данных → синхронизация → запуск. Каждый шаг сообщает о себе, чтобы интерфейс (зона A12) показал прогресс.
- Сбой на любом шаге не оставляет промежуточного состояния: либо переключено полностью, либо возврат к прежнему.
Тесты: просроченный 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не создавать.
Критерии приёмки
- Ветка в
origin. Ни один файл зоны A12 не изменён. - Вызов без достоверно определённой роли уходит вниз, не тратя попыток роутера; отказ цепочки никогда не возвращается как ответ ассистента; правка
2d62d39сохранена. - Граница между учётными системами Hub и Hermes описана в контракте; варианты связывания профилей поданы с ценой каждого, без односторонней реализации.
- Токен Codex обновляется по
refresh_token; переключение останавливает клиент до подмены учётных данных и не оставляет промежуточного состояния; закрыто тестами. agy modelsиспользуется как зонд; обнаружение возвращает непустой список; при таймауте кэш сохраняется; выдуманного запасного списка в адаптере нет.sourceквоты не заявляет измерения там, где чисел нет.- Прогон в окружении с UI-зависимостями; число пропусков объяснено. На
mainнабор даёт 307 passed, 2 skipped. ruff check .чисто. Релизный гейт: назвать состояние до и после, объяснить расхождение с замером ревьюера; ухудшать нельзя.- Отчёт:
START_HEAD,FINAL_HEAD,origin/main,git status, точныйX passed / Y skipped / Z failed.
Порядок сдачи
Передать точный FINAL_COMMIT_SHA. Сдано только после появления ветки в origin.