diff --git a/installer/HermesHubSetup.cs b/installer/HermesHubSetup.cs index ac8a6bf..8bb35b1 100644 --- a/installer/HermesHubSetup.cs +++ b/installer/HermesHubSetup.cs @@ -18,7 +18,7 @@ namespace HermesHubSetup // Подставляется сборщиком из фактического git-коммита. Раньше здесь // жил зашитый "8cddc9f", то есть манифест сообщал неправду о том, из // какого кода собран установщик. - public const string BuildCommit = "0ad946e"; + public const string BuildCommit = "b309972"; 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 0d53459..df3111f 100644 Binary files a/launcher/HermesHub.exe and b/launcher/HermesHub.exe differ diff --git a/launcher/HermesHubWeb.exe b/launcher/HermesHubWeb.exe index 71bd585..b60f253 100644 Binary files a/launcher/HermesHubWeb.exe and b/launcher/HermesHubWeb.exe differ diff --git a/scripts/verify_multi_provider_router.py b/scripts/verify_multi_provider_router.py index 35f1b2f..e082fd1 100644 --- a/scripts/verify_multi_provider_router.py +++ b/scripts/verify_multi_provider_router.py @@ -138,10 +138,14 @@ def run_checks() -> int: # 7. Antigravity profile isolation print("7. Checking Antigravity profile environment directory isolation...") + # Проверяем изоляцию пути, а не побочное создание каталога: запрос пути + # каталогов больше не плодит, иначе любая проверка засоряла бы диск + # десятком пустых слотов. pdir = get_profile_env_dir("ag-w2") - assert pdir.exists() assert "ag-w2" in str(pdir) - print(f" [PASS] Profile directory isolated at {pdir}") + assert "agy_profiles" in str(pdir) + assert not pdir.exists(), "запрос пути не должен создавать каталог" + print(f" [PASS] Profile directory isolated at {pdir} (не создан)") passed += 1 # 8. Error classification diff --git a/src/antigravity_provider/paths.py b/src/antigravity_provider/paths.py index e116928..f98dbb2 100644 --- a/src/antigravity_provider/paths.py +++ b/src/antigravity_provider/paths.py @@ -111,9 +111,21 @@ def get_compatibility_path() -> Path: return get_config_dir() / "compatibility.json" -def get_profile_dir(profile_id: str, provider: Optional[str] = None) -> Path: - """Return isolated storage directory for a profile. - +def get_profile_dir( + profile_id: str, provider: Optional[str] = None, create: bool = False +) -> Path: + """Вернуть каталог профиля. По умолчанию НИЧЕГО не создаёт. + + Раньше эта функция создавала каталог при каждом обращении. В коде зашит + список «стандартных» слотов — ag-orch-primary, ag-orch-fallback, ag-1..ag-20, + ag-w1..ag-w10, — и любой обход этого списка материализовал их на диске. У + владельца накопилось 25 каталогов, из которых он заводил единицы: остальные + появились сами, просто потому что кто-то спросил путь. + + Спрашивать, где профиль жил бы, и создавать его — разные действия. Создание + теперь запрашивается явно через create=True: так делают те, кто + действительно заводит профиль. + Accepts both (profile_id) and (provider, profile_id) or (profile_id, provider) gracefully. """ # If first argument looks like a provider or swapped, resolve cleanly @@ -151,7 +163,8 @@ def get_profile_dir(profile_id: str, provider: Optional[str] = None) -> Path: folder_prefix = f"{prov_lower}_profiles" d = get_hermes_home() / folder_prefix / p_id - d.mkdir(parents=True, exist_ok=True) + if create: + d.mkdir(parents=True, exist_ok=True) return d diff --git a/tests/test_a57_review_terminal_launch.py b/tests/test_a57_review_terminal_launch.py index ef7f555..06663ac 100644 --- a/tests/test_a57_review_terminal_launch.py +++ b/tests/test_a57_review_terminal_launch.py @@ -138,3 +138,30 @@ def test_immediate_exit_is_reported_as_failure(linux, monkeypatch, tmp_path): assert ok is False assert "окно не открылось" in msg assert data.get("exit_code") == 1 + + +# ── Пустые слоты появлялись сами ── + +def test_asking_for_a_path_creates_nothing(tmp_path, monkeypatch): + """У владельца накопилось 25 каталогов профилей при единицах заведённых. + + Причина: get_profile_dir создавал каталог при каждом обращении, а в коде + зашит список «стандартных» слотов — ag-orch-primary, ag-orch-fallback, + ag-1..ag-20, ag-w1..ag-w10. Любой обход этого списка материализовал их все. + Спросить, где профиль жил бы, и завести его — разные действия. + """ + from antigravity_provider import paths + + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + root = tmp_path / "agy_profiles" + + for pid in ("ag-orch-primary", "ag-w7", "ag-19", "ag-cold-3"): + paths.get_profile_dir(pid, "antigravity") + + assert not root.exists() or not any(root.iterdir()), ( + "запрос пути не должен оставлять каталоги на диске" + ) + + created = paths.get_profile_dir("ag-1", "antigravity", create=True) + assert created.is_dir() + assert sorted(p.name for p in root.iterdir()) == ["ag-1"] diff --git a/tests/test_agy_native_login.py b/tests/test_agy_native_login.py index 37f861c..af9f79f 100644 --- a/tests/test_agy_native_login.py +++ b/tests/test_agy_native_login.py @@ -116,7 +116,7 @@ def test_check_profile_native_auth_status(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) slot = "ag-w3" - pdir = get_profile_dir(slot, "antigravity") + pdir = get_profile_dir(slot, "antigravity", create=True) gemini_dir = pdir / ".gemini" gemini_dir.mkdir(parents=True, exist_ok=True) @@ -169,7 +169,7 @@ def test_multi_account_sequential_slot_progression(tmp_path, monkeypatch): # Connect first slot AutoAssigner.ensure_profile_definition("antigravity", first_slot) - pdir = get_profile_dir(first_slot, "antigravity") + pdir = get_profile_dir(first_slot, "antigravity", create=True) (pdir / "auth.json").write_text(json.dumps({"auth_method": "oauth", "email": "user1@gmail.com"})) # Next free slot @@ -178,7 +178,7 @@ def test_multi_account_sequential_slot_progression(tmp_path, monkeypatch): # Connect second slot AutoAssigner.ensure_profile_definition("antigravity", second_slot) - pdir2 = get_profile_dir(second_slot, "antigravity") + pdir2 = get_profile_dir(second_slot, "antigravity", create=True) (pdir2 / "auth.json").write_text(json.dumps({"auth_method": "oauth", "email": "user2@gmail.com"})) # Next free slot diff --git a/tests/test_multi_provider_router.py b/tests/test_multi_provider_router.py index e6e17cf..2e05e7d 100644 --- a/tests/test_multi_provider_router.py +++ b/tests/test_multi_provider_router.py @@ -238,10 +238,23 @@ class TestRouterEngineFailover: class TestAntigravityIsolation: """Test environment isolation for Antigravity profiles.""" - def test_profile_env_dir_creation(self): + def test_profile_env_dir_is_isolated_and_not_created_on_lookup(self): + """Запрос пути не должен создавать каталог. + + Прежде создавал, и любой обход списка стандартных слотов + (ag-orch-primary, ag-1..ag-20, ag-w1..ag-w10) материализовал их все: + у владельца накопилось 25 каталогов при единицах заведённых вручную. + """ pdir = get_profile_env_dir("ag-w1") - assert pdir.exists() assert "ag-w1" in str(pdir) + assert "agy_profiles" in str(pdir) + assert not pdir.exists(), "спросили путь — каталог появиться не должен" + + from antigravity_provider.paths import get_profile_dir + + created = get_profile_dir("ag-w1", "antigravity", create=True) + assert created.exists() + assert created == pdir class TestRouterCLI: