From 93883f9ce43ddfba134e4876c07b2b1abafd1d85 Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Thu, 20 Aug 2026 23:13:59 +0700 Subject: [PATCH] docs(task): split Plan A and UI redesign into two non-overlapping assignments The UI redesign draft duplicated eight Plan A phases on the same files (accounts_view, routing_view, hermes_hub_app, unified_health), which would have put two agents into the same merge conflicts. Ownership is now split by file path, with docs/UI_STATE_CONTRACT.md as the interface between them. Four draft items were already implemented at 50fde5f and are marked as such rather than reassigned: OAuth URL copy-before-open, keyed account card reuse, the no-fake-metrics rule, and the importorskip guard. Co-Authored-By: Claude Opus 5 --- .../2026-08-21-A-antigravity-state-layer.md | 131 ++++++++++++++ .../inbox/2026-08-21-B-codex-ui-redesign.md | 164 ++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 agents/inbox/2026-08-21-A-antigravity-state-layer.md create mode 100644 agents/inbox/2026-08-21-B-codex-ui-redesign.md diff --git a/agents/inbox/2026-08-21-A-antigravity-state-layer.md b/agents/inbox/2026-08-21-A-antigravity-state-layer.md new file mode 100644 index 0000000..7029b61 --- /dev/null +++ b/agents/inbox/2026-08-21-A-antigravity-state-layer.md @@ -0,0 +1,131 @@ +# Задание A (Antigravity): слой состояния и данных + +## Дата поступления +2026-08-21 + +## База +Проверочный HEAD на момент выдачи: **`50fde5f`**. Перед началом выполнить `git fetch`, зафиксировать фактический `BASE_SHA` и не считать этот SHA актуальным автоматически. + +## Ветка +`antigravity/state-layer` + +--- + +## ГРАНИЦА РАБОТ — читать первым + +Параллельно выполняется **Задание B (Codex)** — переработка интерфейса. Чтобы задания не конфликтовали, разделение проходит **по файлам**, а не по смыслу. + +**Ваша зона (можно менять):** +``` +src/antigravity_provider/router/state_store.py +src/antigravity_provider/router/unified_health.py +src/antigravity_provider/router/scheduler.py +src/antigravity_provider/router/event_bus.py +src/antigravity_provider/router/quota_collector.py +src/antigravity_provider/router/model_registry.py +src/antigravity_provider/router/account_identity.py +src/antigravity_provider/router/router_engine.py +src/antigravity_provider/router/router_config.py +src/antigravity_provider/router/session_affinity.py +src/antigravity_provider/router/health_tracker.py +src/antigravity_provider/router/profile_manager.py +src/antigravity_provider/router/auto_assigner.py +src/antigravity_provider/router/adapters/** +src/antigravity_provider/router/*_oauth.py +src/antigravity_provider/*.py +scripts/**, installer/**, config/** +tests/** (кроме tests/test_ui_*.py) +``` + +**Чужая зона (НЕ трогать):** +``` +src/antigravity_provider/router/ui/** ← весь UI, включая views, components, theme, wizard +src/antigravity_provider/router/hermes_hub_app.py +tests/test_ui_*.py +``` + +Если для вашей задачи потребовалось изменить файл из чужой зоны — **это сигнал, что контракт спроектирован неверно**. Вместо правки UI расширьте ViewModel или событие. + +--- + +## P0. Контракт ViewModel публикуется первым + +До любых других изменений зафиксировать и запушить контракт, против которого Codex будет писать интерфейс: `docs/UI_STATE_CONTRACT.md`. + +Описать точно, по фактическому коду: + +- `HubSnapshot` — состав, гарантии консистентности, поле версии/seq; +- `ProfileViewModel` — все поля, какие опциональны, что означает каждое состояние `auth_state`/`health_state`; +- `QuotaSnapshot` и `QuotaBucket` — полная схема (`id`, `label`, `used_percent`, `remaining_percent`, `used`, `limit`, `reset_at`, `unit`, `scope`, `model_family`), какие поля реально заполняются **для каждого провайдера отдельно**, и признак `is_estimated`; +- `AgentViewModel`, `RolePipeline`, `PipelineNode`, `ProviderSummary`, `SystemReadiness`; +- перечень событий `event_bus` с полезной нагрузкой: что именно приходит при изменении квоты одного аккаунта, при добавлении аккаунта, при смене маршрута. + +**Критично:** для каждого поля указать, реально ли backend его отдаёт, или это заглушка. Codex обязан отличать «данных нет» от «данные есть». Раздел «Backend gaps» обязателен. + +Этот документ — интерфейс между двумя заданиями. После публикации менять его только с явной пометкой в отчёте. + +--- + +## Область задачи + +### 1. Единый источник состояния +Завершить `HubSnapshot` как единственный источник для UI. Ни один view не должен иметь возможности самостоятельно инициировать сканирование — не потому, что это запрещено правилом, а потому, что данные ему приходят готовыми. + +Убрать вызовы `scan_all()` из кода, доступного UI: сервис отдаёт снапшот, обновляет его сам. + +### 2. Централизованный планировщик обновлений +На базе существующего `HermesRefreshScheduler`: +- интервалы на провайдера; +- дедупликация одновременных запросов; +- защита от устаревшего ответа (поздний ответ не перезаписывает более свежее состояние — использовать `seq`); +- раздельные операции: обновить один аккаунт / одного провайдера / всё. + +### 3. Событийная модель вместо полного пересбора +Изменение квоты одного аккаунта обязано порождать точечное событие с идентификатором аккаунта, а не сигнал «всё изменилось». То же для добавления/удаления аккаунта, смены авторизации, смены активного маршрута. + +**Отдельно:** OAuth — самостоятельная state machine. Не подключать OAuth listener, таймауты авторизации и жизненный цикл PKCE к общему планировщику. После успешной авторизации: сохранить аккаунт → событие `ACCOUNT_ADDED` → точечное обновление, **без** глобального пересканирования. + +### 4. Мульти-корзинные квоты +Довести `quota_collector` до реального сбора там, где провайдер это отдаёт. Требования: +- не сводить разные лимиты к одному проценту; +- корзины привязаны к семейству моделей, если у провайдера лимиты раздельные (Antigravity: Gemini и не-Gemini — проверить фактическим кодом, что именно доступно); +- каждый снапшот честно помечен `is_estimated` и источником; запрет на `*_api` для локально вычисленных значений остаётся в силе; +- если провайдер не отдаёт данных — отдавать отсутствие данных, а не число. + +Роутер должен понимать, что переключение модели может сменить квотный пул. + +### 5. Реестр моделей и маршрутизация +Довести `model_registry` и capability-роутинг: выбор модели с учётом возможностей, стоимости и остатка квоты. Учитывать `preferred_models` профиля и приоритет роли. + +### 6. Долги, оставшиеся открытыми +- **Мёртвые модули.** `capability_matrix`, `lifecycle_supervisor`, `skill_registry` сейчас лишь реэкспортируются из `router/__init__.py` — ни один кодовый путь их не использует. Реэкспорт не равен интеграции. Либо подключить по-настоящему, либо удалить и записать причину. +- **`HKCU` в тестах.** `APPDATA`/`USERPROFILE` теперь в песочнице, но `HermesHubSetup.cs` пишет в реестр двумя вызовами `Registry.CurrentUser`, а реестр переменными окружения не перенаправляется. Вынести реальный запуск установщика в явный integration-режим либо параметризовать ключ реестра. +- **Комментарии `router_profiles.yaml`** стираются при сохранении (5 строк → 0). Оценить `ruamel.yaml`; если замена YAML-стека велика — зафиксировать отдельным долгом и не расширять scope. +- **Сериализация Antigravity.** `_AGY_INVOCATION_LOCK` вернул корректность ценой параллелизма: вызовы профилей со своим auth снова идут по одному. Оценить отказ от глобальной записи `gemini:antigravity` в пользу изоляции только через `USERPROFILE` — это снимет и лок, и гонку. Если не делать — записать как осознанный долг. + +### 7. Релизная инфраструктура +- выложить ассет `hermes-hub-0.1.1.zip` с суммой из манифеста (сейчас `package_url` → HTTP 404) и `HermesHubSetup.exe` в GitHub Release; +- `dist/checksums.txt` — только из `scripts/build_dist.py`, вручную не править; +- порядок публикации: build → checksum → upload → verify → publish manifest → verify feed. Манифест не должен рекламировать несуществующий артефакт. + +**Тег `v0.1.1` не создавать.** + +--- + +## Критерии приёмки + +1. `docs/UI_STATE_CONTRACT.md` опубликован до остальных изменений, содержит раздел «Backend gaps» и для каждого поля — признак реальности данных. +2. Ни один файл из чужой зоны не изменён (`git diff --name-only BASE_SHA..HEAD -- src/antigravity_provider/router/ui src/antigravity_provider/router/hermes_hub_app.py` пуст). +3. `scan_all()` недоступен из UI-слоя; сервис обновляет снапшот сам. +4. Изменение квоты одного аккаунта порождает событие с идентификатором аккаунта; тест это проверяет. +5. Поздний ответ не перезаписывает более свежее состояние; тест на гонку `seq`. +6. OAuth-сессия не управляется общим планировщиком; успешная авторизация даёт точечное обновление, тест это фиксирует. +7. Мульти-корзинные квоты: для каждого провайдера в контракте указано, какие корзины реально доступны; ни одна локально вычисленная величина не помечена источником провайдера. +8. Мёртвые модули либо интегрированы (есть вызывающий код вне `__init__.py`), либо удалены. +9. Тесты установщика не оставляют записей в `HKCU` при обычном прогоне. +10. `package_url` из манифеста отдаёт HTTP 200, sha256 совпадает с загруженным артефактом. +11. Полный `pytest` зелёный **в окружении без UI-зависимостей**; `ruff check .` чисто; release gate PASSED на финальном коммите. +12. Отчёт содержит `BASE_SHA`, `FINAL_SHA`, `origin/main`, `git status`, точный результат тестов (`X passed / Y skipped / Z failed`) и список того, что осталось. + +## Порядок сдачи +Никаких функциональных коммитов после последнего запуска release gate. Ревьюеру передать точный `FINAL_COMMIT_SHA` и не продолжать разработку поверх него до вердикта. diff --git a/agents/inbox/2026-08-21-B-codex-ui-redesign.md b/agents/inbox/2026-08-21-B-codex-ui-redesign.md new file mode 100644 index 0000000..cec408e --- /dev/null +++ b/agents/inbox/2026-08-21-B-codex-ui-redesign.md @@ -0,0 +1,164 @@ +# Задание B (Codex): переработка интерфейса Hermes Hub + +## Дата поступления +2026-08-21 + +## База +Проверочный HEAD на момент выдачи: **`50fde5f`**. Перед началом выполнить `git fetch`, зафиксировать фактический `BASE_SHA`, изучить существующий UI по коду, не по названиям файлов. + +## Ветка +`codex/ui-redesign` + +## Зависимость +Работа ведётся против контракта `docs/UI_STATE_CONTRACT.md`, который публикует Antigravity в рамках Задания A. **Дождаться публикации контракта перед PHASE 2.** PHASE 1 (токены, компоненты) можно начинать сразу — она от контракта не зависит. + +--- + +## ГРАНИЦА РАБОТ — читать первым + +Параллельно выполняется **Задание A (Antigravity)** — слой состояния и данных. Разделение проходит **по файлам**. + +**Ваша зона (можно менять):** +``` +src/antigravity_provider/router/ui/** ← theme, components, assets, views, wizard +src/antigravity_provider/router/hermes_hub_app.py +tests/test_ui_*.py +``` + +**Чужая зона (НЕ трогать):** +``` +src/antigravity_provider/router/state_store.py +src/antigravity_provider/router/unified_health.py +src/antigravity_provider/router/scheduler.py +src/antigravity_provider/router/event_bus.py +src/antigravity_provider/router/quota_collector.py +src/antigravity_provider/router/model_registry.py +src/antigravity_provider/router/router_engine.py +src/antigravity_provider/router/router_config.py +src/antigravity_provider/router/adapters/** +src/antigravity_provider/router/*_oauth.py +src/antigravity_provider/router/profile_manager.py +scripts/**, installer/**, config/** +``` + +Если для отрисовки не хватает данных — **не добавлять сбор данных в UI**. Зафиксировать пробел в разделе «Backend gaps» отчёта и отрисовать честное отсутствие данных. + +--- + +## УЖЕ СДЕЛАНО — не переделывать + +Проверено по коду на `50fde5f`. Эти пункты из исходного черновика закрыты: + +| Пункт черновика | Факт | +|---|---| +| §14 OAuth URL копируется до нажатия «Открыть браузер» | Реализовано: `_init_antigravity_oauth()` вызывается при входе в шаг 2, поля `oauth_url_entry` и `copy_url_btn` уже есть | +| §22 AccountsView пересоздаёт все карточки | Уже keyed-дельта: `if p.profile_id in self._cards: card.update_from_model(...)`. Осталась пересборка внутреннего `quota_box` — только её и чинить | +| §36 запрет выдуманных метрик | Обеспечен, есть 7 регрессионных тестов (`test_data_truthfulness_and_oauth_security.py`) | +| §37.14 `importorskip` для UI-тестов | Сделано и закреплено CI-job `headless` + `tests/test_import_invariants.py` | + +**Изъято из задания как чужой scope** (передано Antigravity): §23 RoutingView-перестройка на уровне данных, §24 удаление `scan_all` из сервиса, §25 stale-политика, §27 планировщик обновлений, §28 событийная модель, §9/§10 сбор мульти-корзинных квот, §17 логика выбора модели. + +Вам остаётся **отрисовка** того, что отдаёт контракт. + +--- + +## Область задачи + +### PHASE 1 — Фундамент (не зависит от контракта) + +**Design tokens.** Единый набор: цвета, отступы, радиусы, типографика, высоты контролов, паддинги карточек. Никаких случайных `padx=7`/`padx=11` по коду. Тема остаётся тёмной (Hermes), но константы централизованы так, чтобы светлая тема later не требовала переписывания views. + +**Библиотека компонентов.** Нормализовать и достроить: `HubCard`, `SectionHeader`, `StatusBadge`, `PlanBadge`, `ProviderBadge`, `QuotaBar`, `QuotaBucketWidget`, `AccountCardWidget`, `AgentCardWidget`, `RouteTargetWidget`, `EmptyState`, `SearchField`, `FilterButton`, `ActionButton`, `IconButton`, `ConfirmDialog`, `Toast`. Одинаковая разметка не должна дублироваться по views. + +**Семантика цвета.** Зелёный — работает; янтарный — предупреждение, резерв, приближение к лимиту; красный — ошибка, недоступен, исчерпан; серый — отключён, неизвестно. **Золото — только брендинг и акцент, не статус «всё хорошо».** + +**Плотность и типографика.** Уменьшить заголовки и пустоты, унифицировать паддинги, высоты кнопок, радиусы. Длинные email — многоточие с полным текстом в подсказке. Карточки аккаунтов одинаковой геометрии. + +**Иконки.** Один стиль, на существующей инфраструктуре ассетов. Не менять всё на emoji и не тянуть тяжёлую зависимость. Логотип Hermes Hub и символика H остаются главным идентификатором. + +### PHASE 2 — Аккаунты и квоты (приоритетная страница) + +По контракту отрисовать так, чтобы за секунды читалось: провайдер, идентичность аккаунта, тариф, способ авторизации, здоровье, назначенная роль. + +**Идентичность обязательна.** Порядок предпочтения: email → username → provider user ID → внятный fallback. Не показывать «Google account #1», если реальная идентичность доступна в `ProfileViewModel`. + +**Тариф** показывать только когда провайдер его реально отдаёт (см. контракт), иначе не показывать бейдж вовсе. + +**Мульти-корзинные квоты.** Отрисовать столько корзин, сколько отдаёт `QuotaSnapshot`, с их собственными метками, остатками и временем сброса. Не сводить к одному проценту. Если снапшот помечен `is_estimated` — показать это явно, признак уже есть в модели. + +**Компактный режим.** Свёрнутая карточка: провайдер, идентичность, тариф, роль/статус, полосы квот, время сброса, действия. Развёрнутая: подробные корзины, доступные модели, роли, авторизация, приоритет в маршруте, отметки времени. + +**Группировка по провайдерам** со сворачиванием, плюс поиск и фильтры по провайдеру, здоровью, роли, тарифу — насколько это не усложняет код чрезмерно. + +**Дельта-отрисовка.** Внутренний `quota_box` не пересоздавать целиком: корзины обновлять по стабильному ключу. Удаление аккаунта уничтожает только его карточку. + +### PHASE 3 — Обзор + +Dashboard как обзорный центр управления, **только реальные данные**: состояние системы, доступные провайдеры, активные аккаунты, доступные агенты, предупреждения и ошибки; компактное представление активного маршрута; аккаунты с низкой квотой, приближающимся сбросом, исчерпанные, недоступные, с истёкшей авторизацией; последние реальные события журнала. + +Если backend не собирает величину — «Н/Д» или скрыть блок. Никаких «842 rps», «99.98 %», «1.23 % ошибок» из макетов, пока таких данных нет. + +Пункты навигации без функциональности — скрыть либо честный «Позже», но не пустые страницы и не выдуманные метрики. + +### PHASE 4 — Команда + +Иерархия «оркестратор → роли → агенты». Для агента: имя, роль, провайдер, аккаунт, модель, здоровье, индикатор квоты, активная сессия — там, где данные есть в контракте. Существующее переиспользование виджетов в `TeamView` не ломать. + +### PHASE 5 — Маршрутизация + +Понятная цепочка отказоустойчивости для каждой роли: основной → резерв 1 → резерв 2 → резерв 3. Для каждого узла: провайдер, аккаунт, модель, статус квоты, здоровье. Показывать активный узел и, если причина есть в состоянии маршрута, — почему произошло переключение (квота, авторизация, таймаут, недоступность, ручное). + +Сложный drag-and-drop не делать: редактор оставить кнопочным/селекторным, подготовив модель состояния под будущий React-фронтенд. + +### PHASE 6 — Второстепенные экраны + +Привести к единому стилю: состояние, журнал, настройки, «о программе», мастер подключения, диалоги. Мастер должен визуально соответствовать новому интерфейсу; **не ломать** уже работающие потоки Antigravity OAuth, Codex OAuth, Codex API, OpenCode, Claude, Grok и вставку из буфера. + +--- + +## Ограничения + +**Не переписывать приложение на Tauri/React.** Архитектура UI готовится к будущему разделению «состояние → ViewModel → компоненты», но миграция в этот scope не входит. + +**Не делать слепую переработку.** Запрещено «удалить `router/ui` целиком и написать заново». Итеративно, после каждой фазы приложение запускается. + +**Не блокировать UI-поток.** Сетевые запросы, опрос OAuth, получение квот, subprocess, сканирование диска — только в фоне, на существующей инфраструктуре потоков. + +**Секреты.** Не логировать access/refresh-токены и коды авторизации, не показывать API-ключи в открытом виде, не сохранять учётные данные в состоянии UI. Показ — только маскированный. + +**Тег `v0.1.1` не создавать**, релиз не публиковать, манифест не менять. + +--- + +## Критерии приёмки + +1. Ни один файл из чужой зоны не изменён (проверяется `git diff --name-only`). +2. Приложение запускается после каждой фазы. +3. Аккаунты показывают реальную идентичность; тариф — только когда он известен. +4. Отрисовывается несколько квотных корзин; ни одна не сведена к единому проценту; оценочные данные помечены. +5. `quota_box` обновляется по ключу, а не пересоздаётся; удаление аккаунта затрагивает только его карточку. +6. Изменение аккаунта A не перерисовывает карточку аккаунта B. +7. Маршрутизация показывает цепочку основной/резервы со статусами. +8. Команда показывает провайдера, аккаунт и модель там, где данные доступны. +9. OAuth URL копируется до открытия браузера; вставка из буфера работает; ни один из существующих потоков подключения не сломан. +10. Нет выдуманных метрик: отсутствующие данные показаны как «Н/Д» либо скрыты. +11. Секреты не попадают в UI и логи. +12. Проверено на 1280×720, 1366×768, 1920×1080 при масштабировании Windows 100 %, 125 %, 150 % — критичные элементы управления не уходят за экран. +13. Тесты: отрисовка идентичности, бейджа тарифа, нескольких корзин, отсутствующих полей квоты, неизвестного провайдера, неизвестного тарифа, длинного email, копирования OAuth URL, работы буфера обмена, цепочки маршрутизации, удаления одной карточки, независимости обновления A и B. Все UI-модули начинаются с `pytest.importorskip("customtkinter")`. +14. Полный `pytest` без новых падений; прогон **в окружении без UI-зависимостей** тоже зелёный; `ruff check .` чисто; release gate не ухудшен. + +## Проверка производительности + +На тестовых данных из 50 аккаунтов изменить квоту одного и зафиксировать в отчёте количество уничтоженных и созданных виджетов до и после изменений. Ожидается обновление одной карточки, а не пересоздание пятидесяти. + +## Отчёт + +`CODEX_UI_REDESIGN_REPORT.md`: `BASE_SHA`, `FINAL_SHA`, ветка, изменённые файлы, что переработано по экранам, что реально поддержано по идентичности/тарифам/квотам, результат замера производительности, точные команды и результат тестов (`X passed / Y skipped / Z failed`), известные ограничения и **обязательный раздел «Backend gaps»** — чего нельзя сделать одними изменениями UI. + +Скриншоты основных экранов приложить, если возможно. + +--- + +## Главное + +Интерфейс должен за несколько секунд отвечать на четыре вопроса: кто сейчас выполняет задачу, через какого провайдера, аккаунт и модель, сколько реального лимита осталось, и кто подхватит работу при недоступности исполнителя. Оптимизировать под ежедневное управление десятками аккаунтов, а не под красивый скриншот.