From f269891271028cb9192588ef026543c6b8392dbe Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Tue, 1 Sep 2026 02:07:11 +0700 Subject: [PATCH] =?UTF-8?q?fix:=20agy=20=D0=BD=D0=B5=20=D0=BD=D0=B0=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=D0=BB=D1=81=D1=8F=20=D0=B2=20Linux;=20Ollama?= =?UTF-8?q?=20Cloud=20=D0=BD=D0=B5=D0=BB=D1=8C=D0=B7=D1=8F=20=D0=B1=D1=8B?= =?UTF-8?q?=D0=BB=D0=BE=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D0=BE=20=D0=BA=D0=BB=D1=8E=D1=87=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agy. Поиск проверял только раскладку Windows (%LOCALAPPDATA%/agy/bin) и PATH. В Linux утилита ставится в ~/.local/bin, а хаб запускается с урезанным окружением, где этого каталога в PATH нет. Владелец видел «agy executable not found» при установленной и работающей утилите — which agy находил её по адресу /home/ochenstarik/.local/bin/agy. Добавлены стандартные места Linux: ~/.local/bin, /usr/local/bin, /usr/bin, /snap/bin. Сообщение об отказе перечисляет проверенные пути. Отдельно различается «нет доступа к каталогу» и «файла нет». Ollama Cloud. Мастер требовал адрес локального сервера, которого у облачного аккаунта не существует: там только ключ. Теперь при заданном ключе недоступность локального адреса не считается отказом — аккаунт проверяется по каталогу ollama.com и принимается с честной оговоркой, что локальные модели недоступны. Проверено: 19 моделей каталога. Без ключа поведение прежнее. 609 passed, ruff clean, релизный гейт 10/10. Co-Authored-By: Claude Opus 5 --- src/antigravity_provider/agy_subprocess.py | 33 +++++++++++++---- .../router/connection_preflight.py | 35 +++++++++++++++++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/antigravity_provider/agy_subprocess.py b/src/antigravity_provider/agy_subprocess.py index bf59474..2961a29 100644 --- a/src/antigravity_provider/agy_subprocess.py +++ b/src/antigravity_provider/agy_subprocess.py @@ -46,12 +46,32 @@ def _find_agy_exe() -> str: if env and Path(env).is_file(): return env - # 2. Standard location based on hermes home parent + # 2. Стандартные места установки. + # + # Раньше проверялась только раскладка Windows (%LOCALAPPDATA%/agy/bin), а в + # Linux agy ставится в ~/.local/bin. Хаб запускается с урезанным окружением, + # где этого каталога в PATH нет, и вход в Antigravity падал с «agy executable + # not found» при установленной и работающей утилите. from antigravity_provider.paths import get_hermes_home + exe_name = "agy.exe" if os.name == "nt" else "agy" - candidate = get_hermes_home().parent / "agy" / "bin" / exe_name - if candidate.is_file(): - return str(candidate) + candidates = [ + get_hermes_home().parent / "agy" / "bin" / exe_name, + Path.home() / ".local" / "bin" / exe_name, + Path("/usr/local/bin") / exe_name, + Path("/usr/bin") / exe_name, + Path("/snap/bin") / exe_name, + ] + checked = [] + for candidate in candidates: + checked.append(str(candidate)) + try: + if candidate.is_file(): + return str(candidate) + except OSError as exc: + # Каталог может быть закрыт правами: это «не смогли проверить», + # а не «файла нет». + checked[-1] = f"{candidate} (нет доступа: {exc.strerror or exc})" # 3. PATH found = shutil.which("agy") or shutil.which("agy.exe") @@ -59,8 +79,9 @@ def _find_agy_exe() -> str: return found raise FileNotFoundError( - "agy executable not found. Set the AGY_EXE_PATH environment " - "variable, install agy, or ensure it is on PATH." + "Утилита agy не найдена. Проверено: " + + "; ".join(checked) + + "; и PATH процесса. Задайте путь переменной AGY_EXE_PATH либо установите agy." ) diff --git a/src/antigravity_provider/router/connection_preflight.py b/src/antigravity_provider/router/connection_preflight.py index e1baee9..951b716 100644 --- a/src/antigravity_provider/router/connection_preflight.py +++ b/src/antigravity_provider/router/connection_preflight.py @@ -97,6 +97,41 @@ def validate_connection(provider, token="", base_url="", preferred_model=""): except Exception as exc: exc_str = str(exc) exc_lower = exc_str.lower() + # У Ollama Cloud локального сервера нет вовсе: там только ключ. Если + # ключ задан, а локальный адрес не отвечает, это не отказ подключения — + # аккаунт облачный. Проверяем его по общедоступному каталогу и + # принимаем, честно сказав, что локальные модели недоступны. + if provider == "ollama" and token and any( + k in exc_lower for k in ("connection refused", "winerror 10061", "errno 111", "refused", "failed to connect", "target machine actively refused") + ): + try: + cloud = urllib.request.Request( + "https://ollama.com/api/tags", + headers={"Accept": "application/json", "Authorization": f"Bearer {token}"}, + ) + with urllib.request.urlopen(cloud, timeout=15) as response: + payload = json.load(response) + cloud_models = sorted({ + m.get("name") for m in (payload.get("models") or []) + if isinstance(m, dict) and isinstance(m.get("name"), str) + }) + return { + "ok": True, + "message": ( + f"Облачный аккаунт Ollama принят. Моделей в каталоге: {len(cloud_models)}. " + f"Локальный сервер по адресу {base_url} не отвечает — локальные модели недоступны." + ), + "data": {"models": cloud_models, "base_url": base_url, "cloud_only": True}, + } + except Exception as cloud_exc: + return { + "ok": False, + "message": ( + f"Локальный сервер {base_url} не отвечает, и облачный каталог Ollama недоступен: " + f"{str(cloud_exc)[:160]}" + ), + "data": {"models": []}, + } if provider == "ollama" and any(k in exc_lower for k in ("connection refused", "winerror 10061", "errno 111", "111", "refused", "failed to connect", "target machine actively refused")): return { "ok": False,