hermes-hub/agents/inbox/2026-08-23-A15-antigravity-pro-web-api.md
Hermes Team 06b55b49f4 docs(tasks): A15 и A16 — переход на веб-интерфейс
Решение владельца: у него сервер с Ubuntu Server и Xubuntu, Hermes там
будет линуксовый. Веб-интерфейс на сервере строго лучше десктопа.
Десктоп остаётся рабочим до паритета, router/ui/** не трогается.

A15 (Pro): веб-API поверх готового снапшота — GET /api/snapshot и
POST /api/action на семнадцать существующих действий; безопасность
(127.0.0.1 по умолчанию, отказ стартовать на внешнем адресе без токена,
тест на отсутствие секретов в ответе); порт на Linux — восемь мест,
читающих LOCALAPPDATA в обход paths.py; честное сообщение о том, какие
потоки авторизации на headless-сервере не работают. Плюс долг из
прошлого раунда: снапшот не различает «данных нет» и «данные грузятся».

A16 (Flash): клиент без сборки на обычном JS. Не заблокирован сервером —
разрабатывает против docs/web-api/snapshot.example.json. Экраны по
ценности: Аккаунты с видимыми квотами, затем Обзор и Маршрутизация.

Обе стороны пишутся против docs/web-api/CONTRACT.md и до слияния друг
друга не видят — отсюда требование не менять контракт односторонне.

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

160 lines
15 KiB
Markdown
Raw Permalink 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.

# Задание A15 (Antigravity Pro, этот ПК): веб-API и порт на Linux
## Дата поступления
2026-08-23
## База
Проверочный HEAD на момент выдачи: **`7ae4a28`**.
## Ветка
`antigravity/web-api`
---
## Порядок работы с git
```
cd <каталог репозитория>; git fetch origin --prune; git status
git checkout main; git pull --ff-only origin main
git checkout -b antigravity/web-api
```
После первого коммита — `git push -u origin antigravity/web-api`. В конце — push и проверка `git log --oneline -1 origin/antigravity/web-api`, `git status` должен быть чистым.
---
## Что принято по A13
Проверено исполнением, работа сильная.
**Устойчивость набора закрыта по-настоящему.** Общий корень Tk на сессию вместо шести отдельных. Прогнал десять полных прогонов подряд, как требовало задание: **316 passed, ноль ошибок во всех десяти**. До правки каждый прогон давал до семи ошибок, кочующих между файлами, и настоящую поломку приходилось искать перепроверкой в изоляции. Это чинилось не для галочки — оно мешало работе каждый раунд.
**Раздел контракта написан по существу**: все четыре вопроса раскрыты, явно сказано, что роль Hermes не передаёт. Варианты связывания профилей поданы таблицей с оценкой 34 / 812 / 45 дней, ни один не реализован без решения владельца — ровно как требовалось.
Одно исправлено при слиянии: в документе оказалось **11 символов BEL (0x07) на месте буквы «a»** — последовательности вида `\agy-05` были разобраны как escape. Пострадали 19 идентификаторов: `antigravity``ntigravity`, `ag-w2``g-w2`. Контракт читают оба исполнителя, битые имена в нём недопустимы. Проверяйте документы после записи так же, как код.
---
## Решение владельца: переходим на веб-интерфейс
У владельца сервер с Ubuntu Server и Xubuntu, Hermes там будет линуксовый. Веб-интерфейс на сервере строго лучше десктопа: X-forwarding CustomTkinter по сети — мучение, а интерфейс и так был единственным узким местом всех прошлых раундов.
Десктоп **остаётся рабочим** до достижения паритета. Ничего из `router/ui/**` не удаляется.
**Читать перед началом: `docs/web-api/CONTRACT.md`.** Это единственный источник истины для вас и для A16, который параллельно делает клиентскую часть. Отклонение от контракта — дефект, даже если ваш код работает: вторая сторона пишется против документа и вашего кода не видит.
---
## P0-1. Веб-API поверх готового снапшота
Архитектура уже сделала бо́льшую часть работы, проверено исполнением: `HubSnapshot` — dataclass, сериализуется в JSON **одним вызовом** `dataclasses.asdict`, объём около 100 КБ, вся поверхность действий сведена в `_handle_action` и состоит из семнадцати имён.
Новый пакет `src/antigravity_provider/router/web/` — структура задана контрактом.
Эндпоинты — по контракту, раздел 4:
- `GET /api/snapshot` — весь снапшот, `datetime` в ISO-8601;
- `POST /api/action` — тело `{"action": "...", "data": {...}}`, ответ `{"ok": bool, "message": str, "data": {...}}`. **Отказ действия — это `200` с `ok: false`**, а не `4xx`; `4xx` остаётся неизвестному действию и непройденной авторизации;
- `GET /api/health` — без авторизации.
Стек: FastAPI и uvicorn. Обе зависимости **уже объявлены** в `pyproject.toml` в группе `legacy` — она осталась от удалённого `gui_server.py` и не используется ничем. Переименовать в `web` и подключить.
Действия исполнять **через существующий путь**, а не дублировать логику. `_handle_action` сейчас завязан на виджеты; вынести из него исполнительную часть так, чтобы её вызывали и десктоп, и веб. Второй реализации семнадцати действий в проекте быть не должно.
**Долгие операции не должны держать запрос.** `refresh_all` опрашивает провайдеров по сети, `test` дёргает подпроцесс, `agy models` в замерах то отвечает за 40 секунд, то висит больше двух минут. Такие действия возвращают `ok: true` с сообщением «запущено», а результат приходит следующим снапшотом.
## P0-2. Безопасность — часть задания, не довесок
Сервер будет доступен по сети. Контракт, раздел 3:
1. По умолчанию слушать **только `127.0.0.1`**. Другой адрес — явным параметром, и тогда **токен обязателен**.
2. Токен в заголовке `X-Hub-Token`, сравнение через `secrets.compare_digest`.
3. При запуске на не-локальном адресе без токена сервер **отказывается стартовать** с внятным сообщением. Не поднимается открытым, не пишет предупреждение в лог и не продолжает.
4. **Тест, падающий при появлении секрета в ответе.** Проверено на живых данных: сейчас в сериализованном снапшоте нет ни `access_token`, ни `refresh_token`, ни `api_key`, ни JWT, ни строк `ya29.`/`sk-`. Это состояние надо удержать механически, а не обещанием.
5. Никаких секретов в URL и параметрах запроса.
## P0-3. Порт на Linux
Проверено по коду — ядро почти готово:
- `paths.py` **уже** кроссплатформенный: при отсутствии `LOCALAPPDATA` уходит в `~/.hermes`;
- поиск CLI **уже** готов: `shutil.which("agy") or shutil.which("agy.exe")`.
Чинить надо восемь мест, которые дублируют логику `LOCALAPPDATA` **в обход** `paths.py` и на Linux дадут неверные пути:
```
router/hermes_hub_app.py:37
router/router_config.py:319, 462
router/launcher_bootstrap.py:32
router/model_discovery_service.py:34
router/ui/assets.py:48
agy_subprocess.py:50
```
Все — через `paths.get_hermes_home()`. Единый источник истины уже есть, им просто не пользуются.
**Тест:** при заданном `HERMES_HOME` ни один модуль не обращается к `LOCALAPPDATA` напрямую; пути одинаковы во всех модулях.
**Проверка на настоящем Linux обязательна** — заявления «должно работать» не принимаются. Если под рукой нет машины, скажите об этом прямо в отчёте, и проверку сделает владелец.
## P0-4. Авторизация на сервере без экрана — сказать правду
Разобрано по коду, выяснять заново не нужно:
| Провайдер | Поток | На headless-сервере |
|---|---|---|
| OpenAI Codex | device-code (12 упоминаний) | **работает** |
| Grok | device-code (13) | **работает** |
| Antigravity | redirect на localhost | **не работает** |
| Claude | redirect на localhost | **не работает** |
Для Antigravity и Claude редирект придёт на машину пользователя, а не сервера, — поток обрывается.
**Требуется:** серверная часть сообщает клиенту, какие потоки на этой машине доступны, а какие нет, **и почему**. Поле в ответе `/api/health` или отдельный эндпоинт — на ваше усмотрение, но зафиксируйте в контракте и предупредите A16.
Обходной путь предложить, а не изобретать молча: проброс порта по SSH либо авторизация на десктопе с переносом каталога профиля. Что из этого работает — проверить и написать.
**Неработающую кнопку показывать нельзя.** Это прямое продолжение правила честности: интерфейс, предлагающий подключить Antigravity на сервере, где это невозможно, — та же ложь, что выдуманная квота.
## P1-5. Долг из прошлого раунда: квота не видна до фонового опроса
Найдено при проверке A14 и относится к вашей зоне.
`state_store` наполняет снапшот через `quota_service.get_snapshot`, который читает кэш и **при промахе отдаёт пустую заглушку из двух корзин, живой опрос не запуская**. Замер:
```
снапшот сразу после старта : 2 корзины, 0 измеренных
после прогрева кэша : 4 корзины, все измерены, ag-w2 = 37.4%
```
В работающем приложении квота появляется только после фонового обновления, а до него карточки стоят пустыми **без объяснения**. Это и есть жалоба владельца «в аккаунтах квота так и не отображается».
Требуется различать в модели данных **«данных нет»** и **«данные ещё грузятся»**. Сейчас оба состояния выглядят одинаково, и ни интерфейс десктопа, ни будущий веб отличить их не могут. Поле состояния — в снапшот и в контракт.
---
## Ограничения
- Параллельно идёт **A16** (клиентская часть). Ваши файлы: `router/web/**` кроме `static/`, `state_store.py`, `paths.py`, `router_config.py`, `agy_subprocess.py`, `launcher_bootstrap.py`, `model_discovery_service.py`, `pyproject.toml`, `docs/web-api/CONTRACT.md`. **Не ваши:** `router/web/static/**`, `router/ui/**`.
- Контракт менять можно, но **только правкой документа с явным упоминанием в отчёте** — вторая сторона пишется против него.
- Десктоп не ломать: он остаётся рабочим до паритета.
- Никаких чисел и идентификаторов без измерения.
- Тег `v0.1.1` не создавать.
## Критерии приёмки
1. Ветка в `origin`, финальный коммит виден, `git status` чист.
2. `GET /api/snapshot` отдаёт снапшот, совпадающий по структуре с `docs/web-api/snapshot.example.json`; проверено тестом сравнения ключей.
3. `POST /api/action` принимает все семнадцать действий; отказ возвращается как `200` с `ok: false`; неизвестное действие — `4xx`.
4. Действия исполняются через общий путь; второй реализации семнадцати действий в проекте нет.
5. Долгие операции не держат запрос; результат приходит следующим снапшотом.
6. Сервер по умолчанию слушает `127.0.0.1`; на внешнем адресе без токена **отказывается стартовать**; проверено тестом.
7. Есть тест, падающий при появлении в ответе `access_token`, `refresh_token`, `api_key`, JWT или строк `ya29.` / `sk-`.
8. Ни один модуль не читает `LOCALAPPDATA` в обход `paths.py`; проверено тестом с заданным `HERMES_HOME`.
9. Доступность потоков авторизации сообщается клиенту с причиной; зафиксировано в контракте.
10. В снапшоте различаются «данных нет» и «данные грузятся»; поле описано в контракте.
11. Прогон в обоих окружениях; `ruff check .` чисто; релизный гейт остаётся 7/7.
12. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`, и отдельно — проверялось ли на настоящем Linux.
## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`. Сдано только после появления коммита в `origin`.