hermes-hub/agents/inbox/2026-08-24-A20-agy-oauth-credentials.md
Hermes Team 187f181aec docs(task): A20 — восстановить Antigravity через OAuth
Маршрутизация через Antigravity не работает ни для одного из шести
аккаунтов при полностью валидных токенах: квоты по ним приходят
настоящими через прямой HTTPS, а путь через CLI падает с
AuthExpiredError.

Причина найдена и проверена: agy читает <HOME>/.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 <noreply@anthropic.com>
2026-08-23 22:36:35 +07:00

12 KiB
Raw Permalink Blame History

Задание 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 читает учётные данные из <HOME>/.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-92refresh_access_token возвращает только четыре поля:
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 в этом же ответе и просто отбрасываются.

  1. 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.