From 187f181aeca8d3aeb2e5f959ecda209a842e1b84 Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Sun, 23 Aug 2026 22:36:35 +0700 Subject: [PATCH] =?UTF-8?q?docs(task):=20A20=20=E2=80=94=20=D0=B2=D0=BE?= =?UTF-8?q?=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?Antigravity=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20OAuth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Маршрутизация через Antigravity не работает ни для одного из шести аккаунтов при полностью валидных токенах: квоты по ним приходят настоящими через прямой HTTPS, а путь через CLI падает с AuthExpiredError. Причина найдена и проверена: agy читает /.gemini/oauth_creds.json с шестью полями, включая id_token и scope. Hub пишет свой auth.json в другом месте и другой структурой, а id_token и scope теряет в двух местах — oauth.py:88-92 и profile_oauth.py:241-249. Ни один профиль их не хранит. Проверено и не сработало: подмена HOME на каталог профиля, сборка oauth_creds.json без id_token. Значит id_token обязателен. Владелец решил остаться на OAuth, прямой API отклонён. Co-Authored-By: Claude Opus 5 --- .../2026-08-24-A20-agy-oauth-credentials.md | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 agents/inbox/2026-08-24-A20-agy-oauth-credentials.md diff --git a/agents/inbox/2026-08-24-A20-agy-oauth-credentials.md b/agents/inbox/2026-08-24-A20-agy-oauth-credentials.md new file mode 100644 index 0000000..5b19633 --- /dev/null +++ b/agents/inbox/2026-08-24-A20-agy-oauth-credentials.md @@ -0,0 +1,174 @@ +# Задание A20: восстановить работу Antigravity через OAuth + +## Дата поступления +2026-08-24 + +## База +Проверочный HEAD на момент выдачи: **`0c325ae`**. + +## Ветка +`antigravity/agy-oauth-credentials` + +## Приоритет +Выше всего остального. Сейчас **маршрутизация через Antigravity не работает ни для одного из шести аккаунтов** — это десять профилей из двадцати двух и основной провайдер владельца. + +--- + +## Порядок работы с git + +``` +cd <каталог репозитория>; git fetch origin --prune; git status +git checkout main; git pull --ff-only origin main +git checkout -b antigravity/agy-oauth-credentials +git commit -m "..." <- сначала коммит +git push -u origin antigravity/agy-oauth-credentials +``` + +В конце — push и проверка `git log --oneline -1 origin/antigravity/agy-oauth-credentials`, `git status` чистый. + +Прошлый раз работа была закоммичена, но не отправлена, и ветка в `origin` осталась пустой. Push без коммита и коммит без push одинаково бесполезны. + +--- + +## Решение владельца + +**Подключение остаётся через OAuth.** Переход на прямой API отклонён. Задание — починить OAuth, а не обойти его. + +--- + +## Что установлено + +Диагностика проведена целиком, повторять её не нужно. + +### Симптом + +``` +adapter.invoke(ag-w1) -> AuthExpiredError: agy error: authentication failed or timed out +agy models -> Error: Please sign in to view available models +``` + +При этом **квоты по тем же аккаунтам приходят настоящими**: `ag-w2` — 37.4% недельного пула Claude/GPT, `source=provider_api`. То есть OAuth-токены живые и валидные. Не работает именно путь через CLI. + +### Причина + +`agy` читает учётные данные из **`/.gemini/oauth_creds.json`**. Формат — проверен по рабочей глобальной сессии владельца: + +``` +access_token str +refresh_token str +scope str +token_type str +id_token str (длина ~1200, это JWT) +expiry_date int (миллисекунды) +``` + +Hub же пишет **`<профиль>/auth.json`** в другом месте и в другой структуре: + +``` +token.access_token, token.refresh_token, token.expiry, +token.expires_at, token.token_type, email, auth_method, project_id +``` + +Файла `oauth_creds.json` в каталогах профилей **нет ни у одного аккаунта**. Поэтому `agy`, запущенный с подменённым `HOME`, не видит входа и отвечает «please sign in». + +### Чего не хватает и где это теряется + +Проверено по всем шести профилям: **ни один не хранит `id_token` и `scope`**. + +Теряются они в двух местах: + +1. **`oauth.py:88-92`** — `refresh_access_token` возвращает только четыре поля: + +```python +return { + "refresh_token": data.get("refresh_token") or refresh_token, + "access_token": data["access_token"], + "expires_at": _expires_at(data.get("expires_in")), + "token_type": data.get("token_type", "Bearer"), +} +``` + +`id_token` и `scope` приходят от Google **в этом же ответе** и просто отбрасываются. + +2. **`profile_oauth.py:241-249`** — при первичном сохранении в `auth_data["token"]` кладутся только `access_token`, `refresh_token`, `expiry`. Ни `id_token`, ни `scope`, ни `token_type`. + +Косвенное подтверждение, что поле должно быть: `profile_manager.get_profile_status` для Antigravity читает `tokens.get("id_token")` и вызывает `extract_jwt_identity` — код рассчитывает на `id_token`, которого поток никогда не сохранял. + +### Что уже проверено и не сработало + +Чтобы вы не повторяли: + +- Запуск `agy models` с подменой `HOME`/`USERPROFILE` на каталог профиля — **не помогает**, файла с учётными данными там нет. +- Сборка `oauth_creds.json` из имеющихся полей с пустым `id_token` и подставленным `scope` — **не помогает**, `agy` по-прежнему требует вход. Значит одного `access_token` недостаточно, и `id_token` скорее всего обязателен. + +Правка ревьюера уже в `main`: `discover_models` теперь принимает `profile_id` и подменяет окружение, таймаут поднят с 10 до 60 секунд. Основание верное, но само по себе это симптом не лечит. + +--- + +## P0-1. Сохранять полный набор учётных данных + +1. **`oauth.py`**: `refresh_access_token` возвращает `id_token` и `scope` из ответа Google наряду с остальным. Не терять их и при повторном обновлении — если Google не вернул `id_token` в ответе на refresh, сохранять прежний, а не затирать пустым. +2. **`profile_oauth.py`**: при первичном сохранении класть в `token` весь набор — `access_token`, `refresh_token`, `id_token`, `scope`, `token_type`, `expires_at`, `expiry`. +3. Запрашивать в OAuth те же **scope**, что запрашивает сам `agy`. Если текущий набор уже, `id_token` может не прийти вовсе — сверьте со `scope` из рабочей глобальной сессии (`~/.gemini/oauth_creds.json`, поле длиной ~150 символов). + +**Тест:** после прохождения OAuth профиль содержит непустые `id_token` и `scope`; повторное обновление токена их не стирает. + +## P0-2. Писать `oauth_creds.json` в каталог профиля + +При каждом сохранении и обновлении учётных данных Hub обязан класть в `<профиль>/.gemini/oauth_creds.json` файл ровно в формате `agy`: + +- `expiry_date` — **миллисекунды**, не секунды. У Hub хранится `expires_at` в секундах, умножать на 1000; +- запись атомарная, через временный файл и `os.replace`. Это уже правило проекта: `do_save_settings` так и делает после того, как потеряла атомарность при переносе; +- права на файл — как у остальных хранилищ учётных данных, секрет не должен стать доступен шире. + +**Тест в песочнице:** сохранение профиля создаёт `oauth_creds.json` со всеми шестью полями; `expiry_date` в миллисекундах; обновление токена перезаписывает файл, а не плодит второй. + +## P0-3. Проверить, что заработало, — исполнением + +Задание принимается только с доказательством: + +1. `agy models` с подменённым окружением профиля возвращает непустой список — приложить вывод; +2. настоящий вызов модели через `adapter.invoke` возвращает ответ, а не `AuthExpiredError` — приложить; +3. `route_request` для роли, ведущей на Antigravity, отрабатывает без ухода в резерв по причине авторизации. + +Если после правки останется нужда в **однократном повторном входе** по каждому аккаунту (вероятно: у существующих профилей `id_token` не сохранён и взяться ему неоткуда) — **сказать об этом прямо и описать порядок для владельца**. Это законный исход: шесть аккаунтов один раз пройти мастер. Молча оставить шесть нерабочих профилей — нет. + +## P1-4. Обнаружение моделей после починки + +Когда `agy models` заработает, доделать то, что осталось от A18 и не было сделано: + +- кэш моделей **на диске**, переживающий перезапуск; +- обновление в фоне с таймаутом; таймаут **не затирает** прежний кэш; +- **ручная кнопка обновления** — владелец должен иметь возможность попробовать снова; +- при пустом кэше — «список моделей ещё не получен», без литеральных подстановок. + +Сейчас `set_model` отклоняет **любую** модель, включая настоящую, потому что сравнивать не с чем. Владелец хочет поставить двум кодерам `gemini-3.1-pro-high` и не может. + +--- + +## Ограничения + +- Ваши файлы: `oauth.py`, `router/profile_oauth.py`, `router/profile_manager.py`, `agy_subprocess.py`, `router/adapters/antigravity_adapter.py`, `model_discovery*`, `credentials.py`. +- **Учётные данные не логировать.** Ни токен, ни его часть, ни `id_token` не должны попасть в журналы, в снапшот и в веб-API. Тест на отсутствие секретов в ответе уже есть — он должен продолжать проходить. +- Десктоп и веб не ломать. На `main` сейчас 342 passed. +- Тег `v0.1.1` не создавать. + +## Критерии приёмки + +1. Ветка в `origin`, `git status` чист. +2. После OAuth профиль содержит непустые `id_token` и `scope`; обновление токена их не теряет; проверено тестом. +3. `oauth_creds.json` пишется в каталог профиля во всех шести полях, `expiry_date` в миллисекундах, запись атомарная; проверено тестом в песочнице. +4. **Приложен вывод `agy models`, вернувший непустой список** через профиль Hub. +5. **Приложен успешный реальный вызов модели** через `adapter.invoke`. +6. Если нужен однократный повторный вход — порядок для владельца описан в отчёте. +7. Секреты не попадают в журналы и в ответ веб-API; существующий тест на секреты проходит. +8. Кэш моделей на диске, ручное обновление, таймаут не затирает кэш. +9. `ruff check .` чисто; релизный гейт не ухудшен. +10. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. + +## Главное + +Продукт создан ради маршрутизации между аккаунтами Antigravity, и именно она сейчас не работает — при полностью валидных токенах. Всё остальное подождёт. + +## Порядок сдачи +Передать точный `FINAL_COMMIT_SHA`. Сдано только после появления коммита в `origin`.