Решение владельца: у него сервер с 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>
15 KiB
Задание 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 не передаёт. Варианты связывания профилей поданы таблицей с оценкой 3–4 / 8–12 / 4–5 дней, ни один не реализован без решения владельца — ровно как требовалось.
Одно исправлено при слиянии: в документе оказалось 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:
- По умолчанию слушать только
127.0.0.1. Другой адрес — явным параметром, и тогда токен обязателен. - Токен в заголовке
X-Hub-Token, сравнение черезsecrets.compare_digest. - При запуске на не-локальном адресе без токена сервер отказывается стартовать с внятным сообщением. Не поднимается открытым, не пишет предупреждение в лог и не продолжает.
- Тест, падающий при появлении секрета в ответе. Проверено на живых данных: сейчас в сериализованном снапшоте нет ни
access_token, ниrefresh_token, ниapi_key, ни JWT, ни строкya29./sk-. Это состояние надо удержать механически, а не обещанием. - Никаких секретов в 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не создавать.
Критерии приёмки
- Ветка в
origin, финальный коммит виден,git statusчист. GET /api/snapshotотдаёт снапшот, совпадающий по структуре сdocs/web-api/snapshot.example.json; проверено тестом сравнения ключей.POST /api/actionпринимает все семнадцать действий; отказ возвращается как200сok: false; неизвестное действие —4xx.- Действия исполняются через общий путь; второй реализации семнадцати действий в проекте нет.
- Долгие операции не держат запрос; результат приходит следующим снапшотом.
- Сервер по умолчанию слушает
127.0.0.1; на внешнем адресе без токена отказывается стартовать; проверено тестом. - Есть тест, падающий при появлении в ответе
access_token,refresh_token,api_key, JWT или строкya29./sk-. - Ни один модуль не читает
LOCALAPPDATAв обходpaths.py; проверено тестом с заданнымHERMES_HOME. - Доступность потоков авторизации сообщается клиенту с причиной; зафиксировано в контракте.
- В снапшоте различаются «данных нет» и «данные грузятся»; поле описано в контракте.
- Прогон в обоих окружениях;
ruff check .чисто; релизный гейт остаётся 7/7. - Отчёт:
START_HEAD,FINAL_HEAD,origin/main,git status, точныйX passed / Y skipped / Z failed, и отдельно — проверялось ли на настоящем Linux.
Порядок сдачи
Передать точный FINAL_COMMIT_SHA. Сдано только после появления коммита в origin.