Commit graph

64 commits

Author SHA1 Message Date
ochenstarik-ui
4860c5685e feat(agy): A58 read-only eligibility state detection and owner patcher controls 2026-09-02 01:26:46 +07:00
Hermes Team
90aeceb9fd fix(ui): уведомления шли без остановки и закрывали интерфейс
Владелец: «справа постоянно выходят статусы, прям без остановки. я вообще
ничего не вижу за ними».

Это был не таймер, а замкнутый круг. Отрисовка настроек запускала опрос
состояния сжатия; executeAction на успехе вызывал fetchSnapshot; тот снова
перерисовывал настройки — и так без конца. Интерфейс сам себя кормил запросами
к серверу, показывая на каждом обороте два тоста: на запрос и на ответ. Тем же
путём заливал «poll_native_auth» во время входа.

Опросы, которые запускает сам интерфейс, а не владелец, теперь молчат и не
дёргают снапшот — второе и разрывает круг. Отказ опроса показывается там, где
его запросили: у мастера входа для этого своя область сообщений.

Состояние сжатия запрашивается при открытии экрана настроек и по кнопке, а не
при каждой отрисовке.

700 passed, 2 skipped; ruff чисто; релизный гейт пройден.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 22:47:55 +07:00
Hermes Team
fa7bbef8af feat(antigravity): выход через прокси вместо патча бинарника
Вход через терминал прошёл: agy запустился в изолированном каталоге ag-5 и
опознал аккаунт владельца. Отказал Google: «Eligibility check failed: not
currently available in your location». Проверка смотрит на адрес выхода.

У владельца есть узлы 3x-ui в разных странах. Поэтому обход делается выходом
через разрешённую страну, а не патчем чужого бинарника: ничего не ломается при
обновлении Antigravity и не выполняется сторонний код.

Адрес задаётся общий в настройках и отдельный на профиль — разным аккаунтам
может требоваться разная страна. Применяется к запросу каталога моделей, к
вызовам моделей и к сценарию входа в терминале.

Пишутся и заглавные, и строчные имена переменных: Go читает HTTPS_PROXY,
многие библиотеки — https_proxy; ALL_PROXY нужен для socks5.

Адрес проверяется по существу, а не по схеме: приписать socks5:// можно чему
угодно, и тогда мусор выглядел бы принятым, а обращения провайдера молча
ломались бы без внятной причины.

695 passed, 2 skipped; ruff чисто; релизный гейт пройден.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 16:49:04 +07:00
Hermes Team
28f35f863f fix(antigravity): терминал не открывался из-за подменённого HOME; видно работающую сборку
Мастер честно сообщил: «Терминал /usr/bin/xfce4-terminal завершился сразу с
кодом 1, окно не открылось» — новая проверка запуска сработала.

Причина в моей же правке. Терминал запускался с HOME, подменённым на каталог
профиля, а клиенты X11 берут ключ авторизации из ~/.Xauthority: в
agy_profiles/ag-5 такого файла нет и быть не может, поэтому подключиться к
дисплею терминал не мог. Подменять HOME терминалу и не требуется — это делает
сценарий входа, уже внутри окна, перед самым запуском agy.

Заодно устранено побочное действие в запросе: сценарий входа создавался внутри
функции ПОИСКА терминала. Теперь его готовит start_native_agy_login, а поиск
только отвечает на вопрос и ничего не создаёт.

Владелец не мог понять, обновился ли хаб: на Linux строка сборки в боковой
панели была пуста, потому что заполнялась из панели обновлений, а та
подтягивается только при открытии. Теперь номер берётся из снапшота и
показывается вместе со временем запуска процесса.

Показывается running_commit — снятый ОДИН РАЗ при старте процесса. Поле commit
читается из манифеста на диске при каждом запросе, поэтому переживший
обновление процесс рапортует им свежий номер при старом поведении; на этом я
уже спотыкался при разборе окон консоли. Снятое при старте значение отвечает на
настоящий вопрос: какой код сейчас в памяти.

678 passed, 2 skipped; ruff чисто; релизный гейт пройден.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 16:04:07 +07:00
Hermes Team
14d1eb4304 merge main 2026-09-01 14:44:22 +07:00
ochenstarik-ui
3b423ca351 feat(antigravity): нативный вход через agy в терминале (A57)
- Реализован запуск agy в терминале с изолированным HOME (x-terminal-emulator, gnome-terminal, konsole, xfce4-terminal, tilix, alacritty, kitty, terminator, urxvt, foot, xterm / Windows wt, cmd)
- Опрос появления antigravity-oauth-token вместо ожидания процесса терминала
- Защита занятого слота от перезаписи без подтверждения
- Честное извлечение email без выдумывания identity и предотвращение дубликатов слотов
- Сохранён браузерный OAuth в качестве запасного пути
- Добавлены 12 тестов в test_a57_agy_native_login.py, 640 тестов проходят
2026-09-01 14:25:13 +07:00
Hermes Team
d5c8c316b4 merge main 2026-09-01 14:15:40 +07:00
Hermes Team
0ad946eccd fix(accounts): мастер подключения замирал на шаге 3
Владелец видел «сохранение аккаунта и запуск проверки» и ждал. Зависанием это
не было: действие честно дожидалось проверки у провайдера. Для Antigravity она
идёт через CLI и в худшем случае складывается из 90 с на захват замка профиля,
65 на каталог моделей и 90 на пробный вызов — около четырёх минут молчания при
обещанной в интерфейсе «минуте на этап».

Сохранение учётных данных и назначение роли занимают миллисекунды. Теперь
действие возвращается сразу, а опрос провайдера ставится в фон; карточка
обновляется, когда он закончится, — снапшот и так опрашивается по таймеру.

Провайдеры с ключом поведения не меняют: их подключение проверяется
предварительной проверкой до сохранения и возвращается сразу, как требует A54.
Если фоновая служба не работает, проверка по-прежнему выполняется на месте —
иначе результата не будет вовсе.

Надпись в мастере исправлена: обещание «до минуты на этап» не соответствовало
действительности.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 13:23:55 +07:00
Hermes Team
6af388a5dc fix(antigravity): пустой вход выдавался за успешный; дата установки в интерфейсе
Вход. agy читает учётные данные из <каталог профиля>/.gemini/oauth_creds.json.
Хаб этот файл пишет, но при отсутствии токена доступа создавал его пустым, а
сбой записи только заносился в журнал. Владелец видел «подключено», а проверка
отвечала «Please sign in to view available models» — ровно это и случилось с
профилем ag-2 при заново подключённом victor.trushenko@gmail.com.

Теперь вход без токена доступа отвергается с причиной, а несостоявшаяся
запись учётных данных не позволяет считать аккаунт подключённым. Проверено:
данные без токена отклоняются, с токеном проходят.

Версия. Между сборками она не меняется намеренно, а коммит — строка из
шестнадцатеричных цифр, по которой на глаз не отличить старую сборку от
новой. В /api/settings добавлено время установки из манифеста, и оно
показывается рядом с версией: «Hermes Hub Web v0.1.2 · 01.09 04:10».

610 passed, ruff clean, релизный гейт 10/10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 03:17:01 +07:00
Hermes Team
afaf600f56 feat(nvidia): определение моделей, доступных конкретному аккаунту
Каталог NVIDIA публичный: GET /v1/models отдаётся вообще без ключа и
возвращает одни и те же 83 модели всем. Проверено запросом. Полей о
доступности в нём нет — только id, object, created, owned_by. Значит
узнать из каталога, чем может пользоваться аккаунт, невозможно.

Единственный достоверный способ — спросить у самой модели. Недоступная
отвечает «404 Function ... Not found for account <id>» до генерации, то
есть её проверка ничего не стоит; доступная расходует один токен при
max_tokens=1.

Добавлены модуль model_entitlements, действие probe_account_models и
кнопка «Определить доступные модели» в карточке аккаунта для NVIDIA и
OpenRouter. Запуск только по явному нажатию с подтверждением: опрос
тратит вызовы и упирается в ограничения частоты. Результат сохраняется.

Модели раскладываются на три группы, а не на две: доступные, не выданные
аккаунту и НЕ ОПРЕДЕЛЁННЫЕ с причиной. Отвергнутый ключ (401/403),
превышение частоты (429) и сетевой сбой попадают в третью группу —
выдавать их за «недоступно» нельзя. Проверено: без ключа все модели
получают «ключ не задан», с неверным ключом — «ключ отвергнут (HTTP 403)».

610 passed, ruff clean, релизный гейт 10/10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 02:45:01 +07:00
Hermes Team
2bbb9db5de fix(wizard): ключ брался из глобальной переменной, а не из поля
При проверке подключения ключ читался из window._wiz_token. Переменная
переживает предыдущие попытки подключения и могла оказаться пустой или от
другого провайдера: OpenRouter отвечал «HTTP 401: Missing Authentication
header» при заполненном поле, и причина была не видна.

Прямой вызов validate_connection с ключом даёт «401: User not found», то
есть заголовок формируется верно — дело было в источнике значения.

Теперь ключ и адрес читаются из полей в момент проверки.

609 passed, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 02:34:13 +07:00
Hermes Team
266fc51adf fix(providers): ключ не доходил до адаптера; счётчик аккаунтов расходился
Ключ API. add_account сохраняет его через ProfileAuthManager, а адаптеры
NVIDIA и OpenRouter читали только profile.auth_config из
router_profiles.yaml — туда ключ не попадает. Запрос уходил без заголовка
Authorization, и провайдер отвечал «401: Header of type authorization was
missing», хотя список моделей тем же ключом получался: обнаружение читает
ключ из хранилища, а адаптер читал из конфигурации.

Проверено: auth_config в yaml пуст, адаптер теперь находит ключ в
хранилище учётных данных.

Счётчик аккаунтов. Значок в меню брал readiness.accounts_connected_count
(строго AUTHENTICATED), а карточка на странице считала профили правилом
«не NOT_CONFIGURED». Владелец видел 9 в меню и 3 на странице. Приведено к
одному определению — тому же, что у страницы.

609 passed, ruff clean, релизный гейт 10/10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 01:46:46 +07:00
ochenstarik-ui
2282f6a852 feat(a56): context compression engine with 100% fact retention, configurable compressor profile, and AI-Memory tracking 2026-09-01 01:27:38 +07:00
ochenstarik-ui
3e660c39bb fix(a55): account connection fixes - antigravity windows/linux, ollama server discovery, version display, full settings 2026-08-31 23:39:53 +07:00
Hermes Team
b2ca7cdd4d fix: версия в интерфейсе была зашита в разметке; установка падала на занятом файле
Версия. В index.html номер стоял руками в двух местах, а в app.js был
запасным значением '0.1.1'. Номер сборки приходил из API и обновлялся,
версия — нет: владелец обновился до e6eab12 и увидел v0.1.1. Подъём
версии в пяти местах бэкенда до экрана не доходил вовсе. Теперь версия
берётся только из API; если сервер её не передал, пишется Н/Д с причиной,
а не правдоподобный номер.

Установка. Отказ остановки прежнего хаба прерывал установку целиком, и
владелец получал голый код 15. Теперь неудачная остановка не отменяет
установку: причина показывается, работа продолжается, и если файл
действительно занят, об этом скажет копирование с именем файла.

Копирование файлов получило повтор: процесс мог не успеть отпустить файл
после остановки. Пять попыток с паузой вместо отказа с первой.

599 passed, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 21:46:30 +07:00
ochenstarik-ui
b58bfc6c77 fix(a54): reject empty credentials before slot allocation 2026-08-31 20:55:42 +07:00
ochenstarik-ui
ddeba2db0e fix(a54): validate accounts synchronously and manage Windows runtime lifecycle 2026-08-31 20:54:19 +07:00
Hermes Team
5c2a0692d3 merge: A51 — подключённый аккаунт реально попадает в цепочку роли
Проверено ревьюером исполнением: подключение с выбором роли кладёт
аккаунт в её цепочку (developer-1 → ['openrouter-1']). Раньше выбор роли
до цепочки не доходил, и ни один аккаунт владельца ни в одной цепочке не
состоял.

Роль на карточке берётся из живой цепочки: role_assignments.get(pid, [])
вместо подстановки догадки. Таблица DEFAULT_SLOT_ROLES удалена — ссылок
на неё в дереве не осталось.

На маршрутизации показывается «Сейчас ответит: <аккаунт>» либо «никто»
с причиной. В плагине Hermes учитываются вызовы мимо хаба
(record_bypass в четырёх местах, счётчик bypassed_calls_count).

Обратное чтение сделано честно: get_hermes_config_status читает
конфигурацию Hermes ТОЛЬКО на чтение, с отдельным тестом на это.

Разрешение конфликтов с A49 и A50 (ветка A51 отведена от 17b368a):
- action_handler: взят вариант A50 — он надмножество, содержит и
  назначение в роль, и защиту слота от чужого провайдера;
- auto_assigner: взят вариант A51 — таблица-догадка удалена;
  подпись llama.cpp от A50 сохранена, она вне конфликта;
- settings_service, unified_health, app.js: обе стороны, правки
  дополняют друг друга;
- тест свежести памяти: взята строгая проверка, вариант A51 молча
  пропускал отсутствие записанного коммита.

Две правки ревьюера по итогам слияния:
- в app.js при сложении потерялась закрывающая скобка блока настроек;
- в add_account сохранение учётных данных вызывалось дважды, и второй
  вызов обращался к auth_data, которой у уже авторизованного аккаунта не
  существует. Перевод аккаунта в другую роль падал с ошибкой, хотя ключ
  вводить не требуется. Дубль убран, оба теста A51 проходят.

554 passed, 1 skipped, ruff clean, релизный гейт 10/10 и на конфигурации
владельца, и на пустой.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 19:13:02 +07:00
Hermes Team
e7aa896539 merge: A50 — аккаунты, обнаружение моделей и автоматическая проверка
Проверено ревьюером исполнением, все восемь замечаний владельца закрыты:

Чужой слот. Раньше add_account с profile_id=ag-w1 для nvidia возвращал
ok=True и клал аккаунт в слот Antigravity. Теперь отказ с причиной:
«Слот ag-w1 не принадлежит провайдеру nvidia». Свой слот принимается,
идентификатор выдаётся верный.

Группировка по провайдерам восстановлена: скрытие заголовков групп,
добавленное в A48 (display:contents + display:none), убрано.

Автоматическая проверка запускается при старте веб-сервера
(_start_background_refresh), состояние и списки моделей больше не ждут
ручного нажатия.

Облачные модели Ollama: эндпоинт не выдуман — ревьюер проверил запросом,
https://ollama.com/api/tags отвечает 200 и отдаёт 19 моделей
(gpt-oss:20b, kimi-k2.6, glm-5.1 и другие).

Локальный провайдер подписан llama.cpp вместо «Локальный сервер».

Показ хода при долгом опросе честный: «может занять до минуты на этап».

Конфликты с A49 разрешены сложением: правки дополняют друг друга —
поле пути к хранилищу Obsidian и поле периода проверки аккаунтов,
стили вкладки скиллов и стили групп провайдеров. В тесте свежести памяти
взят вариант A50: коммит извлекается из файла, а не зашит.

545 passed, 1 skipped, ruff clean, релизный гейт 10/10 и на конфигурации
владельца, и на пустой.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 18:54:04 +07:00
ochenstarik-ui
3ed85e79eb feat(a51): hub controls hermes - full real routing, bypass telemetry and truthful status 2026-08-31 18:53:55 +07:00
ochenstarik-ui
79ac9cf561 Fix account slot isolation and add background checks with per-account model discovery 2026-08-31 18:42:41 +07:00
ochenstarik-ui
5c3565120e feat(skills): A49 subagents layout, skills tab, SkillDoctor, and Obsidian memory integration 2026-08-31 18:19:01 +07:00
ochenstarik-ui
1fbbeacd13 Restore local provider logos and add model-family brand marks 2026-08-31 18:14:34 +07:00
ochenstarik-ui
144196bb01 Refine A48 overview layout and verify authenticated interface 2026-08-31 15:55:26 +07:00
ochenstarik-ui
c3bfcee846 feat(ui): A48 workspace layout draft awaiting authenticated visual QA 2026-08-31 15:25:15 +07:00
ochenstarik-ui
81a58f6b13 feat(ui): A43 интерактивный холст workflow (n8n style), панорамирование, зум колесом, 6 KPI и верстка по макетам 2026-08-31 12:29:06 +07:00
Hermes Team
ff303b591d Merge remote-tracking branch 'origin/review/a42-provider-fixes' into HEAD 2026-08-31 03:06:13 +07:00
Hermes Team
e7194d3220 fix(web): маршрутизация читала несуществующий ключ снапшота
Экран маршрутизации брал профили из currentSnapshot.profiles, тогда как
/api/snapshot отдаёт dataclasses.asdict(HubSnapshot), где поле называется
all_profiles. Ключа profiles в ответе нет — проверено перечислением полей
датакласса. Это было единственное такое место в файле: остальные девять
обращений уже читали all_profiles.

Следствия, которые чинятся разом:
- правая колонка «доступные аккаунты» была всегда пуста;
- «0 аккаунтов» оставалось литералом из разметки, счётчик не переписывался;
- строки цепочек получали пустой профиль, provider становился 'unknown',
  и все аккаунты рисовались одной иконкой-заглушкой.

Для строк цепочек берётся полный список профилей, для колонки «доступные» —
только подключённые (правило A26).

Убрана полоса квоты с зашитым width:80%: она была одинаковой у всех
аккаунтов и ни на чём не основана. Вместо неё индикатор измеренного
состояния; неизвестное состояние остаётся серым, а не выдаёт себя за
здоровое.

Подписи связей на холсте центрируются (text-anchor отсутствовал, поэтому
подпись уходила вправо от середины связи и обрезалась о край холста) и
получают обводку, чтобы читаться поверх линии.

Инспектор агента показывал модели из preferred_models — это настроенный
список предпочтений профиля, а не то, что даёт провайдер; model_states
строится перебором того же preferred_models, поэтому запасная ветка давала
тот же набор. Источником стал discovered_models провайдера. Настроенная у
агента модель теперь всегда присутствует в списке: раньше, если её там не
было, ни один option не получал selected, показывался первый вариант, и
сохранение конфигурации молча подменяло модель агента.

486 passed, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 02:45:14 +07:00
ochenstarik-ui
2b89372b0f feat(config): A41 чистая конфигурация при первой установке, безопасность учетных данных и сброс
1. P0-1: get_default_router_config() возвращает чистую конфигурацию (0 профилей,
   13 канонических ролей с пустыми цепочками). Миграция не внедряет фиктивные профили.
2. P0-2: Учетные данные (~/.hermes/*_profiles/, hub_settings.json) изолированы и
   никогда не затрагиваются при сбросе или установке.
3. P0-3: Профили создаются динамически при подключении аккаунтов (ag-1, codex-1, etc.).
   Пустые цепочки ролей являются нормальным рабочим состоянием.
4. P0-4: Добавлен экшен reset_router_config и кнопка «Начать настройку заново»
   в настройках с подтверждением и созданием бэкапа router_profiles.yaml.bak_<timestamp>.
5. P0-5: scripts/verify_multi_provider_router.py адаптирован и проходит 10/10 PASS
   как на пустой конфигурации, так и на заполненной.
6. tests/test_a41_clean_install.py: 6 тестов, 490 passed, ruff чисто.
2026-08-31 02:27:42 +07:00
Hermes Team
0ed8fc0a29 fix(web): тему Medium нельзя было выбрать, хотя она полностью реализована
Брендбук требует три темы, причём Medium — отдельная, а не осветлённая Dark:
тёмно-зелёный холст #1A2A1F со светлыми кремовыми карточками #F7F1E3.

В style.css она описана целиком и отрисовывается верно — проверено
подстановкой data-theme вручную. Но в список выбора не попала, а applyTheme
знал только light и dark, поэтому любое другое значение сбрасывало тему в
системную. Выбрать Medium было невозможно.

После правки все три переключаются:

    dark    фон #101510   карточки #1A2A1F
    medium  фон #1A2A1F   карточки #F7F1E3
    light   фон #F7F1E3   карточки белые

Цвета совпадают с палитрой брендбука. 486 passed, ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 01:45:26 +07:00
Hermes Team
a677449b9b merge: A39 поверх A34 — файлы десктопа остаются удалёнными 2026-08-31 01:33:05 +07:00
ochenstarik-ui
ad07425c06 feat(providers): A32 интеграция Ollama с API, Claude probe, OpenRouter headers/metadata, NVIDIA Retry-After и экспорт лимитов
1. OllamaAdapter: поддержка локального инстанса по умолчанию и удаленного Ollama API
   с кастомным base_url и опциональным Bearer токеном, discovery по /v1/models и /api/tags,
   статус квоты «Без ограничений».
2. ClaudeAdapter: реальный health_check API probe и динамический discover_models.
3. OpenRouterAdapter: обязательные заголовки HTTP-Referer и X-OpenRouter-Title,
   сбор метаданных моделей (context_length, display_name).
4. NvidiaAdapter: парсинг заголовка Retry-After и динамическая задержка при 429.
5. Экспорт лимитов: эндпоинт GET /api/quotas/export (JSON / CSV), экшен export_quotas
   и кнопка выгрузки в веб-интерфейсе с маскированием секретов.
6. tests/test_api_providers_a32.py: 15 тестов, 434 passed, ruff чисто.
2026-08-31 01:15:10 +07:00
ochenstarik-ui
c7539b36d4 feat(hub): A34 восстановление подключения аккаунтов, OpenRouter, NVIDIA, удаление десктопа
1. P0-1: Восстановлены все веб-обработчики в app.js (openAddAccountWizard,
   handleNodeAccountChange, handleNodeModelChange, handleRefreshProviderModels,
   checkUpdates), связаны с потоками startDeviceAuth и startRedirectAuth,
   выбор слота обязателен и понятен пользователю.
2. P0-2: Добавлены адаптеры OpenRouter и NVIDIA NIM с поддержкой динамического
   base_url, множественных аккаунтов без ограничений, GET /models и честным
   отображением квот/«Н/Д».
3. P0-3: В мастере подключения локального провайдера реализована кнопка автопоиска
   (discover_local_models), отображение серверов, ошибок портов и автозаполнение.
4. P0-4: Полностью удален устаревший десктопный интерфейс CustomTkinter
   (router/ui/** 20 файлов, hermes_hub_app.py), зависимости customtkinter и pillow
   убраны из pyproject.toml и инсталляторов, оставлен единый ярлык «Hermes Hub».
5. 418 passed, 1 skipped, 4 deselected, ruff чисто.
2026-08-31 00:23:27 +07:00
ochenstarik-ui
5ac01e0a15 feat(local): параметры запроса (request_options) для локальных профилей
Реализована поддержка произвольных параметров запроса (request_options) для
локальных профилей:

1. RouterProfileConfig и ProfileViewModel дополнены полем request_options
   с полной поддержкой вложенных словарей и сериализацией в YAML.
2. LocalLLMAdapter подмешивает request_options в тело POST /chat/completions.
   Явные поля запроса имеют приоритет, при расхождениях пишется warning.
   Изоляция провайдеров сохранена: другие адаптеры не трогают request_options.
3. Неизвестные/некорректные параметры обрабатываются чисто и классифицируются
   как INVALID_REQUEST с извлечением текста ошибки сервера.
4. В веб-интерфейсе реализована карточка профиля с редактором JSON параметров,
   живой валидацией синтаксиса, предпросмотром тела запроса и проверкой
   подключения.
5. Отсутствие зашитых параметров enable_thinking и reasoning_effort в логике.
6. 526 passed, 3 skipped, 4 deselected, ruff чисто.
2026-08-30 23:49:57 +07:00
Hermes Team
59d57a41ef feat(a31): preflight dependency agent, workflow run state recovery, local concurrency and context guard, PII email masking, and cost-controller token honesty 2026-08-26 09:27:36 +07:00
Hermes Team
8b8aebf928 feat(integration): consolidate A28, A29 and A30 on top of origin/main with green release gate 2026-08-26 09:10:10 +07:00
Hermes Team
620c862912 merge: A29 — design system, themes, routing drag-and-drop 2026-08-26 08:53:17 +07:00
Hermes Team
83233c891e feat(web): add workflow canvas and live agent workspace 2026-08-26 08:40:20 +07:00
Hermes Team
a8c37ca6e3 A29: Design System and Routing UI Drag-and-Drop 2026-08-25 19:53:24 +07:00
Hermes Team
d7ad3e3165 feat(updater): in-app updates based on main repo release commits (A27 Pass 1) 2026-08-25 17:54:51 +07:00
Hermes Team
4c2594bcf6 fix(web): «только подключённые» пропускало холодный резерв и пустые слоты
Проверка A26 исполнением. Признак подключённости был записан как

    p.authenticated === true || (p.health_state && p.health_state !== 'not_configured')

и содержит два дефекта. Поля authenticated в ProfileViewModel нет вовсе —
первая половина условия мертва. Вторая пропускает всё, кроме not_configured,
то есть холодный резерв (health_state "disabled") и непроверенные пустые
слоты.

Измерено на живом снапшоте: из 24 профилей фильтр пропускал 5, при том что
по-настоящему подключён 1. При нуле настоящих аккаунтов страница показывала
три карточки «Холодный резерв» вместо пустого состояния — ровно тот мусор,
который владелец просил убрать.

Тот же предикат используется для выбора аккаунта на «Обзоре», поэтому там
предлагалось назначать роли на пустые слоты: мышление слотами, ради отмены
которого задание и делалось.

Authoritative признак — auth_state: у подключённого AUTHENTICATED, у пустого
слота и у холодного резерва NOT_CONFIGURED. AUTH_REQUIRED и AUTH_EXPIRED
означают подключённый аккаунт, которому нужен повторный вход, — показываем.

Заодно в выборе аккаунта показывается почта, а не имя профиля: жалоба из A24
про «Кодер 1 — назначенный аккаунт Кодер 2» иначе возвращалась.

Проверено в браузере: было 5 «подключённых» из 24, стало 2 — оба с
auth_state AUTHENTICATED; холодный резерв из выбора на «Обзоре» исчез.
442 passed, ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 17:17:29 +07:00
Hermes Team
b354ac9e6d feat(router): accounts without slots, overview role assignment and quota thresholds (A26) 2026-08-25 16:36:59 +07:00
Hermes Team
d4b99a49ef fix(web): мастер не говорил, участвует ли слот в маршрутизации
Владелец подключил аккаунт, увидел его в «Аккаунтах» — и не нашёл ни в
«Обзоре», ни в «Маршрутизации».

Поведение верное: эти два экрана показывают цепочки ролей, а в умолчаниях в
цепочках участвуют только ag-orch-fallback и ag-w1..w4. Слоты ag-cold-* и
ag-spare-2 не входят никуда, поэтому подключённый в них аккаунт там и не
появится. Но мастер показывал все десять слотов одинаково, как «свободен», и
выбор был вслепую — а результат выглядел как пропажа аккаунта.

Теперь в списке слотов видна роль: «ag-w1 — свободен · coder-primary
(primary)» против «ag-spare-2 — свободен · не участвует в маршрутизации».
Участвующие идут первыми. После подключения слота вне цепочек показывается
пояснение, где его добавить.

Признак участия берётся из состава самих цепочек, а не из assigned_roles:
холодный резерв и ag-spare-2 значатся с ролью "spare", которой среди шести
маршрутизируемых ролей нет, так что проверка по названию роли давала бы
неверный ответ.

Проверено в браузере: шесть слотов показаны с настоящими ролями, четыре — с
пометкой о неучастии; состав совпадает с разбором снапшота по цепочкам.
432 passed, ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:31:21 +07:00
Hermes Team
0800ca94e0 fix(web): кнопка копирования молча не работала по сети, а возврат вёл в тупик
Владелец открыл хаб по сети и не смог войти в Antigravity: кнопка
«Копировать» не давала ничего, а после подтверждения доступа браузер уходил
на 127.0.0.1:51121 его собственной машины и показывал «страница недоступна».

Два дефекта.

1. navigator.clipboard существует только в защищённом контексте — HTTPS или
   localhost. По http://192.168.1.81:5800 его нет вовсе, и три кнопки
   копирования не работали. Хуже: промис никто не проверял, поэтому они
   показывали «Ссылка скопирована», ничего не скопировав. Подтверждено
   измерением на живой странице по сетевому адресу: isSecureContext=false,
   navigator.clipboard отсутствует. Добавлен общий помощник с запасным
   execCommand('copy') и честным сообщением при неудаче.

2. Адрес возврата вёл в тупик, из которого код ещё надо было выковырять со
   страницы ошибки, где он часто обрезан. Теперь, когда хаб открыт не с этой
   машины, показывается готовая команда проброса порта возврата: тогда
   слушатель хаба принимает возврат сам и вставлять ничего не нужно. Порт и
   адрес берутся из ответа сервера и window.location, не зашиты.

   Запасным путём принимается и голый код: не только полный адрес. Признак
   адреса — "?" или "://", но НЕ слэш: коды Google сами содержат его и
   начинаются с "4/0A...". Первая версия условия их отсекала — поймано
   тестом. Добавлена проверка правдоподобия, иначе произвольный текст уходил
   на обмен и давал невнятную ошибку провайдера вместо подсказки.

Проверено исполнением: голый код, полный адрес и вставка без протокола
принимаются; русский текст, короткая строка и строка с пробелом отвергаются
без обращения к провайдеру. Подсказка о пробросе отрисована на живой
странице, открытой по сетевому адресу. 432 passed, ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 11:06:08 +07:00
Hermes Team
de7c7132ae fix(web): при 401 интерфейс молчал вместо того, чтобы спросить токен
Владелец открыл хаб по сети и получил пустую панель с повторяющимся тостом
«Ошибка сервера: 401». Кода 401 клиент не знал вовсе: ответ падал в общую
ветку ошибок, тост повторялся на каждом опросе, и ни одной подсказки о том,
что нужен токен и где его взять, не было.

Попытка ввести токен в «Настройках» тоже провалилась: рядом с полем токена
стоит кнопка сохранения настроек СЕРВЕРА, а она сама шлёт save_settings и
требует токен — получался замкнутый круг, 401 на попытке ввести токен от 401.

Теперь 401 перехватывается и в fetchSnapshot, и в executeAction: опрос
останавливается, открывается окно с полем ввода и объяснением, откуда взять
значение. Токен проверяется настоящим запросом до сохранения, поэтому
неверный не попадает в localStorage. После принятия окно закрывается, поле в
настройках заполняется, опрос возобновляется.

Дефект, найденный при проверке в браузере: непустой токен с не-ASCII
символами роняет fetch на TypeError, и обработчик умирает молча, ничего не
показав. Заголовки HTTP переносят только ASCII. Добавлена проверка до
отправки и перехват сетевой ошибки — так бывает, когда вместе с токеном
скопирован текст вокруг.

Проверено в браузере на изолированном экземпляре: окно появляется само,
кириллица даёт внятное сообщение, неверный токен отвергается и не
сохраняется, верный принимается — панель оживает, индикатор Live API,
опрос возобновлён. 432 passed, ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 10:55:41 +07:00
Hermes Team
e8aa60b982 fix(web): при недоступном сервере показывался чужой пример как живые данные
Если /api/snapshot не отвечал, клиент молча загружал snapshot.example.json —
63 профиля с почтами user@example.test — и ставил индикатор источника в
зелёное через setSourceIndicator(true, ...). На экране появлялась полная
здоровая панель, не имеющая отношения к этому серверу.

Дефект становится опасным ровно в том сценарии, ради которого делался вход с
другой машины: при работе через SSH-туннель достаточно промахнуться портом
или потерять туннель, чтобы принять пример за собственные данные и,
например, решать по нему, у какого аккаунта кончилась квота.

Осознанная работа с фикстурой сохранена: ?fixture=1 и открытие страницы
файлом. Подстановка вместо ответа сервера убрана — отсутствие данных теперь
читается как отсутствие данных.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 10:14:04 +07:00
Hermes Team
3287565773 feat(web): вход Antigravity и Claude из браузера на любой машине
Мастер подключения писал, что на сервере без экрана вход «через веб-интерфейс
невозможен», и отправлял в консоль по SSH либо переносить каталог
agy_profiles руками. GET /api/health отдавал для этих провайдеров жёстко
вписанное supported: false.

Утверждение оказалось ложным. В коде уже были
ProfileOAuthSession.handle_manual_callback_url и
ClaudeOAuthSession.handle_auth_code — оба принимают вставленное вручную
значение и доводят обмен кода на токены. Наружу их просто не вывели. Браузер
нужен где угодно, а не на машине с Hub: владелец открывает ссылку у себя и
возвращает адрес из адресной строки.

Добавлены действия start_redirect_auth, submit_redirect_callback,
poll_redirect_auth, cancel_redirect_auth. auth_flows теперь отражает
настоящие возможности, а не литерал. Обе заглушки в мастере заменены живым
потоком; мёртвая ветка Claude с полем API Key удалена.

Три дефекта, найденных при проверке исполнением:

1. Одна опечатка при вставке убивала сессию: handle_callback ставил
   status="failed" при отсутствии кода или чужом state, и вход приходилось
   начинать заново, хотя ссылка оставалась годной. Для ручного ввода такие
   ошибки больше не конечные; отказ провайдера конечен по-прежнему.
2. Окно слушателя в 5 минут рассчитано на браузер той же машины. При входе с
   другого ПК его не хватает: 20 минут — значение, проверенное на практике.
3. find_free_slot всегда возвращал ag-orch-fallback: занятость определяется по
   файлу учётных данных, а agy на Windows держит их в keyring, поэтому все
   десять слотов выглядят свободными. Вход затёр бы работающий аккаунт. Слот
   теперь выбирает владелец из списка, построенного по снапшоту, с пометкой,
   какие заняты и кем.

Два теста закрепляли снятую заглушку: test_headless_server_auth_matrix требовал
слов «Headless» и «agy» в интерфейсе, test_c_state_mismatch требовал
status == "failed". Первый переведён на проверку настоящего потока, второй
усилен: свойство безопасности (отказ без обмена кода) проверяется по-прежнему,
и дополнительно проверено, что после промаха верная вставка доходит до обмена.

Проверено вживую в браузере: список из 10 слотов с пометкой занятости,
выбранный слот доходит до сервера, ссылка настоящая от accounts.google.com,
поле вставки на месте. 431 passed, 2 skipped; ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 09:52:53 +07:00
Hermes Team
158210dc01 fix(web): браузер отдавал старый app.js, интерфейс выглядел не обновлённым
Владелец сообщил, что в маршрутизации не переставляются блоки, нигде нет
выбора аккаунта для роли и на «Обзоре» ничего нельзя изменить. На скриншоте
при этом видна кнопка «Изменить цепочку», которой в коде уже нет: A24 её
удалил вместе с renderTeam и renderProviders.

Причина не в логике. FileResponse отдавал app.js и style.css без заголовка
Cache-Control, поэтому браузер применял эвристическое кэширование и держал
скрипт от прошлой сборки. index.html при навигации перепроверялся и был
свежим — отсюда смесь нового текста подсказки со старыми кнопками, а
перетаскивание и выбор модели просто отсутствовали в загруженном коде.

Действие reorder_chain при этом исправно: проверено вызовом, порядок
цепочки меняется и сохраняется в router_profiles.yaml.

Заодно подключённые аккаунты выводятся первыми, а пустые слоты
(«не подключён») — в конце группы: рабочие карточки были разбросаны
между пустыми и их приходилось выискивать.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 00:32:42 +07:00
Hermes Team
c49e6eac31 fix(web): окно аккаунта не обновлялось и застревало на заглушке
Владелец открыл карточку Grok и увидел «Grok 2h — Н/Д» и статус «Не
проверялся» — хотя сервер в этот момент уже отдавал настоящие данные:
подписка 14%, GrokChat 13%, GrokBuild 1%.

Проверено на живом сервере: и /api/snapshot, и quota_snapshot внутри
профиля содержали правильные корзины. Дефект целиком в клиенте — окно
рисовалось ОДИН РАЗ при открытии и на опрос не реагировало. Открытое до
завершения прогрева квот, оно навсегда оставалось с заглушкой
_generate_baseline_snapshot, а после успешной проверки подключения
по-прежнему показывало «Не проверялся».

Теперь открытый профиль отслеживается и перерисовывается при каждом
применении снапшота. Объявление переменной поднято выше первого
использования: let не поднимается, и обращение раньше объявления
роняло бы обработчик снапшота целиком.

При закрытии окна сбрасывается отслеживание и останавливается опрос кода
устройства — раньше он продолжал работать после закрытия мастера.

Тесты: 431 passed, ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 20:40:13 +07:00
Hermes Team
a40bcac3c3 fix(web): удаление аккаунта не работало из интерфейса
Владелец: «удалить так и не могу ненужный». Backend был починен в
d755a07, но кнопка по-прежнему не работала.

Причина в клиенте: он отправлял только profile_id, без provider. Сервер
не знал, в каком каталоге искать auth.json, строил неверный путь и снова
отвечал «удалять нечего».

Идентификатор профиля однозначен, поэтому провайдер теперь берётся из
конфигурации, когда его не передали. Действие работает независимо от
того, кто его вызвал.

Заодно в клиенте: подтверждение перед необратимым удалением и обновление
экрана после — раньше карточка оставалась в прежнем виде, и было
непонятно, сработало ли.

Проверено через веб-API без параметра provider: до — авторизован,
после — нет.

Тесты: 426 passed, ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 20:19:58 +07:00