# Полный аудит Hermes Hub и бэклог доработок **Дата аудита:** 2026-08-20 **Проверенная ревизия:** `origin/main` @ `5ccfd48` (github.com/ochenstarik-ui/hermes-hub, private) **Метод:** статический разбор всех исходников + прогон тестов на чистом checkout `origin/main` в отдельном worktree **Версия:** 0.1.0 (`pyproject.toml`, `compatibility.json`) против 1.3.0 (`about_view.py`, `gui_server.py`) **Объём:** ~19 500 строк (Python ~15k, C# 0.8k, HTML 0.85k, PS1/BAT 0.25k, docs/config ~2.5k) > Первая редакция этого документа делалась по локальному снимку `fdf9ecc` (на 3 коммита старше). Все пункты ниже перепроверены по `origin/main`; раздел 2 фиксирует, что именно уже исправлено в `7609ad8`. --- ## 1. Общая оценка Ядро маршрутизации (`router_engine`, `health_tracker`, `session_affinity`, адаптеры) спроектировано грамотно: чистые слои, dataclass-модели, потокобезопасность, детерминированные тесты с моками. Коммит `7609ad8` («product stabilization») закрыл заметную часть проблем презентационного слоя — появился кэш сканирования, строгий resolver статусов, идентичность приложения в Windows, предпрогрев экранов. Тем не менее **продукт остаётся неработоспособным как приложение**: - нативное приложение не запускается на чистой машине — ни один установщик не ставит зависимости UI; - failover для основного провайдера (Antigravity) не срабатывает никогда из-за формата ошибок `agy_generate`; - три пользовательских сценария падают с `AttributeError`/`NameError`: удаление credentials, подключение аккаунта по API-ключу, перераспределение ролей; - настройки не сохраняются и ни на что не влияют; - «roadmap»-модули (+819 строк) не подключены ни к одному кодовому пути. **Фактический прогон тестов на чистом checkout `origin/main`:** `1 failed, 28 passed, 2 skipped` + 1 ошибка сбора (`test_ui_refinement.py` не импортируется без `customtkinter`). Отчёт `agents/done/…stabilization…md` заявляет «35/35 PASSED» — это верно только на машине, где `dist/` существует локально и зависимости доустановлены руками. **Зрелость:** ядро — Beta, оболочка — Alpha, упаковка — не готова к дистрибуции. --- ## 2. Что уже исправлено в `7609ad8` (не требует работы) | Было в аудите | Статус в `origin/main` | |---|---| | `TeamView.update_data(app_state)` требует аргумент → главный экран не обновляется | **Исправлено** — сигнатура унифицирована (`app_state=None`) во всех view | | `scan_all()` вызывается 9–10 раз за один refresh | **Исправлено** — TTL-кэш 30 с (`unified_health.py:248`) | | Ложные `HEALTHY`/`QUOTA_EXHAUSTED` у неподключённых слотов | **Исправлено** — введён `STATUS_NOT_CONFIGURED`, устаревшие записи квот чистятся | | Иконка Python в панели задач | **Исправлено** — `SetCurrentProcessExplicitAppUserModelID("HermesHub.Desktop")` + multi-res `.ico` | | Лаг переключения вкладок | **Исправлено** — предпрогрев всех 8 экранов в `__init__`, переключение через `pack_forget()` | | Установщик не копирует `assets/branding` | **Исправлено** в новом `installer/HermesHubSetup.py` (в `.cs` и `install.ps1` — нет) | | Нет документации по разработке/безопасности/производительности | **Исправлено** — добавлены `docs/AUTH.md`, `DEVELOPMENT.md`, `PERFORMANCE.md`, `ROUTER.md`, `SECURITY.md` | | Нет адаптивности окна | **Исправлено** — debounce-обработчик resize | --- ## 3. P0 — Блокеры (перепроверено на `origin/main`, всё воспроизводится) | # | Дефект | Файл | Последствие | |---|---|---|---| | 1 | `customtkinter` и `pillow` не устанавливаются ни `HermesHubSetup.py`, ни `HermesHubSetup.cs`, ни `install.ps1` | все три установщика | Запуск через `pythonw.exe` завершается молча: ни окна, ни ошибки. Проверено: в `%LOCALAPPDATA%\hermes\hermes-agent\venv` пакета нет | | 2 | `ProfileAuthManager.get_profile_dir` не существует — это модульная функция, а не метод класса | `profile_manager.py:79` vs `gui_server.py:216`, `hermes_hub_app.py:120`, `add_account_wizard.py:200` | `AttributeError` при удалении credentials и при сохранении API-ключа | | 3 | `json` не импортирован, но используется | `ui/add_account_wizard.py:200` | `NameError` → **Codex и OpenCode Go нельзя подключить через UI вообще** | | 4 | `AutoAssigner.auto_assign_all` не существует | `hermes_hub_app.py:411`, `team_view.py:188,243` | Кнопки «Перераспределить роли» и «⋮» падают | | 5 | `agy_generate` возвращает ошибки как успешный `chat.completion`: текст ошибки в `content`, ключа `error` нет | `agy_subprocess.py:584` + `router_engine.py` | `RouterEngine` проверяет `if "error" in response` → не срабатывает → `mark_success`, **failover для Antigravity не работает никогда**, пользователь получает текст ошибки вместо ответа модели | | 6 | Мастер ждёт `session.status == "error"`, сессия ставит `"failed"` | `add_account_wizard.py:243` vs `profile_oauth.py` | При ошибке OAuth мастер молча висит 120 с | | 7 | Кнопка «Назначить» шлёт action `assign_role`, обработчика нет | `accounts_view.py:244` vs `hermes_hub_app._handle_action` | Кнопка не делает ничего, без обратной связи | | 8 | Шаг 4 мастера не применяет выбранную роль — только пишет в журнал | `add_account_wizard.py:_finish` | Выбор пользователя игнорируется | | 9 | Шаг 3 показывает «✓ Аккаунт успешно проверен» без всякой проверки для API-ключей; список моделей захардкожен (`gpt-4.1`, `qwen-coder`, `deepseek-v2`) | `add_account_wizard.py` | Ложноположительная валидация, вымышленные модели | --- ## 4. P1 — Производительность и отзывчивость UI | # | Проблема | |---|---| | 10 | `_restore_status()` вызывает `get_system_readiness()` **в UI-потоке** через `after(6000, …)`. При холодном/истёкшем кэше — блокировка окна на время сетевого скана 16 профилей | | 11 | При промахе кэша `scan_all()` опрашивает 16 профилей последовательно (таймаут 10 с на профиль) — нет параллелизма и общего бюджета времени | | 12 | 5 экранов (`accounts`, `health`, `providers`, `routing`, `logs`) полностью уничтожают и пересоздают виджеты через `winfo_children()` + `destroy()` — мерцание, потеря позиции скролла | | 13 | Нет периодического автообновления: данные устаревают до ручного нажатия «Обновить» (таймер только для resize) | | 14 | Тост затирает строку состояния и восстанавливается блокирующим вызовом | --- ## 5. P1 — Логика роутера и корректность состояний Файлы `router_engine.py`, `health_tracker.py`, `agy_subprocess.py`, `profile_manager.py`, `router_config.py`, `session_affinity.py`, `hermes_plugin.py` в `7609ad8` **не менялись** — все находки в силе. | # | Проблема | Файл | |---|---|---| | 15 | `_CM_LOCK` удерживается всё время subprocess `agy` (до 180+ с) → все Antigravity-профили сериализуются, параллельная работа команды невозможна | `antigravity_adapter.py` | | 16 | Профильные токены пишутся в **глобальную** запись `gemini:antigravity` Credential Manager и не восстанавливаются → «основной аккаунт Hermes» меняется как побочный эффект маршрутизации; гонки с самим `agy` и Hermes CLI | `antigravity_adapter.py` | | 17 | `overall_state` не сбрасывается при истечении cooldown (только в `mark_success`) → профиль остаётся «Квота исчерпана» бессрочно | `health_tracker.py:is_healthy` | | 18 | Роутер решает по состоянию семейства моделей, UI — по `overall_state`: роутер шлёт запросы в профиль, который UI показывает мёртвым | `health_tracker.py` / `unified_health.py` | | 19 | Session affinity без TTL и вытеснения — неограниченный рост памяти; `session_affinity_ttl_seconds` из шаблона конфига не реализован | `session_affinity.py` | | 20 | `router_state.json` пишется без межпроцессной блокировки — GUI и процесс Hermes затирают состояние друг друга | `health_tracker.py` | | 21 | `ProfileHealthRecord.active_leases` всегда 0, но выводится в CLI и API | `health_tracker.py`, `cli_commands.py` | | 22 | Категория `TRANSIENT` классифицируется, но обрабатывается как обычный отказ — без повтора и задержки | `router_engine.py` | | 23 | Классификация ошибок по подстрокам (`"429" in err_lower`) — ложные срабатывания на любом тексте с «429» | все адаптеры | | 24 | `_safe_env()` не используется: `custom_env = dict(os.environ)` передаёт в subprocess `agy` все ключи (`OPENAI_API_KEY` и пр.) | `agy_subprocess.py`, `antigravity_adapter.py` | | 25 | Нет стриминга ответов — Hermes получает только полный ответ | `router_engine.py` | | 26 | Нет учёта токенов, стоимости и latency между вызовами | — | | 27 | В шаблоне `router_profiles.example.yaml` блок `router:` полностью игнорируется загрузчиком (ожидаются ключи верхнего уровня); `max_failover_attempts`, `cooldown_base_seconds`, `cooldown_max_seconds`, `session_affinity_ttl_seconds` отсутствуют в схеме | `router_config.py:load_router_config` | | 28 | `save_router_config` пишет другую структуру, чем шаблон → первое сохранение («назначить оркестратором») уничтожает комментарии и структуру пользовательского YAML | `router_config.py` | | 29 | `max_concurrency=1` для Codex и OpenCode во встроенном дефолте, хотя это stateless REST (в шаблоне 2 и 3) — искусственное ограничение пропускной способности | `router_config.py` | | 30 | `antigravity_llm_execution` перехватывает **все** вызовы `llm_execution` независимо от провайдера и никогда не вызывает `next_call`; ошибка роутера логируется на уровне `debug` | `hermes_plugin.py` | | 31 | Каталог моделей захардкожен (`gemini-3.7-flash`, `qwen3.8-max`, `kimi-k2.7-code`…) и не сверяется с реальным | `router_config.py` | | 32 | `discover_models()` определяет каталог, вызывая `agy` с заведомо невалидной моделью и парся текст ошибки | `agy_subprocess.py` | | 33 | `AutoAssigner.check_duplicate_identity` сравнивает `auth_data["email"]`, но `profile_oauth` сохраняет только `{"token", "auth_method"}` → **детектор дубликатов не срабатывает никогда** | `auto_assigner.py` | --- ## 6. P1 — Безопасность | # | Проблема | |---|---| | 34 | `CLIENT_ID`/`CLIENT_SECRET` Google зашиты в код и **склеены из фрагментов строк** (`"GOC" + "SPX" + …`, `oauth.py:26-28`) — приём обхода сканеров секретов, чужой OAuth-клиент. Требует явного решения: собственный клиент или документированное заимствование. Отчёты «0 секретов при сканировании» проходят именно из-за склейки | | 35 | `ProfileAuthManager.save_profile_auth` пишет токены plaintext без ограничения ACL. `docs/SECURITY_MODEL.md` заявляет изоляцию «правами текущего пользователя ОС», но код прав не выставляет (`chmod` есть только в `CredentialStore` и на Windows не работает) | | 36 | `access_token` передаётся в query-строке при верификации (`tokeninfo?access_token=…`) — попадает в логи | | 37 | `CORSMiddleware(allow_origins=["*"], allow_credentials=True)` на локальном API с эндпоинтами `delete`, `set-main`, `oauth/start` (`gui_server.py`) | | 38 | Лаунчер генерирует и исполняет Python-скрипт `%LOCALAPPDATA%\hermes\hermes_hub_entry.py` при каждом запуске — вектор подмены между записью и стартом | | 39 | Нет подписи установщика и лаунчера; `dist/checksums.txt` формируется вручную и не проверяется при установке | | 40 | В `hermes-hub.log` пишутся тексты ошибок провайдеров без санитизации (могут содержать фрагменты токенов) | --- ## 7. P1 — Тесты и качество | # | Проблема | |---|---| | 41 | `pytest tests/` **падает на сборе**: `test_ui_refinement.py` → `hermes_hub_app` → `customtkinter` | | 42 | `test_setup_exe_exists` **падает на чистом checkout**: `dist/` в `.gitignore`, ещё 2 теста уходят в skip. Реальный результат — `1 failed, 28 passed, 2 skipped`, а не заявленные 35/35 | | 43 | Тесты мутируют реальные пользовательские данные: `do_set_main` пишет `router_active_profile.json`, `do_set_orchestrator` перезаписывает `router_profiles.yaml`, `get_profile_env_dir` создаёт каталоги профилей. Нет фикстуры с временным `HERMES_HOME` | | 44 | `test_installer.py` **запускает реальный установщик**: копирование в `%LOCALAPPDATA%`, ярлык, запись в реестр | | 45 | Тесты ходят в сеть (Google tokeninfo, OpenCode `/models`) — недетерминированность | | 46 | Нет тестов на: `classify_error` всех адаптеров, мастер подключения, YAML round-trip `load↔save`, CLI, логику установщиков | | 47 | Нет CI (`.github/workflows` отсутствует, репозиторий приватный) | | 48 | `ruff` в dev-зависимостях, но нет секции `[tool.ruff]` и запуска; нет `mypy` | --- ## 8. P1 — Упаковка, установка, дистрибуция | # | Проблема | |---|---| | 49 | **Два параллельных установщика**: `installer/HermesHubSetup.cs` (711 строк, GUI-мастер, silent-режим, реестр, коды 0/10/11/12) и `installer/HermesHubSetup.py` (101 строка, копирует assets, но без реестра, ярлыка, silent-кодов). Неясно, какой канонический; `dist/HermesHubSetup.exe` собран из `.cs` | | 50 | Проверка совместимости фиктивна: `compatibility.json` не читается, `MIN_HERMES_VERSION` ни с чем не сравнивается, `IsHermesCompatible = true` всегда при наличии Python → **код возврата 11 недостижим**. То же в `install.ps1` (версия печатается, но не проверяется) | | 51 | `installer/HermesHubSetup.cs` и `scripts/install.ps1` не копируют `assets/branding` (исправлено только в `.py`-варианте) | | 52 | `ui/assets.py:39` — захардкоженный fallback `Path("E:/Agent projects/hermes-hub")` в исходниках | | 53 | `build_launcher.ps1` не передаёт `/win32icon:HermesHub.ico` → у самого `.exe` нет иконки (иконка ставится только в рантайме через `iconbitmap`) | | 54 | `install.ps1` копирует `__pycache__` | | 55 | `launcher/HermesHub.exe` — собранный бинарник в git; собранный установщик, наоборот, не отслеживается | | 56 | Нет single-instance guard — повторный клик по ярлыку открывает второе окно | | 57 | Нет логирования запуска: при `pythonw.exe` любая ошибка импорта = полная тишина | | 58 | Нет механизма обновления в самом приложении | | 59 | Версия расходится: `0.1.0` (pyproject, compatibility.json, installer) против `1.3.0` (About, FastAPI) | | 60 | `scripts/launch_hermes_hub.bat` запускает несуществующий веб-режим на `:8765`; CLI-аргументы `hub --port/--no-browser` парсятся и игнорируются | --- ## 9. P2 — Мёртвый и неподключённый код | # | Объект | Строк | Статус | |---|---|---|---| | 61 | `supervisor/lifecycle_supervisor.py`, `supervisor/policies.py`, `capability/capability_matrix.py`, `skills/skill_registry.py`, `scheduler/task_safety.py`, `adapters/deepseek_adapter.py` | 819 | **Roadmap-модули из `7609ad8` не подключены ни к одному кодовому пути.** Импортируются только тестами (`task_safety` и `deepseek_adapter` — вообще ничем). `DeepSeekResponsesAdapter` не зарегистрирован в `_ADAPTERS` → недостижим через `get_adapter()` | | 62 | `gui_server.py` + `gui_cockpit.html` | 1177 | Параллельный веб-стек, не запускается ниоткуда; `fastapi`/`uvicorn` остаются обязательными зависимостями | | 63 | `runtime.generate_chat_completion`, `antigravity_client.py`, `transform.py`, `openai_compat.py`, `cloudcode.py` | ~590 | Прямой API-путь к Antigravity, не используется (весь трафик через `agy` CLI) | | 64 | `ui/views/dashboard_view.py` («Главная») | 141 | Не подключён к навигации | | 65 | `ui/splash.py` (`SplashScreen`) | 77 | Не используется | | 66 | `HealthTracker.get_status_summary()` | — | Не вызывается ниоткуда | | 67 | Резолвинг `hermes_home` | — | Продублирован в 5 файлах с тремя разными реализациями (`HERMES_HOME` / `LOCALAPPDATA` / `~/.hermes`) | | 68 | `do_set_main`/`do_test_profile`/`do_delete_credentials` | — | Дублируются между `gui_server.py` и `hermes_hub_app.py` | --- ## 10. P2 — Документация | # | Проблема | |---|---| | 69 | `docs/ARCHITECTURE.md` и `CHANGELOG.md` описывают «Edge App Mode», «FastAPI Backend :8765», «health check gate (HTTP 200)» — этой архитектуры больше нет | | 70 | `agents/AGENTS.md` и отчёты в `agents/done/` указывают Source of Truth `E:\Agent projects\hermes-hub`; ссылки вида `file:///E:/Agent%20projects/...` мертвы | | 71 | `docs/SECURITY_MODEL.md` не описывает фактическую подмену глобальной записи `gemini:antigravity` при маршрутизации | | 72 | Нет описания схемы `router_profiles.yaml` (единственный источник истины — код) и справочника CLI | | 73 | Отчёты в `agents/done/` содержат утверждения, не подтверждаемые кодом: «35/35 PASSED», «интерактивные переключатели параметров», «9/9 экранов», «иконка вшита в лаунчер». Требуется выравнивание с фактами | | 74 | UI жёстко на русском, CLI на английском, строки не вынесены — i18n невозможен | --- ## 11. P2–P3 — Функциональные пробелы | # | Пробел | |---|---| | 75 | Настройки не сохраняются: `CTkSwitch`/`CTkOptionMenu` не привязаны к `self.settings` (нет ни одного `command=`), `_save_settings()` пишет неизменённый словарь | | 76 | Ни один параметр не влияет на роутер: `failover_attempts`, `session_affinity`, `auto_failover`, `model_timeout_sec`, `monitoring_interval_min` нигде не читаются | | 77 | Пути в «Настройках» указывают на `hermes\router_profiles.yaml` и `hermes\auth.json`; реальные — `hermes\config\router_profiles.yaml` и `hermes\agy_profiles\\auth.json` | | 78 | Нет ручного назначения профиля на роль (только автоматическое) | | 79 | Экран «Маршрутизация» read-only: нельзя изменить цепочку failover | | 80 | Нельзя добавить или удалить слот — 16 профилей жёстко зашиты в коде | | 81 | Кнопка «Обновить модели» не вызывает `adapter.discover_models()` — показываются `preferred_models` из конфига | | 82 | Фоновый мониторинг здоровья отсутствует, хотя переключатель для него есть | | 83 | Нет уведомлений (tray/toast) об исчерпании квоты и переключении на резерв | | 84 | Нет экспорта/импорта конфигурации и резервного копирования профилей | | 85 | Нет истории квот и использования | | 86 | Журнал живёт в памяти (200 записей) и обнуляется при перезапуске; `hermes-hub.log` пишется, но не читается обратно | | 87 | В журнале нет поиска и фильтра по уровню (только по категории) | | 88 | Тема жёстко тёмная, нет клавиатурной навигации и доступности | | 89 | `compatibility.json` заявляет `linux`/`darwin`, но Credential Manager, пути и лаунчер — только Windows | --- ## 12. Рекомендуемый план работ ### Этап 1 — Вернуть работоспособность (P0, ~2 дня) 1. Устанавливать `customtkinter`/`pillow` в venv Hermes из установщика; проверять импорт после установки и падать с внятной ошибкой. 2. Исправить `get_profile_dir` (сделать `@staticmethod` либо импортировать функцию), добавить `import json` в мастер. 3. Реализовать `AutoAssigner.auto_assign_all()` либо убрать кнопки. 4. Ввести маркер ошибки в `agy_generate` (`{"error": …}` или исключение) и восстановить failover для Antigravity; покрыть регрессионным тестом. 5. Починить мастер: статус `"failed"`, реальная валидация API-ключа, реальные модели, применение выбранной роли, обработчик `assign_role`. 6. Прогонять `pytest` в изолированном `HERMES_HOME`; убрать запуск установщика и сетевые вызовы из тестов; сделать `test_setup_exe_exists` skip-ом при отсутствии `dist/`. ### Этап 2 — Корректность состояний и отзывчивость (P1, ~3 дня) 7. Все сетевые операции — только в фоне; `after(0, …)` получает готовые данные (включая `_restore_status`). 8. Параллельный опрос профилей с общим бюджетом времени вместо последовательного. 9. Обновление виджетов in-place вместо `destroy()`/пересоздания в 5 экранах. 10. Сброс `overall_state` при истечении cooldown; единый источник истины по здоровью для роутера и UI. 11. TTL и вытеснение в `SessionAffinityTracker`; файловая блокировка `router_state.json`. 12. Сузить `_CM_LOCK` до операции подмены credential и восстанавливать прежнюю запись — либо перейти на изоляцию только через `USERPROFILE`. 13. Связать `settings_view` с реальными параметрами роутера; выровнять пути. 14. Починить `check_duplicate_identity` (сохранять `email` в `auth.json` при OAuth). ### Этап 3 — Безопасность и упаковка (P1, ~2 дня) 15. Решение по OAuth-клиенту; прекратить склейку секрета из фрагментов. 16. ACL/DPAPI для `auth.json`; санитизация логов; убрать токен из query-строки. 17. Убрать `allow_origins=["*"]` либо удалить веб-стек целиком. 18. Выбрать один установщик; реальная проверка версии из `compatibility.json` (сделать код 11 достижимым); исключить `__pycache__`; убрать `E:/…` fallback; встроить иконку через `/win32icon`. 19. Логирование запуска в файл до инициализации UI; single-instance guard. ### Этап 4 — Долг и документация (P2, ~2 дня) 20. Подключить roadmap-модули к реальным кодовым путям (как минимум зарегистрировать `DeepSeekResponsesAdapter` в `_ADAPTERS`) либо явно пометить их как экспериментальные. 21. Удалить или вынести в `legacy/` веб-стек и прямой API-путь; убрать `fastapi`/`uvicorn` из обязательных зависимостей. 22. Единый модуль `paths.py`; устранить дублирование `do_*`. 23. Синхронизировать версию (0.1.0), обновить `ARCHITECTURE.md`/`CHANGELOG.md` под нативную архитектуру, исправить пути `E:\`. 24. Выровнять отчёты в `agents/done/` с фактическим состоянием. 25. Добавить CI (pytest + ruff) и конфигурацию ruff. ### Этап 5 — Развитие (P3) 26. Ручное назначение ролей и редактор цепочек failover. 27. Динамические слоты, реальный `discover_models`. 28. Фоновый мониторинг + уведомления о квотах. 29. Персистентный журнал с поиском; статистика использования. 30. Стриминг ответов, учёт токенов и стоимости. 31. Экспорт/импорт конфигурации, бэкап профилей. --- ## 13. Сводка | Приоритет | Пунктов | Область | |---|---|---| | **Исправлено в `7609ad8`** | 8 | Кэш сканирования, resolver статусов, идентичность Windows, предпрогрев, assets, docs | | **P0** — блокеры | 9 | Запуск приложения, failover Antigravity, падения UI, мастер подключения | | **P1** — критично | 51 | Отзывчивость, корректность состояний, безопасность, тесты, упаковка | | **P2** — важно | 20 | Неподключённый код, архитектурный долг, документация | | **P3** — развитие | 9 | Новая функциональность | | **Итого к работе** | **89** | |