# Задание A21 (Antigravity Flash): довести веб-интерфейс до паритета с десктопом ## Дата поступления 2026-08-24 ## База Проверочный HEAD на момент выдачи: **`187f181`**. ## Ветка `antigravity/web-parity` --- ## Порядок работы с git ``` cd <каталог репозитория>; git fetch origin --prune; git status git checkout main; git pull --ff-only origin main git checkout -b antigravity/web-parity git commit -m "..." <- сначала коммит git push -u origin antigravity/web-parity ``` В конце: ``` git status <- дерево чистое git log --oneline -1 origin/antigravity/web-parity <- ваш финальный коммит ``` --- ## Что принято по A18 Действие `set_model` сделано и устроено правильно: живёт в `action_handler`, валидирует модель по списку провайдера, десктоп и веб зовут одно и то же. **Но обнаружение моделей (P0-2) не сделано вовсе** — `model_discovery_service` и зонд не менялись, ручного обновления нет, кэша на диске нет. Из-за этого `set_model` отклоняет **любую** модель, включая настоящую: сравнивать не с чем. ``` do_set_model('ag-w2','gemini-3.1-pro-high') -> (False, "список моделей провайдера ещё не получен") ``` Это второе задание подряд, где пункт про обнаружение остался нетронутым. **В это задание он не включён** — выяснилось, что причина глубже и лежит в авторизации: `agy models` отвечает «Please sign in» при шести валидных OAuth-профилях, потому что Hub не пишет `oauth_creds.json` в формате, который CLI ожидает. Это чинит **A20**, и обнаружение доделывается там же, после авторизации. Отдельно скажу прямо, потому что это повторяется: **пропущенный пункт нужно называть в отчёте пропущенным.** Не «сделано», не молчание — просто «не успел» или «не смог, потому что». Один такой абзац экономит раунд. ## Что принято по A16 — и это важно для нового задания Веб-клиент был лучшей работой за все раунды. Экран «Аккаунты» с квотами, пулами и периодами, честный индикатор источника данных, восемь содержательных скриншотов. Это задание — продолжение той же работы. --- ## Задача: четыре недостающих экрана Веб покрывает пять экранов из девяти: ``` есть: Аккаунты, Обзор, Маршрутизация, Модели и провайдеры, Команда нет: Аналитика, Состояние, Журнал событий, Настройки ``` Цель — паритет, чтобы десктоп можно было наконец перестать тащить второй веткой. Пока паритета нет, **десктоп не трогаем**: `router/ui/**` остаётся рабочим. ## P0-1. Аналитика — данные уже есть `snapshot.metrics.telemetry` заполнена настоящими измерениями. С машины владельца: ```json "global": { "window_seconds": 86400, "total_calls": 19, "successful_calls": 4, "failed_calls": 15, "call_share": 1.0, "error_rate": 0.7895, "latency_p50_ms": 1.4, "latency_p95_ms": 81631.1, "latency_max_ms": 81777.7, "total_prompt_tokens": null, "total_completion_tokens": null, "total_tokens": null } ``` Показать: вызовы, доля ошибок, латентность p50/p95/max, разрезы по провайдерам и ролям (они рядом в том же объекте). **Токены равны `null` — это не ноль.** Провайдеры их не отдают. Показывать «Н/Д» с причиной, ни в коем случае не «0». Обратите внимание на сами числа: 15 отказов из 19 и p95 в 81 секунду — это следствие сломанной авторизации `agy`, которую чинит A20. Экран должен честно показывать такую картину, а не сглаживать её. ## P0-2. Состояние — данные тоже есть `snapshot.metrics.host`: ```json "cpu_percent": 11.8, "memory_percent": 75.5, "memory_used_mb": 9077.9, "disk_percent": 53.7, "disk_used_gb": 255.7, "net_speed_mbps": null ``` Плюс `snapshot.readiness`: `state`, `title_ru`, `summary_ru`, `roles_ready_count` / `total_roles`, `accounts_connected_count` / `total_accounts`, `providers_ready_count` / `total_providers`, `warnings`. `net_speed_mbps` равен `null` — показывать «Н/Д», не ноль. Список `warnings` вывести целиком: это готовности ради него и считаются. ## P0-3. Журнал событий — источника в API нет, его нужно добавить Проверено: **событий в снапшоте нет ни в каком виде.** В backend они есть — `EventLogService` в `unified_health.py` с методом `get_events(limit, category)`. Требуется новый эндпоинт: ``` GET /api/events?limit=&category=<необязательно> -> {"events": [{"timestamp","category","message","details","level"}]} ``` Отдавать в обратном хронологическом порядке, с разумным пределом по умолчанию. **Секреты в события не попадают** — существующий тест на отсутствие секретов в ответах должен продолжать проходить, и на новый эндпоинт его нужно распространить. На экране: лента с фильтром по уровню и категории и поиском по тексту. ## P0-4. Настройки — текущих значений в API тоже нет Действие `save_settings` существует, а прочитать текущие значения через API нельзя: в снапшоте их нет. Требуется: ``` GET /api/settings -> текущее содержимое hub_settings.json ``` **Без секретов.** Поле `web_api_token` наружу не отдавать никогда — ни целиком, ни частично. Отдавать признак «токен задан / не задан». На экране: параметры, выбор темы, интервал обновления квот, пути. Сохранение — через существующее действие `save_settings`, второй реализации не заводить. ## P0-5. Контракт Оба новых эндпоинта — в `docs/web-api/CONTRACT.md`, с примерами ответов. Версию контракта поднять. Это не бюрократия: контракт версии 1.0 описал каталог `static/`, но не назвал, **кто его отдаёт**, — обе стороны выполнили написанное, и в браузере был 404. Пропуск был на авторе контракта, но цена — потерянный раунд. --- ## Ограничения - Параллельно идёт **A20** (авторизация Antigravity). Его файлы: `oauth.py`, `profile_oauth.py`, `profile_manager.py`, `agy_subprocess.py`, `adapters/**`, `model_discovery*`, `credentials.py`. **Ничего из этого не трогать.** - **Ваша зона на это задание расширена**: `router/web/**` целиком, включая `server.py` — там нужны два новых эндпоинта. A20 в `web/` не заходит, конфликта не будет. - `router/ui/**` не трогать: десктоп остаётся рабочим до паритета. - Правило честности без исключений: `null` — это «Н/Д» с причиной, а не ноль. Отличать «данных нет» от «данные грузятся» — механизм `is_loading` уже работает на «Аккаунтах», используйте его же. - Секреты не отдавать ни в одном новом эндпоинте. - Тег `v0.1.1` не создавать. ## Критерии приёмки 1. Ветка в `origin`, финальный коммит виден, `git status` чист. 2. Четыре экрана работают на данных: Аналитика, Состояние, Журнал событий, Настройки. 3. `GET /api/events` и `GET /api/settings` реализованы и описаны в контракте; версия контракта поднята. 4. Тест на отсутствие секретов распространён на новые эндпоинты и проходит; `web_api_token` наружу не отдаётся. 5. Ни одно `null` не показано как ноль; у каждого «Н/Д» есть причина. 6. Ни один файл зоны A20 не изменён; `router/ui/**` не тронут. 7. **Скриншоты всех четырёх экранов с настоящими данными.** Открыть и посмотреть перед тем, как прикладывать: в прошлый раз три из восьми оказались пустым чёрным кадром. 8. `ruff check .` чисто; релизный гейт не ухудшен. 9. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`. На `main` сейчас 342 passed, 2 skipped. 10. **Если какой-то пункт не сделан — сказать об этом прямо**, с причиной. ## Главное После этого задания веб покрывает всё, что умеет десктоп, и владелец сможет пользоваться Hub на своём сервере с Ubuntu, не запуская окно по SSH. Это же условие для того, чтобы перестать поддерживать два интерфейса — а интерфейс был узким местом каждого раунда этого проекта. ## Порядок сдачи Передать точный `FINAL_COMMIT_SHA`. Сдано только после появления коммита в `origin`.