fix(antigravity): пустой вход выдавался за успешный; дата установки в интерфейсе

Вход. agy читает учётные данные из <каталог профиля>/.gemini/oauth_creds.json.
Хаб этот файл пишет, но при отсутствии токена доступа создавал его пустым, а
сбой записи только заносился в журнал. Владелец видел «подключено», а проверка
отвечала «Please sign in to view available models» — ровно это и случилось с
профилем ag-2 при заново подключённом victor.trushenko@gmail.com.

Теперь вход без токена доступа отвергается с причиной, а несостоявшаяся
запись учётных данных не позволяет считать аккаунт подключённым. Проверено:
данные без токена отклоняются, с токеном проходят.

Версия. Между сборками она не меняется намеренно, а коммит — строка из
шестнадцатеричных цифр, по которой на глаз не отличить старую сборку от
новой. В /api/settings добавлено время установки из манифеста, и оно
показывается рядом с версией: «Hermes Hub Web v0.1.2 · 01.09 04:10».

610 passed, ruff clean, релизный гейт 10/10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-09-01 03:17:01 +07:00
parent 9c57a7c607
commit 6af388a5dc
3 changed files with 40 additions and 4 deletions

View file

@ -123,6 +123,14 @@ class ProfileAuthManager:
access_token = token_info.get("access_token") or auth_data.get("access_token") or ""
refresh_token = token_info.get("refresh_token") or auth_data.get("refresh_token") or ""
# Файл без токена доступа бесполезен: agy на нём отвечает «Please sign in
# to view available models», а хаб считает аккаунт подключённым. Пустой
# вход не должен выдаваться за успешный.
if not access_token and not refresh_token:
raise ValueError(
"Вход не завершён: провайдер не вернул токен доступа. "
"Учётные данные agy не записаны."
)
scope = token_info.get("scope") or auth_data.get("scope") or ""
token_type = token_info.get("token_type") or auth_data.get("token_type") or "Bearer"
id_token = token_info.get("id_token") or auth_data.get("id_token") or ""
@ -314,7 +322,16 @@ class ProfileAuthManager:
try:
cls.write_agy_oauth_creds(pdir, auth_data)
except Exception as e:
logger.warning("Failed to write .gemini/oauth_creds.json for profile=%s: %s", profile_id, e)
# Прежде сбой оставался только в журнале, и владелец видел
# «подключено» при неработающем аккаунте. agy читает именно
# этот файл, поэтому без него подключения нет.
logger.error(
"Не записаны учётные данные agy для профиля %s: %s", profile_id, e
)
raise RuntimeError(
f"Учётные данные для agy не записаны ({e}). "
f"Аккаунт {profile_id} подключённым не считается."
) from e
# For Local and OpenAI-compatible providers, synchronize custom_base_url in router_profiles.yaml
if provider in ("local", "local-llm", "llama.cpp", "ollama", "vllm", "openrouter", "nvidia", "nvidia-nim"):

View file

@ -23,7 +23,7 @@ from antigravity_provider.router.state_store import HubStateStore
from antigravity_provider.router.action_handler import ActionExecutor
from antigravity_provider.router.router_config import load_router_config
from antigravity_provider.updater.update_manager import get_installed_commit, UpdateManager
from antigravity_provider.updater.update_manager import get_installed_commit, get_installed_build_time, UpdateManager
logger = logging.getLogger("hermes.router.web")
@ -410,6 +410,10 @@ def get_settings(authorized: bool = Depends(get_auth_token)):
"config_dir": system_paths["config_dir"],
"log_file": system_paths["log_file"],
"installed_commit": get_installed_commit(),
# Версия между сборками не меняется намеренно, а коммит — строка из
# шестнадцатеричных цифр, по которой трудно на глаз отличить старую
# сборку от новой. Время установки отвечает на этот вопрос сразу.
"installed_at": get_installed_build_time(),
"version": __version__,
"last_update_check": last_check.to_dict() if last_check else None,
"network_security": {

View file

@ -570,6 +570,21 @@ async function executeAction(actionName, actionData = {}) {
}
// ── GLOBAL HEADER ──
// Версия между сборками не меняется намеренно, а коммит — строка из
// шестнадцатеричных цифр. Дата установки отвечает на вопрос «старая сборка
// загрузилась или новая» сразу и без сверки коммитов.
function versionTagText(curVer) {
const installedAt = currentSettings && currentSettings.installed_at;
let stamp = '';
if (installedAt) {
const d = new Date(installedAt);
if (!isNaN(d.getTime())) {
stamp = ' · ' + d.toLocaleString('ru-RU', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
}
}
return curVer ? `Hermes Hub Web v${curVer}${stamp}` : 'Hermes Hub Web — Н/Д: версия не передана сервером';
}
function updateGlobalHeader() {
if (!currentSnapshot) return;
@ -631,7 +646,7 @@ function updateGlobalHeader() {
const curVer = (currentSnapshot && (currentSnapshot.version || (currentSnapshot.metrics || {}).version)) || (currentSettings && currentSettings.version) || '';
const versionTag = document.getElementById('version-tag');
if (versionTag) {
versionTag.textContent = curVer ? `Hermes Hub Web v${curVer}` : 'Hermes Hub Web — Н/Д: версия не передана сервером';
versionTag.textContent = versionTagText(curVer);
}
}
@ -2412,7 +2427,7 @@ function renderUpdateUI() {
}
const versionTag = document.getElementById('version-tag');
if (versionTag) {
versionTag.textContent = curVer ? `Hermes Hub Web v${curVer}` : 'Hermes Hub Web — Н/Д: версия не передана сервером';
versionTag.textContent = versionTagText(curVer);
}
if (statusBadge) {