docs(task): A21 — четыре недостающих экрана веб-интерфейса
Веб покрывает пять экранов из девяти. Не хватает Аналитики, Состояния, Журнала событий и Настроек. Данные для первых двух уже в снапшоте: metrics.telemetry (19 вызовов, 15 отказов, латентность p50/p95/max) и metrics.host плюс readiness. Для двух других источника в API нет вовсе — нужны GET /api/events (EventLogService в backend есть) и GET /api/settings без секретов. Зона Flash на это задание расширена на router/web/** целиком, включая server.py: A20 в web/ не заходит, конфликта не будет. Обнаружение моделей в задание НЕ включено, хотя A18 его пропустил: причина оказалась глубже и лежит в авторизации agy, это чинит A20. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
187f181aec
commit
972e34911c
1 changed files with 162 additions and 0 deletions
162
agents/inbox/2026-08-24-A21-antigravity-flash-web-parity.md
Normal file
162
agents/inbox/2026-08-24-A21-antigravity-flash-web-parity.md
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
# Задание 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=<n>&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`.
|
||||||
Loading…
Reference in a new issue