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>
This commit is contained in:
Hermes Team 2026-08-23 13:00:36 +07:00
parent cdfd9f1c8d
commit 78cd826624
3 changed files with 472 additions and 178 deletions

View file

@ -1,178 +0,0 @@
# Задание A10 (Antigravity): всё оставшееся — backend и интерфейс
## Дата поступления
2026-08-23
## База
Проверочный HEAD на момент выдачи: **`0165434`**. Обязательно обновить локальную копию — см. следующий раздел.
## Ветка
`antigravity/all-remaining`
---
## Главное изменение: границы зон больше нет
У Codex закончились лимиты. **Вся работа переходит к вам, включая интерфейс.** Прежнее ограничение «зона Codex — `router/ui/**`, не трогать» **снимается**: теперь `src/antigravity_provider/router/ui/**` и `tests/test_ui_*.py` ваши.
Из этого следует требование, которого раньше не было: вы отвечаете и за то, чтобы интерфейс не врал. Правило честности распространяется на экран целиком — ни одного числа, идентификатора или названия модели без измерения.
---
## Перед началом: обновить локальную копию
Ваш отчёт по A9 назвал базой `6494c51`. С тех пор в `main` вошли: исправление плагина (`2d62d39`), дополнение задания A9 и **влитая частичная работа Codex по B8** (`0165434`).
```
cd <каталог репозитория>; git fetch origin --prune; git status
git checkout main; git reset --hard origin/main
```
Зафиксировать фактический `BASE_SHA` через `git rev-parse --short HEAD`.
---
## О статусе A9
Отчёт по A9 получен и прочитан. **Ни одной строки этой работы в репозитории нет.** Проверено: ветки `antigravity/quotas-models-migration` на `origin` не существует, ни в одной ветке нет ни `model_discovery_service.py`, ни `test_a9_migration_quotas_models.py`, ни миграции в `router_config.py`.
Работа осталась на вашей машине. Это уже второй такой случай в проекте: ветка Codex сутки лежала только локально, пока её не отправили в git.
**Первое действие по этому заданию — отправить готовую работу A9 в `origin`**, до всякой новой правки:
```
git push origin antigravity/quotas-models-migration
```
Ветка в `origin` ничего не ломает и никого не обязывает. Несохранённая работа теряется вместе с диском.
Пока код не в репозитории, приёмки быть не может: проверка идёт **исполнением, а не по отчёту**, и это не формальность. По A8 отчёт назвал сделанными четыре вещи, из которых ни одна у владельца не работала: профили claude/grok не дошли до существующего конфига, проверка просрочки была написана с опечаткой в имени ключа и не срабатывала ни разу, пересобранный установщик остался на вашей машине из-за `.gitignore`, флаг `/reinstall` не использовался в коде вообще.
Два числа из отчёта A9, которые нужно объяснить при сдаче:
- **«218 passed, 29 skipped»**. На `main` в venv Hermes набор даёт **290 passed, 2 skipped**. Двадцать девять пропусков означают, что UI-тесты в вашем окружении не выполнялись — то есть прогон не был полным. Требуется прогон в окружении **с** `customtkinter`, `pillow`, `psutil`.
- **«Release Gate 7/7 PASSED»**. Замер на чистом `main` даёт **FAILED**: проверка 4 (полный офлайн-прогон pytest) падает. Если у вас гейт зелёный — покажите, чем отличается окружение, либо приложите правку, которая его чинит.
---
## P0-1. Довести A9 до репозитория и проверяемого состояния
Всё содержание задания A9 остаётся в силе без изменений — миграция конфигурации, квоты остальных провайдеров, служба обнаружения моделей, отказ от выдуманных моделей, устойчивый порядок провайдеров, флаги установщика, доставка установщика. Читайте A9 как часть этого задания.
Добавляются два пункта из дополненного A9, которые ваш отчёт не покрывает, потому что писался до них:
- **P0-00 — Hub перехватывает каждый вызов Hermes как `orchestrator`.** Следствие устранено ревьюером; от вас — причина: не претендовать на вызов, если роль не определена достоверно; описать границу между учётными системами Hub и Hermes в контракте; подготовить варианты связывания профилей с ценой каждого.
- **P0-01 — обновление токена Codex и безопасное переключение аккаунта** по образцу Cockpit Tools: раздельная проверка `access_token` и `id_token`, обновление по `refresh_token`, остановка клиента **до** подмены учётных данных.
## P0-2. Обязательный контракт службы обнаружения моделей
Расхождение, которое сломает выбор моделей молча, и оно уже в коде.
Codex в `ui/model_catalog.py` импортирует:
```python
from antigravity_provider.router.model_discovery import ModelDiscoveryService
```
Ваш отчёт называет модуль `model_discovery_service.py`. Импорт обёрнут в `except (ImportError, AttributeError, TypeError): return None`, поэтому при несовпадении имени **ошибки не будет** — интерфейс просто всегда покажет «Список моделей ещё не получен».
Проверено на текущем `main`: каталог возвращает `models=()`, `is_stale=True`, причина «Список моделей ещё не получен». Выбор модели подключён и пуст.
**Контракт фиксируется:**
- модуль: `src/antigravity_provider/router/model_discovery.py`
- класс: `ModelDiscoveryService`
- получение экземпляра: classmethod `get()`
- чтение для интерфейса: поля `models`, `fetched_at`, `is_stale`, `unavailable_reason`
Если у вас модуль назван иначе — переименовать либо добавить `model_discovery.py` точкой входа.
**Тест обязателен, и он должен падать при несовпадении пути.** Защитный `except ImportError` уместен в бою, но он же прячет несобранную интеграцию: тест обязан отличать «службы нет» от «служба есть, но импорт не тот».
## P0-3. Компактные карточки аккаунтов
Codex до этого не дошёл — `accounts_view.py` не менялся.
Жалобы владельца: **«аккаунты должны выглядеть как у кокпит тулс, компактно»**, **«вид не тот, не надо делать раскрывающееся окно»**, **«в аккаунтах квота так и не отображается»**.
Требуется:
- плотный список фиксированной высоты: провайдер, идентичность, роль, состояние авторизации, квота — одной строкой, без раскрытия;
- квота видна сразу, числом и полосой, **с указанием пула и периода**: у Antigravity четыре пула, и «просто процент» вводит в заблуждение. Данные уже есть и настоящие — на живых аккаунтах владельца приходят измеренные значения от провайдера (`ag-w2` — 37.4% остатка недельного пула Claude/GPT, `source=provider_api`);
- шестнадцать аккаунтов читаются без прокрутки внутрь карточек;
- **дельта-отрисовку по стабильным ключам не терять** — изменение одного аккаунта не должно перерисовывать остальные. Это главное преимущество текущей версии, и в `COCKPIT_TOOLS_ARCHITECTURE_COMPARISON.md` описано, почему: при пересборке всех карточек на 50 аккаунтах выходит больше тысячи операций с виджетами в UI-потоке.
Документ в корне репозитория — архитектурное сравнение, а не макет. Берите из него принцип обновления и плотность, не буквальную вёрстку.
## P0-4. Разобрать дублирующие разделы
Codex до этого не дошёл — `quotas_view.py` не менялся.
Жалоба: **«провайдеры и квоты и лимиты вообще не понятно для чего нужны, там всё то же, что и в аккаунты»**.
Владелец прав: после появления квот в карточках аккаунтов отдельный раздел квот потерял смысл. Нужно решение, а не сохранение обоих на всякий случай.
- **«Квоты и лимиты»** — убрать из навигации либо оставить только то, чего нет в «Аккаунтах»: сводка по провайдеру целиком, история расхода, ближайшие сбросы.
- **«Провайдеры»** — оставить относящееся к провайдеру, а не к аккаунту: доступность runtime, обнаруженные модели, версия CLI, состояние авторизации в целом.
В отчёте перечислить перенесённое и удалённое. Пустой раздел не оставлять.
## P0-5. Причина у каждого Н
Жалоба: **«что означает н/д в маршрутизация запросов»**.
Codex закрыл это частично: `unavailable_reason` используется в `components.py` и на «Обзоре», но не везде.
Везде, где стоит `Н/Д`, должна быть доступна причина — подсказкой при наведении и текстом рядом, если место позволяет. Формулировки конкретные: «нет телеметрии: роль ещё не вызывалась», «провайдер не отдаёт лимиты», «аккаунт не подключён». Данные для этого есть.
## P1-6. Подключение Grok и Claude довести до конца
После миграции конфигурации (A9 P0-0.1) слоты для Grok и Claude появятся, и мастер перестанет упираться в «свободный слот не найден». Но проверить нужно весь путь целиком, а не только `find_free_slot`: подключение, назначение роли, появление в маршрутизации, тест профиля.
Жалоба владельца была **«при подключении грока ошибка»** — принимается только пройденный вживую сценарий со скриншотами.
---
## Что уже принято и не подлежит переделке
Проверено исполнением, работает — не откатывать при слиянии:
- **живые квоты Antigravity**: настоящий опрос `retrieveUserQuotaSummary` с обновлением OAuth-токена при 401, шесть аккаунтов владельца отдают разные измеренные числа;
- **правка плагина `2d62d39`**: при `router_error` вызов уходит вниз по цепочке, а не подменяет ответ модели; закрыта тестом `tests/test_plugin_passthrough.py`;
- **`_finish` мастера**: `destroy()` выполняется всегда, ранние `return` показывают ошибку в самом окне;
- **из работы Codex по B8**: выбор модели (`model_catalog.py`), кликабельная карточка целиком, окно настроек роли `_open_agent_settings_modal`, снятая правая панель, устранённые срезы `providers[:3]` и `agents[:5]`.
---
## Ограничения
- Границы зон нет — весь репозиторий ваш. Ответственность за честность интерфейса теперь тоже ваша.
- Не выдумывать числа, идентификаторы и названия моделей. Нет данных — `None` в модели, `Н/Д` с причиной на экране.
- Три темы сохранить, дельта-отрисовку не терять.
- Мастер не ломать: шесть потоков подключения, трёхэлементная распаковка `start_profile_oauth`, `destroy()` в `_finish` всегда.
- Сеть и подпроцессы — не в UI-потоке. Замерено: `agy models` в одном прогоне отвечает за 40 секунд, в следующем висит больше двух минут.
- Тег `v0.1.1` не создавать.
- **Ветку пушить сразу после первого коммита.**
## Критерии приёмки
1. Работа A9 находится в `origin` и проверяема исполнением.
2. Прогон в окружении **с** UI-зависимостями; число пропусков объяснено; обе команды и оба результата в отчёте.
3. Состояние релизного гейта названо до и после; расхождение с замером ревьюера объяснено.
4. Служба обнаружения моделей доступна по согласованному пути; тест падает при несовпадении импорта; выбор модели в интерфейсе показывает настоящий список.
5. Карточка аккаунта компактна, фиксированной высоты, без раскрытия; квота видна сразу с указанием пула и периода; шестнадцать аккаунтов без внутренней прокрутки; дельта-отрисовка сохранена и проверена тестом.
6. Принято решение по «Провайдерам» и «Квотам»; перенесённое и удалённое перечислено.
7. У каждого `Н/Д` доступна причина.
8. Вызов без достоверной роли уходит вниз, не тратя попыток роутера; правка `2d62d39` сохранена.
9. Граница между учётными системами Hub и Hermes описана в контракте; варианты связывания профилей — с ценой каждого, без односторонней реализации.
10. Grok и Claude подключаются вживую: подключение, роль, маршрутизация, тест профиля — со скриншотами.
11. `ruff check .` чисто.
12. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`.
13. **Скриншоты живого сценария с настоящими данными.** Пустых состояний не присылать.
## Главное
Владелец сказал: «надо чтобы хаб уже заработал». Ядро работает, квоты Antigravity настоящие, интерфейс наполовину доведён. Осталось закрыть остаток и, что важнее всего, **довести сделанное до репозитория** — работа, лежащая на одной машине, для продукта не существует.
## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`.

View file

@ -0,0 +1,252 @@
# Задание 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`.**

View file

@ -0,0 +1,220 @@
# Задание A12 (Antigravity Flash, другой ПК): интерфейс и чистка разделов
## Дата поступления
2026-08-23
## База
Проверочный HEAD на момент выдачи: **`cdfd9f1`**.
## Ветка
`antigravity/interface-cleanup`
## Кому
Быстрая ветка работ. Задачи описаны подробно и не требуют проектных решений — только аккуратное исполнение.
---
## Порядок работы с git — читать первым
Прошлый раз работа A9 была готова, но **не отправлена в `origin`**, и трое суток её не существовало для проекта. Чтобы это не повторилось, порядок жёсткий.
**Шаг 1. В самом начале — обновить локальную копию из git:**
```
cd <каталог репозитория>; git fetch origin --prune; git status
```
Если рабочее дерево чистое:
```
git checkout main; git pull --ff-only origin main
```
Если есть незакоммиченные правки — сначала сохранить их отдельной веткой, вслепую сбрасывать нельзя.
**Шаг 2. Создать ветку от свежего `main` и сразу отправить её:**
```
git checkout -b antigravity/interface-cleanup
git push -u origin antigravity/interface-cleanup
```
Пустая ветка в `origin` ничего не ломает и никого не обязывает. Зато с этого момента работа перестаёт зависеть от одного диска.
**Шаг 3. Пушить после каждого осмысленного коммита**, не копить.
**Шаг 4. В самом конце — обязательный финальный push и проверка, что он прошёл:**
```
git push origin antigravity/interface-cleanup
git status
git log --oneline -1 origin/antigravity/interface-cleanup
```
Последняя команда должна показать ваш финальный коммит. Если не показывает — push не прошёл, повторить.
Зафиксировать фактический `BASE_SHA` через `git rev-parse --short HEAD` и указать его в отчёте. Не считать `cdfd9f1` актуальным автоматически — параллельно идёт A11.
---
## Параллельная работа: строгая граница по файлам
Одновременно выполняется **A11** вторым исполнителем.
**Ваши файлы:**
```
src/antigravity_provider/router/ui/**
src/antigravity_provider/router/model_discovery.py
src/antigravity_provider/router/model_discovery_service.py
src/antigravity_provider/router/router_config.py
src/antigravity_provider/router/auto_assigner.py
src/antigravity_provider/router/unified_health.py
installer/**
tests/test_ui_*.py
```
**Не ваши** (зона A11): `hermes_plugin.py`, `router_engine.py`, `codex_oauth.py`, `profile_manager.py`, `quota_collector.py`, `adapters/**`, `agy_subprocess.py`, `docs/UI_STATE_CONTRACT.md`.
Понадобился чужой файл — скажите, будет заказан отдельно.
---
## Что принято по A9
Проверено исполнением, работа хорошая: миграция конфигурации подняла 16 профилей до 22, `claude` и `grok` получили слоты, резервная копия создаётся, десять профилей владельца не тронуты, комментарии не потеряны, флаг `/reinstall` заработал, предупреждение CS0219 исчезло.
**Один урок, который стоит запомнить.** Служба обнаружения моделей была написана правильно, но **недостижима**: файл назван `model_discovery_service.py`, а интерфейс импортирует `model_discovery`. Импорт в `ui/model_catalog.py` обёрнут в `except ImportError`, поэтому ошибки не возникало — выбор моделей просто оставался пустым навсегда, с надписью «Список моделей ещё не получен». Ревьюер добавил точку входа при слиянии.
Защитный `except ImportError` уместен в бою, но он же прячет несобранную интеграцию. Пишете модуль для чужого потребителя — проверьте импорт **исполнением**, а не глазами.
---
## Что уже сделал Codex и переделывать не нужно
У Codex закончились лимиты, его работа влита частично. Не откатывайте:
- выбор модели: `ui/model_catalog.py` с честным пустым состоянием;
- **карточка кликабельна целиком** (`cursor="hand2"` + привязка `<Button-1>`), «три точки» перестали быть единственным входом;
- **окно настроек роли**: `_open_agent_settings_modal`;
- **правая панель «Статус в реальном времени» убрана**, центр расширен;
- жёсткие срезы `providers[:3]` и `agents[:5]` в диаграмме устранены;
- причины у части `Н/Д` через `unavailable_reason`.
Codex остановился ровно перед двумя пунктами ниже — они и есть основная работа задания.
---
## P0-1. Компактные карточки аккаунтов
`accounts_view.py` не менялся вообще. Это самый заметный для владельца пункт.
Жалобы дословно: **«аккаунты должны выглядеть как у кокпит тулс, компактно»**, **«вид не тот, не надо делать раскрывающееся окно»**, **«в аккаунтах квота так и не отображается»**.
Требуется:
- плотный список фиксированной высоты: провайдер, идентичность, роль, состояние авторизации, квота — одной строкой, **без раскрытия**;
- **квота видна сразу**, числом и полосой, с указанием пула и периода. У Antigravity четыре пула, и «просто процент» вводит в заблуждение;
- шестнадцать аккаунтов читаются без прокрутки внутрь карточек;
- **дельта-отрисовку по стабильным ключам не терять** — изменение одного аккаунта не перерисовывает остальные.
Данные для квоты **уже есть и настоящие**. На живых аккаунтах владельца приходят измеренные значения от провайдера:
```
ag-w2 Claude/GPT — неделя осталось 37.4% source=provider_api
ag-w3 Claude/GPT — неделя осталось 90.1% source=provider_api
ag-w1 Gemini — неделя осталось 99.7% source=provider_api
```
Где данных нет — `Н/Д` с причиной, а не ноль и не прочерк без объяснения. У `opencode-go` и `grok` провайдер лимитов не отдаёт, и причина уже заполняется в `unavailable_reason`.
Про дельта-отрисовку: в корне репозитория лежит `COCKPIT_TOOLS_ARCHITECTURE_COMPARISON.md` — там описано, почему это важно: при пересборке всех карточек на 50 аккаунтах выходит больше тысячи операций с виджетами в UI-потоке, и окно заметно подвисает. Документ — архитектурное сравнение, а не макет: берите принцип обновления и плотность, не буквальную вёрстку.
**Тест:** изменение квоты одного аккаунта не пересоздаёт виджеты остальных.
## P0-2. Разобрать дублирующие разделы
`quotas_view.py` не менялся, `providers_view.py` тронут на девять строк.
Жалоба: **«провайдеры и квоты и лимиты вообще не понятно для чего нужны, там всё то же, что и в аккаунты»**.
Владелец прав: после появления квот в карточках аккаунтов отдельный раздел квот потерял смысл. Нужно **решение**, а не сохранение обоих на всякий случай.
- **«Квоты и лимиты»** — убрать из навигации либо оставить только то, чего нет в «Аккаунтах»: сводка по провайдеру целиком, история расхода, ближайшие сбросы. Нет такого содержания — убрать раздел.
- **«Провайдеры»** — оставить относящееся к провайдеру, а не к аккаунту: доступность runtime, обнаруженные модели, версия CLI, состояние авторизации в целом.
В отчёте перечислить, что перенесено, что удалено и почему. Пустой раздел не оставлять: либо содержание, либо нет пункта навигации.
## P0-3. Причина у каждого Н
Жалоба: **«что означает н/д в маршрутизация запросов»**.
Codex закрыл частично: `unavailable_reason` используется в `components.py` и на «Обзоре», но не везде.
Везде, где стоит `Н/Д`, причина должна быть доступна — подсказкой при наведении и текстом рядом, если место позволяет. Формулировки конкретные: «нет телеметрии: роль ещё не вызывалась», «провайдер не отдаёт лимиты», «аккаунт не подключён». Данные для этого есть.
Это претензия не к честности, а к молчаливости: владелец видит прочерк и не знает, это поломка, ненастроенное или неизмеримое.
## P0-4. Тест на достижимость службы обнаружения моделей
Ревьюер добавил точку входа `router/model_discovery.py`, и выбор моделей снова собран. Но защита от повторения этой ошибки отсутствует.
Требуется тест, который **падает**, если служба недостижима по согласованному пути:
- модуль: `src/antigravity_provider/router/model_discovery.py`
- класс: `ModelDiscoveryService`
- получение экземпляра: classmethod `get()`
Тест обязан отличать «службы нет» от «служба есть, но импорт не тот». Проверять через `ui/model_catalog` недостаточно — там `except ImportError` всё проглотит; импортируйте модуль напрямую.
## P0-5. Подключение Grok и Claude довести до конца
После миграции из A9 слоты появились, проверено:
```
find_free_slot(grok) -> grok-worker-1
find_free_slot(claude) -> claude-orch
```
Мастер больше не должен упираться в «свободный слот не найден». Но проверить нужно **весь путь**, а не только выдачу слота: подключение, назначение роли, появление в маршрутизации, тест профиля.
Жалоба владельца была **«при подключении грока ошибка»**. Принимается только пройденный вживую сценарий со скриншотами.
## P1-6. Порядок провайдеров в диаграмме
A9 задал детерминированный порядок провайдеров в `unified_health.get_provider_summaries()` (по числу авторизованных профилей, затем по общему числу слотов, затем по имени) и привёл `total_providers` к пяти.
Убедиться, что диаграмма на «Обзоре» этот порядок соблюдает и показывает всех пятерых, а не первых трёх. Срезы Codex убрал, но проверить связку целиком стоит — раньше `claude` и `grok` молча отбрасывались.
---
## Ограничения
- Строгая граница по файлам — см. выше.
- Не выдумывать числа, идентификаторы и названия моделей. Нет данных — `Н/Д` с причиной либо блок отсутствует.
- Три темы сохранить, дельта-отрисовку не терять.
- Мастер не ломать: шесть потоков подключения, трёхэлементная распаковка `start_profile_oauth`, `destroy()` в `_finish` выполняется всегда.
- Сеть и подпроцессы — не в UI-потоке. Замерено: `agy models` в одном прогоне отвечает за 40 секунд, в следующем висит больше двух минут.
- Тег `v0.1.1` не создавать.
## Критерии приёмки
1. **Ветка в `origin`**, финальный коммит виден через `git log origin/antigravity/interface-cleanup`. Ни один файл зоны A11 не изменён.
2. Карточка аккаунта компактна, фиксированной высоты, без раскрытия; квота видна сразу с указанием пула и периода; шестнадцать аккаунтов без внутренней прокрутки.
3. Дельта-отрисовка сохранена: изменение одного аккаунта не пересоздаёт виджеты остальных; проверено тестом.
4. Принято решение по «Провайдерам» и «Квотам»; перенесённое и удалённое перечислено; пустых разделов нет.
5. У каждого `Н/Д` доступна причина.
6. Есть тест, падающий при недостижимости `router/model_discovery.ModelDiscoveryService`.
7. Grok и Claude подключаются вживую: подключение, роль, маршрутизация, тест профиля.
8. Диаграмма показывает всех пятерых провайдеров в заданном порядке.
9. Прогон **в обоих окружениях** — без UI-зависимостей и с `customtkinter`/`pillow`/`psutil`; обе команды и оба результата в отчёте. На `main` полный набор даёт 307 passed, 2 skipped.
10. `ruff check .` чисто. Релизный гейт не ухудшен.
11. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`.
12. **Скриншоты живого сценария с настоящими данными**: «Аккаунты» с видимыми квотами, подключение Grok, окно роли. Пустых состояний не присылать.
## Главное
Владелец сказал: «надо чтобы хаб уже заработал». Квоты настоящие, роли настраиваются, подключение Grok разблокировано. Осталось, чтобы шестнадцать аккаунтов читались с одного взгляда и в каждом непонятном месте был ответ, почему там прочерк.
## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`. **Сдано только после появления ветки в `origin`** — проверьте это командой перед тем, как отчитаться.