diff --git a/installer/HermesHubSetup.cs b/installer/HermesHubSetup.cs index af141f2..4de074e 100644 --- a/installer/HermesHubSetup.cs +++ b/installer/HermesHubSetup.cs @@ -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"; diff --git a/launcher/HermesHub.exe b/launcher/HermesHub.exe index e966728..834ec37 100644 Binary files a/launcher/HermesHub.exe and b/launcher/HermesHub.exe differ diff --git a/launcher/HermesHubWeb.exe b/launcher/HermesHubWeb.exe index 57a0728..8ca172f 100644 Binary files a/launcher/HermesHubWeb.exe and b/launcher/HermesHubWeb.exe differ diff --git a/src/antigravity_provider/router/action_handler.py b/src/antigravity_provider/router/action_handler.py index f4cc6ca..1bc1ecc 100644 --- a/src/antigravity_provider/router/action_handler.py +++ b/src/antigravity_provider/router/action_handler.py @@ -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'): diff --git a/tests/test_native_login_registers_account.py b/tests/test_native_login_registers_account.py new file mode 100644 index 0000000..b8c5701 --- /dev/null +++ b/tests/test_native_login_registers_account.py @@ -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