fix(antigravity): вход через терминал не регистрировал аккаунт

Владелец: «в программе нет аккаунтов. я добавил первый… аккаунты так и не
появились». При этом вход проходил, и agy отдавал одиннадцать моделей.

Список аккаунтов строится по конфигурации маршрутизатора. Вход через терминал
завершается своим путём, мимо add_account, и записи в конфигурации не создаёт:
учётные данные на диске, get_profile_status отвечает «подключён», а в интерфейсе
пусто. Воспроизведено исполнением — профиль с ключами agy и нулём записей в
конфигурации не виден нигде.

Теперь при успешном завершении входа профиль регистрируется, и только потом
идёт обновление состояния. Неудача регистрации возвращается отказом с причиной,
а не молчаливым успехом: аккаунт, о котором сказано «подключён», но которого
нигде нет, — та же слепота, что и зависший мастер.

Проверено исполнением до и после правки: было «профилей в конфиге: 0, видно:
ничего», стало «профилей: 1 [ag-5], видно: antigravity[ag-5]».

708 passed, 2 skipped; ruff чисто; релизный гейт пройден.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-09-02 02:05:08 +07:00
parent 7b36538ded
commit 58cb88e529
5 changed files with 126 additions and 2 deletions

View file

@ -18,7 +18,7 @@ namespace HermesHubSetup
// Подставляется сборщиком из фактического git-коммита. Раньше здесь
// жил зашитый "8cddc9f", то есть манифест сообщал неправду о том, из
// какого кода собран установщик.
public const string BuildCommit = "90aeceb";
public const string BuildCommit = "e431e39";
public const string MIN_HERMES_VERSION = "0.20.0";
public const string MAX_TESTED_HERMES = "0.20.4";

Binary file not shown.

Binary file not shown.

View file

@ -952,7 +952,21 @@ class ActionExecutor:
ok, msg, res_data = poll_native_agy_login(session_id)
if ok and res_data.get('status') == 'completed':
_rescan_after_auth('antigravity', res_data.get('profile_id'))
slot = res_data.get('profile_id')
# Вход через терминал завершается своим путём и мимо add_account.
# Учётные данные при этом на диске, профиль числится
# подключённым — но записи о нём в конфигурации маршрутизатора
# нет, а список аккаунтов строится по ней. Владелец входил
# успешно и видел пустой экран.
if slot:
def_ok, def_msg = AutoAssigner.ensure_profile_definition('antigravity', slot)
if not def_ok:
return {
'ok': False,
'message': f'Вход выполнен, но аккаунт не зарегистрирован: {def_msg}',
'data': res_data,
}
_rescan_after_auth('antigravity', slot)
return {'ok': ok, 'message': msg, 'data': res_data}
if action in ('cancel_native_auth', 'cancel_native_agy_login', 'cancel_terminal_auth'):

View file

@ -0,0 +1,110 @@
"""Вход через терминал должен регистрировать аккаунт, а не только записывать ключи.
Владелец: «в программе нет аккаунтов. я добавил первый аккаунты так и не
появились». При этом вход проходил, agy отдавал одиннадцать моделей.
Причина: список аккаунтов строится по конфигурации маршрутизатора, а вход через
терминал завершается своим путём, мимо add_account, и записи в конфигурации не
создаёт. Учётные данные на диске, профиль числится подключённым и его нигде
не видно.
"""
from __future__ import annotations
import json
from unittest.mock import patch
import pytest
from antigravity_provider import paths
from antigravity_provider.router.action_handler import ActionExecutor
from antigravity_provider.router.router_config import load_router_config
from antigravity_provider.router.unified_health import UnifiedHealthService
def _profile_with_agy_credentials(slot: str = "ag-5"):
pdir = paths.get_profile_dir(slot, "antigravity", create=True)
cli = pdir / ".gemini" / "antigravity-cli"
cli.mkdir(parents=True, exist_ok=True)
(cli / "antigravity-oauth-token").write_text(
json.dumps(
{
"auth_method": "consumer",
"token": {"access_token": "ya29.TEST", "refresh_token": "1//TEST"},
}
),
encoding="utf-8",
)
return pdir
def _completed(slot: str = "ag-5"):
return (
True,
"Авторизация успешно завершена через agy CLI",
{"status": "completed", "profile_id": slot, "email": "owner@gmail.com"},
)
@pytest.mark.unit
def test_completed_login_registers_the_profile():
_profile_with_agy_credentials()
with patch(
"antigravity_provider.agy_subprocess.poll_native_agy_login",
return_value=_completed(),
):
res = ActionExecutor.execute("poll_native_auth", {"session_id": "s1"})
assert res["ok"], res
assert "ag-5" in load_router_config().profiles, (
"без записи в конфигурации аккаунт нигде не появится"
)
@pytest.mark.unit
def test_registered_profile_is_visible_in_the_interface():
_profile_with_agy_credentials()
with patch(
"antigravity_provider.agy_subprocess.poll_native_agy_login",
return_value=_completed(),
):
ActionExecutor.execute("poll_native_auth", {"session_id": "s1"})
shown = [
view.profile_id
for group in UnifiedHealthService.get().scan_all(force=True).values()
for view in group
]
assert "ag-5" in shown
@pytest.mark.unit
def test_failed_registration_is_reported_not_swallowed():
"""Молчаливый успех при незарегистрированном аккаунте — то же зависание вслепую."""
_profile_with_agy_credentials()
with patch(
"antigravity_provider.agy_subprocess.poll_native_agy_login",
return_value=_completed(),
), patch(
"antigravity_provider.router.auto_assigner.AutoAssigner.ensure_profile_definition",
return_value=(False, "слот занят другим провайдером"),
):
res = ActionExecutor.execute("poll_native_auth", {"session_id": "s1"})
assert not res["ok"]
assert "не зарегистрирован" in res["message"]
assert "слот занят другим провайдером" in res["message"]
@pytest.mark.unit
def test_login_still_in_progress_registers_nothing():
with patch(
"antigravity_provider.agy_subprocess.poll_native_agy_login",
return_value=(True, "Ожидание завершения авторизации в терминале...", {"status": "pending"}),
):
res = ActionExecutor.execute("poll_native_auth", {"session_id": "s1"})
assert res["ok"]
assert not load_router_config().profiles