From 36b449bc6ce9240546c73baf3427cf543a53c928 Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Mon, 24 Aug 2026 09:35:36 +0700 Subject: [PATCH] =?UTF-8?q?fix(installer):=20=D0=BE=D1=88=D0=B8=D0=B1?= =?UTF-8?q?=D0=BA=D0=B0=2012=20=D0=BF=D1=80=D0=B8=20=D1=83=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B5=20=E2=80=94=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D1=82=D1=80=D0=B5=D0=B1?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BB=D0=B0=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8?= =?UTF-8?q?=D0=B3=D1=83=D1=80=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=2020?= =?UTF-8?q?=20=D0=B0=D0=B2=D0=B3=D1=83=D1=81=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Владелец получил «Ошибка установки (Код: 12)» на чистой машине. Причина: scripts/verify_multi_provider_router.py, который установщик запускает после развёртывания, требовал РОВНО 16 профилей и дословно заданные цепочки ролей. Миграция из A9 законно доводит конфигурацию до 22 профилей, добавляя claude и grok. Проверено: скрипт падал с «Expected 16 profiles, got 22», то есть установка обрывалась на любой машине, где миграция отработала. Проверки переписаны структурными: есть ли профили у каждого провайдера, непусты ли цепочки ролей и ссылаются ли они только на существующие профили. Смысл проверки — работоспособна ли маршрутизация, а не совпадает ли конфигурация с зафиксированной когда-то. Скрипт проходит 10/10. Отдельно: код 12 возвращался и при отказе проверки, и из общего catch — владелец видел число без причины. Непредвиденный сбой отделён в код 15, обе ветки теперь пишут пояснение в интерфейс установщика. Тесты: 373 passed, ruff чисто. Установщик пересобран. Co-Authored-By: Claude Opus 5 --- installer/HermesHubSetup.cs | 13 ++++++-- launcher/HermesHub.exe | Bin 6144 -> 6144 bytes launcher/HermesHubWeb.exe | Bin 11776 -> 11776 bytes scripts/verify_multi_provider_router.py | 40 +++++++++++++++--------- tests/fixtures/scratch_state.lock | 0 5 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 tests/fixtures/scratch_state.lock diff --git a/installer/HermesHubSetup.cs b/installer/HermesHubSetup.cs index 7d11d8a..4527a30 100644 --- a/installer/HermesHubSetup.cs +++ b/installer/HermesHubSetup.cs @@ -396,6 +396,12 @@ namespace HermesHubSetup p.WaitForExit(10000); if (p.ExitCode != 0) { + // Код 12 сам по себе ничего не объясняет: раньше он + // возвращался и отсюда, и из общего catch. Владелец + // видел «Ошибка установки (Код: 12)» без причины. + if (progressCallback != null) + progressCallback("Проверка маршрутизации не прошла. Подробности: " + + verifyScript, 0); return 12; // Router Verification failed } } @@ -406,8 +412,11 @@ namespace HermesHubSetup } catch (Exception ex) { - if (progressCallback != null) progressCallback("Error: " + ex.Message, 0); - return 12; + // Отдельный код для непредвиденного сбоя: смешивать его с + // отказом проверки маршрутизации значит лишать владельца + // возможности отличить одно от другого. + if (progressCallback != null) progressCallback("Ошибка установки: " + ex.Message, 0); + return 15; } } diff --git a/launcher/HermesHub.exe b/launcher/HermesHub.exe index 11a17cb64fd0e5928d12031bebe1fee130225386..4b9176e65f77f1428b95da6144eb1e650668e743 100644 GIT binary patch delta 34 qcmZoLXfT-2!E|fw#;(&m{GD^$4b;xLEp-aJqmuMTadQgq94-I@?+$+e delta 34 qcmZoLXfT-2!E|!v#;(&m{9z1|_y7I&Ty*N7P<~@i(dHE1Ia~k`I}eor diff --git a/launcher/HermesHubWeb.exe b/launcher/HermesHubWeb.exe index d7c22ddae5925611a2026100fde4b090b36d94e3..9f1b56626c55356e3b6ae7b091c9fd6507d7bbef 100644 GIT binary patch delta 77 zcmV-T0J8spT!37VhyvQJv5NN+BsWGvGc+(ZH!U_sIXNvfGcYtQK{!P@EjckYG&VOh jH8(OsMzf_8(+?15K(l|DZv;cA>5)`tMRLEhh$DCk<98Z6 delta 77 zcmV-T0J8spT!37Vhyux}v5NN+Br!KdHZnOwMlD4`LNqNjMK>}nK{GTtEjTqpHbyZ- jK{Q1)Gqa@=(+?2ODdrcn>vB$`NrlGo+D=2Wh$DCk0Wurj diff --git a/scripts/verify_multi_provider_router.py b/scripts/verify_multi_provider_router.py index c4bdb73..a7e6087 100644 --- a/scripts/verify_multi_provider_router.py +++ b/scripts/verify_multi_provider_router.py @@ -43,24 +43,36 @@ def run_checks() -> int: # 1. Config inventory print("1. Checking profile inventory and provider counts...") config = get_default_router_config() - assert len(config.profiles) == 16, f"Expected 16 profiles, got {len(config.profiles)}" - codex_count = sum(1 for p in config.profiles.values() if p.provider == "openai-codex") - ag_count = sum(1 for p in config.profiles.values() if p.provider == "antigravity") - opengo_count = sum(1 for p in config.profiles.values() if p.provider == "opencode-go") - assert codex_count == 3, f"Expected 3 Codex profiles, got {codex_count}" - assert ag_count == 10, f"Expected 10 Antigravity profiles, got {ag_count}" - assert opengo_count == 3, f"Expected 3 OpenCode Go profiles, got {opengo_count}" - print(f" [PASS] 16 profiles registered ({codex_count} Codex, {ag_count} Antigravity [7 active, 3 cold], {opengo_count} OpenCode Go)") + # Проверка структурная, а не пересчёт. Раньше здесь стояло «ровно 16 + # профилей» и дословные цепочки от 20 августа. Миграция законно довела + # конфигурацию до 22 профилей, добавив claude и grok, — и установка стала + # падать с кодом 12 на любой машине, где миграция отработала. Смысл этой + # проверки в том, работоспособна ли маршрутизация, а не совпадает ли + # конфигурация с зафиксированной когда-то. + counts = {} + for prof in config.profiles.values(): + counts[prof.provider] = counts.get(prof.provider, 0) + 1 + assert config.profiles, "В конфигурации нет ни одного профиля" + for required in ("openai-codex", "antigravity", "opencode-go"): + assert counts.get(required), f"Нет ни одного профиля провайдера {required}" + summary = ", ".join(f"{prov}: {n}" for prov, n in sorted(counts.items())) + print(f" [PASS] Профилей: {len(config.profiles)} ({summary})") passed += 1 # 2. Role Fallback Chains print("2. Checking role fallback policies...") - assert "orchestrator" in config.roles - assert config.roles["orchestrator"].preferred_chain == ["codex-orch", "ag-orch-fallback", "opengo-3"] - assert config.roles["coder-primary"].preferred_chain == ["codex-worker-1", "ag-w1", "opengo-3"] - assert config.roles["reviewer"].preferred_chain == ["codex-worker-2", "opengo-2", "ag-w2"] - assert config.roles["research"].preferred_chain == ["opengo-1", "ag-w3", "ag-w4"] - print(" [PASS] All 6 logical role fallback chains validated") + assert "orchestrator" in config.roles, "Роль orchestrator отсутствует" + # Цепочки настраиваются владельцем и меняются — дословно их сверять нельзя. + # Проверяем то, что действительно ломает маршрутизацию: цепочка непуста и + # каждый профиль в ней существует. + for role_name, policy in config.roles.items(): + chain = policy.preferred_chain or [] + assert chain, f"У роли {role_name} пустая цепочка отказоустойчивости" + for pid in chain: + assert pid in config.profiles, ( + f"Роль {role_name} ссылается на несуществующий профиль {pid}" + ) + print(f" [PASS] Цепочки {len(config.roles)} ролей ссылаются только на существующие профили") passed += 1 # 3. Model family extraction diff --git a/tests/fixtures/scratch_state.lock b/tests/fixtures/scratch_state.lock new file mode 100644 index 0000000..e69de29