From 681c899cfd421e413fff98a6623b0bf156717cb2 Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Tue, 25 Aug 2026 11:16:27 +0700 Subject: [PATCH] =?UTF-8?q?fix(web):=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D1=91=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=B0=D0=BA=D0=BA?= =?UTF-8?q?=D0=B0=D1=83=D0=BD=D1=82=20=D0=BD=D0=B5=20=D0=BF=D0=BE=D1=8F?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=BB=D1=81=D1=8F=20=D0=B2=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Владелец подключил первый аккаунт Antigravity на сервере, получил «Авторизация успешно завершена» — и не увидел его в разделе «Аккаунты». Учётные данные сохранялись правильно: save_profile_auth и load_profile_auth симметричны, get_profile_status после записи отдаёт authenticated=True — проверено исполнением. Не совпадало другое: состояние профилей берётся из кэша UnifiedHealthService, а фоновый цикл веб-сервера обновляет снапшот с force_scan=False и кэш не трогает. Измерено на изолированном каталоге: до входа ag-w1 -> not_configured сразу после входа ag-w1 -> not_configured после refresh(force=False) ag-w1 -> not_configured <- цикл делает это после refresh(force=True) ag-w1 -> not_tested То есть аккаунт не появился бы никогда, пока хаб не перезапустят. Теперь после успешного входа выполняется пересбор с force_scan=True — во всех четырёх точках завершения: device-flow, ручная вставка адреса, опрос redirect-потока и код Claude. Ошибка пересбора логируется и сам вход не роняет. Функция объявлена на уровне модуля: вложенной она была видна не всем точкам, и device-flow получал NameError внутри обработки успеха. Мой тест этого не поймал, потому что проверял только redirect-путь — нашла проверка ruff. Проверено: not_configured -> not_tested сразу после входа, без ручного обновления; путь device-flow исполняется без NameError. 432 passed, ruff чисто. Co-Authored-By: Claude Opus 5 --- .../router/action_handler.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/antigravity_provider/router/action_handler.py b/src/antigravity_provider/router/action_handler.py index b4201e8..b7f1e6d 100644 --- a/src/antigravity_provider/router/action_handler.py +++ b/src/antigravity_provider/router/action_handler.py @@ -236,6 +236,27 @@ def do_set_model(profile_id: str, model: str, role_id: Optional[str] = None) -> return False, "Не удалось сохранить файл конфигурации" +# Подключённый аккаунт обязан появиться в списке сразу. +# +# Учётные данные сохраняются на диск, но состояние профилей берётся из кэша +# UnifiedHealthService, а фоновый цикл веб-сервера обновляет снапшот с +# force_scan=False, то есть кэш не трогает. Проверено исполнением: после входа +# профиль оставался not_configured и при обычном refresh, и переходил в +# not_tested только при force_scan=True. Владелец видел «аккаунт подключён» и +# пустой список аккаунтов. +# +# Функция объявлена на уровне модуля намеренно: как вложенная она была видна +# не всем точкам завершения входа, и device-flow получал NameError внутри +# обработки успеха. +def _rescan_after_auth() -> None: + try: + from antigravity_provider.router.state_store import HubStateStore + + HubStateStore.get().refresh(force_scan=True) + except Exception as exc: # пересбор не должен ронять сам вход + logger.warning("Не удалось пересобрать снапшот после входа: %s", exc) + + class ActionExecutor: """Shared execution layer for Desktop and Web actions.""" @@ -310,6 +331,7 @@ class ActionExecutor: status = getattr(session, 'status', 'unknown') if status == 'completed': + _rescan_after_auth() return {'ok': True, 'message': 'Аккаунт подключён', 'data': {'status': status}} if status in ('failed', 'timeout'): reason = getattr(session, 'error_msg', None) or 'Авторизация не завершена' @@ -402,6 +424,8 @@ class ActionExecutor: if not session: return {'ok': False, 'message': 'Сессия авторизации не найдена или уже завершена'} ok, msg = session.handle_auth_code(raw_value) + if ok: + _rescan_after_auth() else: from antigravity_provider.router.profile_oauth import get_oauth_session @@ -409,6 +433,8 @@ class ActionExecutor: if not session: return {'ok': False, 'message': 'Сессия авторизации не найдена или уже завершена'} ok, msg = session.handle_manual_callback_url(raw_value) + if ok: + _rescan_after_auth() return {'ok': ok, 'message': msg} if action == 'poll_redirect_auth': @@ -420,6 +446,7 @@ class ActionExecutor: return {'ok': False, 'message': 'Сессия авторизации не найдена'} status = getattr(session, 'status', 'unknown') if status == 'completed': + _rescan_after_auth() return {'ok': True, 'message': 'Аккаунт подключён', 'data': {'status': 'completed'}} if status in ('failed', 'cancelled'): return {'ok': False, 'message': session.error_msg or 'Авторизация не удалась'}