# Задание A34: восстановить подключение аккаунтов, добавить OpenRouter и NVIDIA, удалить десктоп ## Дата поступления 2026-08-30 ## База Работать **поверх ветки ревьюера** `review/a28-a31-fixes` (`529192b`), а не поверх `main` и не поверх своей прошлой ветки. В ней уже лежат A28–A31, слитые с `main`, плюс исправления ревьюера. ``` git fetch origin --prune git checkout -b antigravity/a34-restore-and-providers origin/review/a28-a31-fixes ``` В `main` напрямую не пушить. В конце — push, `git log --oneline -1`, `git status` чистый. ## Порядок исполнения Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-6** написан для аудитора. Пункты выполняются **по порядку**. P0-1 блокирует всё остальное: пока нельзя подключить аккаунт, ни новых провайдеров, ни автопоиска проверить не на чем. --- ## Что уже сделано ревьюером — не переделывать Снято исполнением на кандидате A28–A31 и исправлено в `529192b`: 1. **Дублирование ролей.** `RoleRegistry.migrate_legacy_roles` существовала, но не вызывалась ниоткуда; интерфейс показывал 19 агентов вместо 13, шесть пар неотличимы по названию. Миграция подключена, порядок обработки исправлен, цепочки владельца сохраняются. Проверено на его живой конфигурации: 19 → 13, цепочки совпадают. 2. **Сохранённый workflow** мигрируется вместе с ролями, иначе рёбра ссылались на исчезнувших агентов. 3. **Поиск локальных серверов** — новый модуль `router/local_discovery.py` и действие `discover_local_models`. Серверная часть готова и проверена; **не хватает только интерфейса** (P0-3). 4. **Глобальный мьютекс Antigravity** снят ранее (`7e83c38`): три параллельных вызова занимали 3.01 с, стали 1.00 с. Возвращать нельзя, есть тест. 5. **CORS** закрыт по умолчанию (`c35bc48`). Список источников — настройка `web_api_allowed_origins`. --- ## P0-1. Восстановить подключение аккаунтов — блокирующее При переписывании клиента в A29 **функции удалили, а вызовы оставили**. Проверено в браузере на живом кандидате, консоль: ``` openAddAccountWizard is not defined ← «+ Добавить аккаунт» ничего не делает checkUpdates is not defined ← падает при каждой загрузке страницы ``` Полный список повисших обработчиков — определений 0, вызовы есть: ``` openAddAccountWizard нельзя подключить аккаунт handleNodeAccountChange нельзя сменить аккаунт у агента в «Обзоре» handleNodeModelChange нельзя сменить модель handleRefreshProviderModels нельзя обновить список моделей checkUpdates проверка обновлений падает на загрузке ``` При этом `startDeviceAuth` и `startRedirectAuth` **в коде остались и работают**, но не вызываются ниоткуда — стали мёртвыми. Их надо не писать заново, а связать с восстановленным мастером. Требуется вернуть работоспособность каждому пункту списка. Действия на сервере существуют и менять их не нужно: ``` add_account, start_device_auth, poll_device_auth, start_redirect_auth, submit_redirect_callback, poll_redirect_auth, assign_role, set_model, refresh_models, check_updates ``` Что мастер обязан уметь, по провайдерам: - **Grok, OpenAI Codex** — код устройства. Адрес и код приходят **от провайдера**, подставлять свои нельзя. - **Antigravity, Claude** — вход по ссылке с возвратом. Ссылку можно открыть **на любой машине**. Принимается и полный адрес возврата, и один только код. При работе с другой машины показывается готовая команда проброса порта возврата. - **Локальный сервер** — адрес и необязательный ключ, плюс автопоиск из P0-3. - **Выбор слота обязателен и делается владельцем.** Автоподбор ошибается: `find_free_slot` определяет занятость по файлу учётных данных, а `agy` на Windows держит их в keyring, поэтому все слоты выглядят свободными и всегда возвращается первый. Вход затирал бы работающий аккаунт. В списке слотов видно, какие заняты и кем, и участвует ли слот в маршрутизации. **Проверять исполнением, а не глазами.** Откройте страницу, нажмите каждую кнопку, посмотрите консоль. Ни одного `ReferenceError` при загрузке и при работе. ## P0-2. OpenRouter и NVIDIA, аккаунтов по несколько Владелец подключил их в Hermes напрямую, мимо хаба: «главный кодекс стоит, подключил себе нвидеа и опенроутер и грок». Хаб их не видит — адаптеров нет, упоминаний в коде нет вовсе. Оба **OpenAI-совместимы**, поэтому образец есть: `adapters/local_adapter.py` и `adapters/deepseek_adapter.py` работают ровно так же — `POST {base_url}/chat/completions`, `GET {base_url}/models`. 1. **Адаптеры** `openrouter` и `nvidia`. Базовый адрес — **настройка, не константа**. Для OpenRouter это `https://openrouter.ai/api/v1`, у NVIDIA свой; но зашивать нельзя, владелец может использовать прокси. 2. **Несколько аккаунтов на провайдера**, без потолка. Потолок в A26 уже снят: `find_free_slot` выдаёт идентификаторы сама, когда предопределённые кончились (`codex-4`, `codex-5`). Сделать так же. 3. **Ключ вводится в мастере** и хранится там же, где ключи прочих провайдеров. В снапшот, в журнал и в `/api/settings` он попадать не должен — тест на это уже есть. 4. **Обнаружение моделей** через `GET /models`. У OpenRouter список большой; показывать надо тот, что вернул провайдер, а не подмножество из головы. 5. **Квоты.** OpenRouter отдаёт остаток кредитов, у NVIDIA свои лимиты. Отдаёт — показывать; не отдаёт — **«Н/Д» с причиной**, а не ноль и не пустая полоса, которую можно принять за исчерпание. 6. **Логотипы** провайдеров у владельца есть в каталоге с макетами. Файла нет — нейтральная заглушка, а не чужой знак. ## P0-3. Автопоиск локальных моделей в интерфейсе Серверная часть готова ревьюером, писать её заново не нужно. ``` router/local_discovery.py discover_local_servers() action_handler действие discover_local_models ``` Опрашивает Ollama 11434, LM Studio 1234, llama.cpp 8080–8082, vLLM 8000, Jan, GPT4All, Text Generation WebUI — параллельно, девять портов за 1.5 с. Возвращает только ответившие, со списком моделей от самого сервера. Требуется кнопка **«Найти на этом компьютере»** в шаге подключения локального провайдера: 1. Нажатие — опрос, показ найденного: имя сервера, адрес, список моделей. 2. Выбор найденного заполняет адрес; вводить руками по-прежнему можно. 3. **Ничего не найдено — так и сказать**, с подсказкой запустить Ollama, LM Studio или llama.cpp либо ввести адрес вручную. Пустой список это результат, а не ошибка. 4. Порт, занятый чужим сервисом, показывается **с причиной** — иначе владелец будет гадать, почему заведомо работающий сервер не виден. 5. Опрос идёт в фоне, интерфейс не блокируется. Учесть: хаб может работать на сервере, а браузер у владельца на другой машине. Поиск идёт **там, где работает хаб**, и это надо сказать в интерфейсе прямо, иначе результат будет непонятен. ## P0-4. Удаление десктопа (задание A32 целиком) Выполняется **после** P0-1: пока веб не подключает аккаунты, удалять десктоп нельзя. Полный текст — `agents/inbox/2026-08-25-A32-remove-desktop.md`, здесь коротко: ``` router/ui/** 20 файлов, ~6 910 строк hermes_hub_app.py ~1 345 строк, CustomTkinter cli_commands.py:481 команда запуска десктопа HermesHubSetup.cs:575 ярлык «Hermes Hub (Desktop).lnk» HermesHubSetup.cs:140 проверка зависимостей ТРЕБУЕТ customtkinter pyproject.toml:40 customtkinter>=6.0.0 ``` Сначала **таблица паритета** «умение десктопа → где в вебе → проверено», и только потом удаление. Непокрытое умение не удалять, а назвать в отчёте. Отдельно: в `b4ae08e` появился обход «GUI helpers importable without customtkinter». После удаления десктопа эта прослойка не нужна — снять её, а не оставлять. Обновление пойдёт **поверх установок с десктопом** на трёх машинах владельца: старый ярлык обязан убираться, учётные данные, настройки и цепочки ролей — уцелеть. ## P0-5. Проверка на живой установке 1. Собрать оба установщика, поставить на Windows и на Linux. 2. Подключить хотя бы по одному аккаунту каждого потока: код устройства, вход по ссылке, локальный сервер. 3. Разложить аккаунты по ролям и убедиться, что порядок переживает перезапуск. 4. Проверить обновление поверх старой установки. ## P0-6. Аудит вторым проходом 1. **Повисшие вызовы.** Собрать все `onclick`/`onchange` и убедиться, что каждая функция определена. Именно этот класс дефекта и пропустили в A29: разметка звала пять функций, которых нет. 2. **Консоль браузера чистая** при загрузке и при работе. 3. **Выдуманные значения.** Особый риск в P0-2: квоты и списки моделей новых провайдеров. Ни одного числа, которого не дал провайдер. 4. **Ключи не утекают** в снапшот, журнал и `/api/settings`. 5. **Мьютекс Antigravity не вернулся** — есть тест, он должен проходить. 6. **Побочные изменения** объяснить. 7. **Пропущенный пункт назвать пропущенным.** --- ## Ограничения - Не переделывать то, что перечислено в разделе «сделано ревьюером». - Без сборки, без npm, без фреймворка — решение обосновано в контракте. - Действия только через `action_handler`; новые — с правкой `docs/web-api/CONTRACT.md`. - Правило честности без исключений: нет данных — «Н/Д» и причина. - Тег `v0.1.1` не создавать. ## Критерии приёмки 1. Ветка в `origin` от `review/a28-a31-fixes`, `git status` чист. 2. В консоли браузера нет `ReferenceError` ни при загрузке, ни при работе; все обработчики определены — проверено списком. 3. Аккаунт подключается всеми тремя потоками; слот выбирает владелец, занятые видны. 4. Аккаунт назначается агенту и меняется модель — из «Обзора» и из «Маршрутизации»; переживает перезапуск. 5. OpenRouter и NVIDIA подключаются, аккаунтов больше трёх на провайдера; ключи не утекают; модели берутся у провайдера. 6. Квоты новых провайдеров показаны настоящие либо «Н/Д» с причиной. 7. Кнопка автопоиска находит запущенные локальные серверы; пустой результат объяснён; чужой сервис на порту назван с причиной. 8. `router/ui/**` и `hermes_hub_app.py` удалены, `customtkinter` из зависимостей убран, ярлык один; обновление поверх старой установки сохраняет данные. 9. Таблица паритета приложена. 10. `ruff check .` чисто; релизный гейт не ухудшен. 11. **Скриншоты:** мастер на каждом из трёх потоков, автопоиск локальных, «Обзор» с назначением аккаунта, «Маршрутизация». 12. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. На ветке ревьюера сейчас **475 passed, 2 skipped**. ## Главное Сейчас в новой сборке нельзя подключить ни одного аккаунта и нельзя назначить его агенту — разметка зовёт пять функций, которых в коде нет. Всё остальное в задании бессмысленно, пока это не восстановлено. ## Порядок сдачи Передать точный `FINAL_COMMIT_SHA`. Сдано только после появления коммита в `origin`.