hermes-hub/agents/inbox/2026-08-23-A9-antigravity-quotas-models-migration.md
Hermes Team 6494c5198a docs(tasks): A9 и B8 по итогам живого прогона владельца
A9 (Antigravity): миграция профилей в существующий router_profiles.yaml —
корень жалобы «при подключении грока ошибка»; квоты для codex и opencode;
служба обнаружения моделей с кэшем и фоновым обновлением; отказ от
выдуманных списков моделей. Плюс четыре утверждения отчёта A8, не
подтвердившиеся проверкой: профили claude/grok до пользователя не дошли,
проверка is_expired была мертва, dist в .gitignore, флаг /reinstall
не используется.

B8 (Codex): выбор модели агента, компактные карточки аккаунтов без
раскрытия, кликабельная карточка целиком, окно настроек роли, снятие
правой панели, устранение жёстких срезов диаграммы, решение по
дублирующим разделам, причина у каждого Н/Д.

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

223 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.

# Задание A9 (Antigravity): миграция конфигурации, квоты остальных провайдеров, реальные модели
## Дата поступления
2026-08-23
## База
Проверочный HEAD на момент выдачи: **`b625b0f`**. Обязательно обновить локальную копию — см. следующий раздел.
## Ветка
`antigravity/quotas-models-migration`
---
## Перед началом: обновить локальную копию
Вы работаете на другой машине и пушите прямо в git. `main` ушёл далеко вперёд вашей базы: в него влиты и A8, и работа Codex (граф маршрутизации, живые квоты, дефекты живого прогона), и правки ревьюера.
```
cd <каталог репозитория>; git fetch origin --prune; git status
```
Если рабочее дерево чистое:
```
git checkout main; git reset --hard origin/main
```
Зафиксировать фактический `BASE_SHA` через `git rev-parse --short HEAD` и указать его в отчёте. Не считать `b625b0f` актуальным автоматически.
---
## Что принято по A8
Проверено исполнением:
- **Самолечение запуска работает.** `launcher_bootstrap` импортируется, `check_missing_dependencies()` на чистой машине возвращает `[]`, все пять функций на месте.
- **`find_free_slot` больше не выдумывает идентификаторы.** Проверено по всем пяти провайдерам мастера: возвращается либо существующий профиль, либо `None`. Это было главным дефектом A8 и он закрыт.
- **Экран переустановки в мастере есть** и подключён к `SetupEngine.IsInstalled` (`HermesHubSetup.cs:714`), с кнопкой «Переустановить» и показом версий.
- **Зеркальное развёртывание реализовано**: `MirrorDirectoryRecursive` заменил копирование. Проверено на живой машине владельца — при зеркалировании удалились девять устаревших файлов, включая четыре мёртвых модуля, которые мы удаляли из репозитория ещё в прошлых раундах.
- Тесты `test_deployment_doctor.py` проходят, ruff чисто.
Работа хорошая. Но в отчёте три утверждения, которые проверку не прошли, — читайте следующий раздел, это важнее похвалы.
---
## P0-0. Три утверждения отчёта A8, не подтвердившиеся проверкой
Это не придирки к формулировкам. Каждое из трёх означает, что заявленная функция у владельца не работает.
### 1. Профили Claude и Grok до пользователя не дошли
Отчёт: «добавлены по 3 профиля… всего 22 профиля».
Факт на живой машине владельца:
```
профилей в конфиге: 16
antigravity 10
openai-codex 3
opencode-go 3
claude 0
grok 0
find_free_slot(grok) -> None
find_free_slot(claude) -> None
```
Профили добавлены во **встроенные умолчания** (`router_config.py`) и в **пример** (`router_profiles.example.yaml`). Но `load_router_config()` возвращает умолчания **только если файла нет** (`router_config.py:324`). У владельца файл есть — `%LOCALAPPDATA%\hermes\config\router_profiles.yaml`, и он побеждает. Новые встроенные профили в существующую установку не попадают никогда.
Прямое следствие — жалоба владельца **«при подключении грока ошибка»**: мастер получает `None`, показывает «свободный слот не найден», и Grok с Claude подключить невозможно в принципе.
**Требуется миграция конфигурации.** При загрузке существующего `router_profiles.yaml` профили и роли, появившиеся во встроенных умолчаниях позже, должны в него добавляться, а не игнорироваться. Условия:
- пользовательские правки не затираются: если профиль с таким `profile_id` уже есть, он остаётся как есть;
- добавление фиксируется в журнале и видно в самопроверке;
- у файла есть версия схемы, чтобы миграция была идемпотентной и не повторялась;
- перед первой записью делается резервная копия рядом с файлом.
**Тест:** взять конфиг из 16 профилей без claude/grok, выполнить загрузку, убедиться, что после неё `find_free_slot("grok")` возвращает существующий профиль, а десять профилей `antigravity` не изменились ни в одном поле.
### 2. Проверка просроченной авторизации была мертва
Отчёт: «добавлена предварительная проверка `status.get("expired")`».
Ключ в словаре называется **`is_expired`** (`profile_manager.py:414`), поэтому `status.get("expired")` всегда `None`. Проверка не срабатывала ни разу. Ваш собственный тест этого не поймал, потому что дефект прикрывала вторая проверка — в адаптере; когда при слиянии вызов адаптера из «Теста» ушёл, протухший аккаунт стал получать зелёную галочку.
Исправлено ревьюером при слиянии (`b625b0f`), трогать не нужно. Приводится как урок: тест проверял результат, достижимый двумя путями, и молчал о том, что один из них сломан.
### 3. Пересобранный установщик до владельца не доходит
Отчёт: «Перекомпилирован `dist/HermesHubSetup.exe` и обновлен `dist/checksums.txt`».
`dist/` числится в `.gitignore:9`. Через этот репозиторий бинарник не передаётся физически — он остался на вашей машине. Владелец ставит из своей локальной сборки.
**Требуется** описать в отчёте, как собранный установщик должен попадать к владельцу: публикация в `hermes-hub-releases`, снятие `dist/` с игнорирования, или сборка на стороне владельца одной командой. Выберите один способ и обоснуйте. Пока способа нет, утверждать «установщик обновлён» нельзя.
### 4. Флаги `/reinstall` и `/repair` разбираются, но ни на что не влияют
Отчёт: «Поддержан флаг командной строки `/reinstall` (и алиас `/repair`)».
`HermesHubSetup.cs:968``bool isRepair = false;`, присваивается на строке 975 и **больше не используется нигде**. Это видно даже компилятору:
```
HermesHubSetup.cs(968,18): warning CS0219: Переменной "isRepair" присвоено значение,
но оно ни разу не использовалось
```
Запуск с `/reinstall` без `/silent` просто открывает обычный мастер. Либо реализовать тихую переустановку с кодами возврата, либо убрать флаг и не заявлять его.
---
## P0-1. Квоты для OpenAI Codex и OpenCode Go
Жалобы владельца: **«лимиты не подтягиваются, всё стоит Н/Д»** и **«у опенкода тоже нет лимитов»**.
Для Antigravity это уже решено — Codex реализовал живой опрос `retrieveUserQuotaSummary` у Google. Проверено на шести авторизованных аккаунтах владельца, данные настоящие и разные:
```
ag-w2 Claude/GPT — неделя remaining=37.4 used=62.6 source=provider_api
ag-w3 Claude/GPT — неделя remaining=90.1 used= 9.9 source=provider_api
ag-w1 Gemini — неделя remaining=99.7 used= 0.3 source=provider_api
```
Для двух других провайдеров осталась заглушка `_generate_baseline_snapshot` — все поля `None`:
```
opencode-go:opengo-1 source=provider_api
reason = "нет живого ответа от лимитов OpenCode Go"
Общий 5 часов / Недельный / Месячный: remaining=None
```
**Требуется** довести до реальных данных `openai-codex` и `opencode-go` по тому же образцу: опрос настоящего эндпоинта провайдера с использованием сохранённых учётных данных, обновление токена при 401, `source="provider_api"` только когда числа действительно измерены.
Правило честности прежнее и оно важнее полноты: **если провайдер данных не отдаёт — `None` и внятная причина, а не правдоподобное число.** Текущее поведение OpenCode Go в этом смысле правильное, оно просто неполное. Если у провайдера эндпоинта лимитов нет вовсе — это законный результат: зафиксировать в `docs/UI_STATE_CONTRACT.md` как недоступное, с причиной, чтобы интерфейс подписал честно.
**Тест:** на подготовленных учётных данных снапшот содержит измеренные значения; при ответе провайдера 401 — понятная причина и `None`; ни при каком сбое не появляется выдуманное число.
## P0-2. Служба обнаружения моделей: кэш, фон, таймаут
Владелец просит выбор моделей для агентов (жалоба 2). Интерфейс делает Codex, но опора нужна ваша.
`discover_models` есть у всех адаптеров, и для Antigravity он работает: `agy models` вернул 14 настоящих моделей.
Но вызывать его из интерфейса напрямую нельзя. Замерено на живой машине: **тот же `agy models` в одном прогоне отвечает за 40 секунд, а в следующем висит больше двух минут.** Синхронный вызов заморозит окно намертво.
**Требуется** служба обнаружения моделей:
- результат кэшируется на диске рядом с конфигурацией, с временем получения;
- обновление — в фоне, с жёстким таймаутом и понятным поведением при его срабатывании;
- интерфейс получает список мгновенно из кэша плюс признак свежести;
- при пустом кэше отдаётся `None`, а не выдуманный список — интерфейс покажет «список моделей ещё не получен»;
- ошибка обнаружения не должна ронять карточку и не должна молча подставлять умолчания.
**Тест:** обнаружение с искусственной задержкой дольше таймаута не блокирует вызывающий поток и оставляет прежний кэш.
## P0-3. Выдуманные списки моделей
`auto_assigner.ensure_profile_definition` (добавлен Codex, но это ваша зона) подставляет новым профилям жёстко зашитые списки:
```python
"grok": (["grok-3", "grok-3-mini", "grok-2"], ...),
"antigravity": (["gemini-3.7-flash", "claude-sonnet-4-6", "gemini-3.5-flash"], ...),
```
Это тот же класс дефекта, с которым мы боролись в квотах, только про модели. И он уже даёт ложь: у живого провайдера **`gemini-3.7-flash` не существует**. Реальный список:
```
gemini-3.7-flash-high / -medium / -low
gemini-3.6-flash-high / -medium / -low
gemini-3.5-flash-high / -medium / -low
gemini-3.1-pro-high / -low
claude-sonnet-4-6, claude-opus-4-6-thinking, gpt-oss-120b-medium
```
При этом `gemini-3.7-flash` стоит в живом конфиге владельца как `default_model` роли `orchestrator`, а `gemini-3.6-flash-high` у роли `fast` — существует. То есть часть ролей настроена на несуществующую модель.
**Требуется:**
1. Списки моделей для новых профилей брать из обнаружения (P0-2), а не из литерала. Пока обнаружение не выполнено — оставлять список пустым; профиль без списка моделей честнее профиля с выдуманным.
2. Проверка конфигурации: модели, которых нет у провайдера, отмечаются в самопроверке и в контракте как недействительные, с указанием роли и профиля. Молча подставлять «похожую» модель нельзя — это решение владельца.
3. Разобраться, почему вызов с `gemini-3.7-flash` до сих пор не приводил к явной ошибке. Если провайдер молча подставляет ближайшую — это надо знать и написать в отчёте, потому что тогда владелец получает не ту модель, которую выбрал.
**Тест:** профиль, созданный при отсутствии кэша моделей, не содержит ни одного идентификатора модели; проверка конфигурации сообщает о модели, отсутствующей у провайдера.
## P1-4. Жёсткие срезы в данных для диаграммы
`dashboard_view.py:601``providers = list(snapshot.providers)[:3]`. Провайдеров пять, два молча отбрасываются. Отрисовка — зона Codex, и срез уберут там, но решение о том, сколько провайдеров вообще имеет смысл показывать и в каком порядке, принимается на стороне данных: сейчас порядок ничем не задан, поэтому какой именно провайдер исчезнет — дело случая.
Задать явный, устойчивый порядок провайдеров в снапшоте (например, по числу авторизованных профилей, затем по алфавиту) и описать его в контракте.
## P1-5. Остаток по YAML
Внутренние комментарии `router_profiles.yaml` по-прежнему теряются (7 → 2). Пункт висит с A7 и в A8 не закрыт. Либо полный round-trip, либо статус «частично» с перечнем теряемого — в контракте и в отчёте. С учётом P0-0.1 это стало важнее: миграция будет писать в этот файл, и терять при каждой записи комментарии владельца нельзя.
---
## Ограничения
- Граница: зона Codex — `src/antigravity_provider/router/ui/**`, `tests/test_ui_*.py`. По `hermes_hub_app.py` действует прежнее исключение для backend-функций вроде `do_test_profile`, но не для представления.
- Никаких чисел и идентификаторов без измерения. Нет данных — `None` и причина.
- Тег `v0.1.1` не создавать.
- Резервная копия `router_profiles.yaml` перед первой записью миграции — обязательна.
## Критерии приёмки
1. Ни один файл зоны Codex не изменён.
2. На существующем конфиге без claude/grok после загрузки `find_free_slot` для обоих возвращает существующий профиль; десять профилей `antigravity` не изменены; проверено тестом.
3. Миграция идемпотентна и не теряет пользовательские правки и комментарии.
4. Квоты `openai-codex` и `opencode-go` приходят измеренными либо `None` с причиной; ни одного выдуманного числа; проверено тестом на обоих исходах.
5. Обнаружение моделей кэшируется, обновляется в фоне и не блокирует вызывающий поток при таймауте; проверено тестом с искусственной задержкой.
6. Новые профили не содержат выдуманных моделей; проверка конфигурации сообщает о несуществующих моделях в ролях владельца.
7. `/reinstall` либо работает с кодами возврата, либо удалён; предупреждение CS0219 при сборке отсутствует.
8. В отчёте назван конкретный способ доставки установщика владельцу.
9. Прогон **в обоих окружениях**; обе команды и оба результата в отчёте.
10. `ruff check .` чисто. Про релизный гейт: он **красный на `main` уже сейчас** (проверка 4 падает не по вашей вине). Указать в отчёте его состояние до и после ваших правок; ухудшать нельзя.
11. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`.
## Главное
Владелец сказал: «надо чтобы хаб уже заработал». Ядро работает — живой каскад отказоустойчивости в журнале это доказал, и квоты Antigravity теперь настоящие. Осталось, чтобы не работающее выглядело как не работающее, а не как Н/Д без объяснений, и чтобы провайдер, который он хочет подключить, подключался.
## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`.