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

252 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Задание 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
```
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`.**