Обе работы приняты, проверено исполнением.
A24: ровно семь разделов, renderTeam и renderProviders удалены, кнопки
«Изменить цепочку» нет, перетаскивание блоков есть. Перестановка цепочки
проверена вживую: сохраняется в router_profiles.yaml и откатывается.
A25: провайдер local подключён к настоящему серверу владельца через
SSH-туннель к 127.0.0.1:8081. health_check проходит, /v1/models отдаёт
модель, реальный вызов возвращает «ОК» за 3.1 с. Профили local-1 и
local-2 добавлены. Квота отдаётся отдельным состоянием
(source=local_provider, «Без ограничений»), а не как отсутствие данных.
Адрес сервера нигде не зашит.
Разрешение конфликта в action_handler: A25 внёс edit_route и assign_role
в список «просто навигация», где они возвращают заглушку. В A24 это
работающие обработчики — сохранение цепочки и назначение роли. Приняв
версию A25 целиком, мы бы молча сломали перестановку блоков. Оставлены
оба: локальный провайдер в add_account и рабочие обработчики ниже.
Исправлено при слиянии:
1. Адаптер отдавал пустой ответ как успех. У сервера владельца
--reasoning on --reasoning-budget 4096: при скромном max_tokens весь
бюджет уходит на рассуждения, llama.cpp возвращает 200, заполняет
reasoning_content и оставляет content пустым. Проверено на живой
модели: max_tokens=40 — ответа нет, 200 — приходит «ОК». Роутер
засчитал бы такой вызов, а пользователь не получил бы ничего.
Теперь это явный отказ с объяснением, и срабатывает переключение.
Проверка вынесена из блока перехвата: иначе оборачивалась в
«Transport Error», хотя транспорт отработал штатно.
2. Заглушка в тесте A25 возвращала "choices": [] — такого настоящий
сервер не отдаёт. Приведена к реальному виду.
Тесты: 404 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Владелец: «грок не даёт добавить https://x.ai/device» — страница отдаёт
404. Причина хуже опечатки в адресе.
Веб-мастер для Grok и Codex не был подключён к серверу ВООБЩЕ. Он
показывал жёстко вписанный адрес x.ai/device (настоящий —
auth.x.ai/device, и тот приходит от провайдера полем verification_uri) и
ВЫДУМАННЫЕ коды устройства GRK-7842 и CDX-9104. Пользователь вводил бы
несуществующий код бесконечно.
Это тот самый класс дефекта, который вычищали из десктопа в первом
аудите, вернувшийся в новом коде.
Выдуманные значения убраны. Пока поток не проведён через веб-API, шаг
честно сообщает, что подключение через веб не реализовано, и указывает
рабочий путь — десктопное приложение, где поток проведён полностью.
Отдельно: тест test_headless_server_auth_matrix ТРЕБОВАЛ наличия
"https://x.ai/device" в коде, то есть закреплял дефект как требование.
Переписан на противоположное — запрещает выдуманные коды и зашитые
адреса провайдеров.
Тесты: 375 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Со скриншота владельца: заголовок «Ролей в строю: 0/6», а ниже шесть
предупреждений «роль работает через резервный аккаунт». Пять ролей
исправно отвечали, интерфейс сообщал, что не работает ни одна.
roles_ready считал только роли со здоровым ОСНОВНЫМ профилем. Роль,
обслуживаемая резервом, попадала в degraded_roles, но не в ready.
Ошибка в худшую сторону: отказ показывался там, где всё работает — а
переключение на резерв это ровно то, ради чего продукт и создан.
Теперь роль с живым резервом считается работающей и одновременно
помечается деградировавшей: оба состояния остаются различимыми.
Попутно склонение: «Есть 1 ролей без рабочего маршрута» заменено на
согласованное с числом — 1 роль, 2 роли, 5 ролей.
Проверено на живых данных: 6/6 в строю, состояние «деградация», а не
«критическое».
Тесты: 375 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Три дефекта из установки владельца на вторую машину.
1. Веб-сервер падал сразу: «web server process terminated unexpectedly».
Установщик ставит в venv Hermes только customtkinter, pillow, pyyaml и
psutil — fastapi и uvicorn отсутствовали в списке вовсе. Добавлены во
все шесть мест: проверка, сообщение, pip, uv, перепроверка.
Поэтому на Windows открывался только десктоп: веб физически не мог
стартовать.
2. Лаунчер показывал голое «terminated unexpectedly» без причины — та же
болезнь, что у кода 12. Теперь перехватывает вывод процесса и выводит
последние строки ошибки в окне.
3. Окно тормозило при перетаскивании и продолжало двигаться несколько
секунд после отпускания мыши. _RouteDiagram перерисовывал всю канву на
КАЖДОЕ событие <Configure>, а при перетаскивании их сотни; очередь не
успевала разгребаться. Гашение в приложении существовало, но
_handle_debounced_resize был пустой заглушкой.
Перерисовка сведена к одной после затишья. Замерено: 199 событий
давали 199 перерисовок, теперь 4.
Тесты: 373 passed, ruff чисто. Установщик пересобран.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Найдено проверкой всех шести ролей на живой машине владельца. До правок
работали три роли из шести.
1. antigravity_adapter.classify_error возвращал ErrorCategory.UNKNOWN —
значения с таким именем не существует, есть AUTH_REQUIRED, FATAL,
INVALID_REQUEST, QUOTA_EXHAUSTED, RATE_LIMITED, TRANSIENT. Обращение
роняло сам классификатор с AttributeError, то есть отказ происходил
ровно там, где обрабатывался другой отказ, и маршрутизация обрывалась
вместо перехода к резервному профилю. Заменено на FATAL по образцу
codex и opencode: неразобранная ошибка не должна давать повторов.
2. Уровень усилия не подставлялся, если у конкретного профиля не выполнен
вход agy: карта усилий строится обнаружением ЧЕРЕЗ этот профиль, и при
неудаче оставалась пустой. Уровни же — свойство модели, а не аккаунта.
Добавлен запасной источник: сохранённый на диске список моделей со
склеенными идентификаторами вида gemini-3.7-flash-high, из которых
уровни выводятся напрямую и переживают перезапуск.
После правок работают все шесть ролей, включая живое переключение
'fast': opengo-1 -> ag-w4.
Закрыто тестами, включая защиту от возврата несуществующей категории.
Тесты: 373 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Проверено исполнением, оба главных пункта закрыты.
P0-1, восстановление после отказа авторизации: выбран самый честный из
предложенных вариантов — снятие отметки по событию починки, а не по
таймеру. HealthTracker подписывается на EVENT_ACCOUNT_ADDED и
EVENT_ACCOUNT_AUTH_CHANGED. Проверено: профиль с AUTH_REQUIRED после
события возвращается в строй без ручного вмешательства.
P0-2, проверка моделей. Выдуманная модель отклоняется; базовое имя
gemini-3.7-flash принимается (поправка учтена); склеенное
gemini-3.1-pro-high тоже. Главное — при ПУСТОМ кэше выдумка больше не
проходит: молчаливое согласие устранено.
P0-3, ручное обновление списка моделей: действие refresh_models плюс
кнопка в клиенте.
Исправлено при слиянии:
1. Блокировка файла состояния была переведена с неблокирующей на
БЕСКОНЕЧНО блокирующую. Исходный вариант был неверен — при неудаче
исключение проглатывалось и запись шла без блокировки, — но
бесконечное ожидание хуже: на Unix flock(LOCK_EX) висит вечно, и один
застрявший держатель подвесил бы приложение целиком. Впереди
Linux-сервер. Ожидание ограничено 5 секундами, дальше честный отказ.
Проверено: 6 потоков по 5 записей — 0.11 с, ошибок нет.
2. Действие refresh_models не было занесено в контракт. Ровно тот дрейф,
ради предотвращения которого контракт и существует. Контракт поднят
до 1.3, действий стало девятнадцать.
Тесты: 370 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Владелец возразил на моё утверждение, что gemini-3.7-flash не существует.
Он прав, утверждение было неверным, и я повторил его в четырёх заданиях.
gemini-3.7-flash — настоящее семейство, уровень усилия у неё отдельный
параметр. В интерфейсе Antigravity это видно прямо: пункт «Gemini 3.7
Flash» с вложенным выбором Low/Medium/High. В коде это отражено:
_display_to_cli разбирает «Gemini 3.7 Flash (High)» в пару
("gemini-3.7-flash", "high"). Меня ввела в заблуждение первая колонка
вывода agy models со склеенными идентификаторами.
Настоящий дефект был в коде: _model_supported_efforts вызывала
discover_models() БЕЗ профиля, то есть в глобальном окружении без входа.
Карта поддерживаемых усилий оставалась пустой, подстановка уровня по
умолчанию не срабатывала, и agy отвергал вызов с «requires --effort» —
при совершенно настоящей модели.
profile_id проведён через agy_generate в _model_supported_efforts.
Проверено исполнением: gemini-3.7-flash без указания усилия отрабатывает
и возвращает ответ.
Моки в test_antigravity_concurrency приведены к терпимости по kwargs.
Добавлена поправка agents/inbox/2026-08-24-CORRECTION-gemini-model-names.md:
ложное утверждение попало в A9, A11, A18 и B8, и без опровержения кто-то
чинил бы несуществующую проблему или сломал рабочую конфигурацию.
Тесты: 359 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Четыре недостающих экрана добавлены: Аналитика, Состояние, Журнал
событий, Настройки. Веб покрывает все девять разделов десктопа.
Два новых эндпоинта реализованы: GET /api/events и GET /api/settings.
Секреты не утекают — проверено исполнением, наружу отдаётся только
признак web_api_token_configured (bool), сам токен отсутствует.
Слияние без конфликтов. Тесты: 359 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Проверено исполнением, все три требуемых доказательства получены.
1. agy models через профиль Hub: обнаружено 14 моделей, включая
gemini-3.1-pro-high и gemini-3.1-pro-low.
2. Реальный вызов adapter.invoke(ag-w1): модель ответила «ОК».
3. route_request(coder-primary): переключение с отказавшего
codex-worker-1 на ag-w1, ответ получен, router_error отсутствует.
Подход из A20 (синтез oauth_creds.json) действительно был тупиковым;
родной вход agy с подменой HOME решает задачу. Реализация аккуратная:
видимая консоль на Windows через CREATE_NEW_CONSOLE, терминалы на Linux,
HOMEDRIVE выставляется корректно.
P0-4 подтверждён: пересохранение профиля не изменяет ни одного файла в
глобальном ~/.gemini.
Исправлено при слиянии:
1. Инструкция в веб-клиенте вела на НЕСУЩЕСТВУЮЩИЙ файл
launcher/main.py. Заменена на реальный вызов launch_native_agy_login
с указанием профиля.
2. test_headless_server_auth_matrix проверял дословную формулировку и
падал при её правке, хотя поведение оставалось верным. Приведён к
проверке сути, добавлена защита от возврата несуществующего пути.
Конфигурация владельца: обоим кодерам поставлена gemini-3.1-pro-high по
его прямой просьбе — теперь это возможно.
Тесты: 355 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Add openid to OAuth scopes for id_token issuance
- Preserve id_token, scope, and token_type on token exchange and refresh
- Atomically write and sync .gemini/oauth_creds.json in profile directories
- Auto-resolve active profile environment in discover_models
- Cache discovered models in models_cache.json with graceful timeout handling
- Add unit test coverage for full OAuth lifecycle and model discovery caching
A17 принято, проверено на живых профилях владельца: «Работает» больше не
ставится непроверенному профилю. Из 22 профилей теперь 1 «Работает»
(есть записанный успех), 7 «Не проверялся», 11 «Аккаунт не добавлен»,
3 «Отключён». Grok и opengo-1, на которые жаловался владелец, показаны
честно. Добавлено поле last_success_at.
A18 принято частично: действие set_model существует и валидирует модель
по списку провайдера. Но обнаружение моделей (P0-2) не сделано вовсе —
model_discovery_service и зонд не менялись, ручного обновления нет,
кэша на диске нет. Из-за этого set_model отклоняет ЛЮБУЮ модель, включая
настоящую: список провайдера пуст, и валидация не с чем сравнивать.
Правка при слиянии: discover_models запускался в ГЛОБАЛЬНОМ окружении,
где вход agy не выполнен, — при шести рабочих OAuth-профилях. Теперь
принимает profile_id и подменяет HOME/USERPROFILE на каталог профиля,
как это делает adapter.invoke. Таймаут поднят с 10 до 60 секунд.
Это не вылечило симптом, и причина оказалась глубже — см. отчёт.
Тесты: 336 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Владелец: «стоит опенкод аккаунт, который не подключен, у него кончились
лимиты и аккаунт не работает». Лимиты ни при чём.
Мастер подключения сохраняет ключ через ProfileAuthManager, а
_resolve_api_key смотрел только в auth_config из YAML и в переменные
окружения. Хранилище профилей он не читал вовсе — в отличие от grok,
claude и codex, где такая проверка есть.
Следствие: любой аккаунт OpenCode Go, подключённый через интерфейс, был
нерабочим. Маршрутизация падала с «No API key found for OpenCode Go
profile», и эта строка уже попадалась в следе отказов оркестратора.
Проверено на живом профиле владельца: до правки health_check=False и
тест профиля возвращал «локальный runtime недоступен», хотя api_key
лежал в auth.json. После — health_check=True, тест проходит.
Закрыто тестами, включая проверку, что пустое хранилище по-прежнему
даёт отказ, а не ложноположительный результат.
Тесты: 331 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Владелец запустил веб, увидел «Н/Д» у всех аккаунтов и сообщил, что
лимиты не подтягиваются. Через пятнадцать секунд всё появилось: опрос
провайдера просто ещё шёл.
Признак is_loading сервер отдавал (баз�овый снапшот выставляет его при
незавершённом опросе), но клиент его игнорировал и рисовал «Н/Д» — тот
же текст, что у подключённого аккаунта без лимитов. Два разных состояния
выглядели одинаково, и различить их было нельзя.
Теперь во время опроса ячейка показывает «Загрузка…» и «Опрашиваем
провайдера…» вместо прочерка.
Причина отказа важнее флага: если провайдер уже ответил «лимитов не
даю», состояние загрузки подавляется — иначе opencode-go и grok
показывали бы «Загрузка…» бесконечно.
Закреплено тестом в test_web_client_contract.py, включая проверку этого
подавления.
Тесты: 328 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/api/snapshot отдавал квоты с source="baseline" и нулём измеренных
корзин всегда. Две независимые причины, и лечение одной из них ничего
не давало.
1. state_store наполняет квоты через quota_service.get_snapshot, который
читает кэш и при промахе отдаёт пустую заглушку, живой опрос НЕ
запуская. Кэш никто не грел: в десктопе это делал
_refresh_quotas_on_startup, в вебе аналога не было. Штатный
планировщик службы не спасает — его цикл сначала спит интервал
(300 с по умолчанию) и только потом опрашивает.
2. HubStateStore.get_snapshot() возвращает КЭШИРОВАННЫЙ снапшот и
пересобирает его только при первом вызове. Даже после прогрева квот
ответ оставался прежним. В десктопе пересбор делал _refresh_data.
Добавлен фоновый цикл: прогрев квот при старте, затем пересбор снапшота
каждые 30 секунд. Порядок важен — снапшот, собранный до прогрева,
зафиксировал бы пустые корзины.
Проверено исполнением: квоты появляются через ~10 секунд после старта,
24 измеренных корзины, source=provider_api, ag-w2 Gemini неделя 80.5% —
совпадает с прямым опросом провайдера.
Регрессия закрыта tests/test_web_snapshot_freshness.py, включая проверку
порядка «прогрев перед пересбором».
Тесты: 327 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A15 и A16 сошлись в пустоту: API отвечал, файлы клиента лежали в
репозитории, но server.py не монтировал static — в браузере был 404 и до
интерфейса было не добраться.
Причина организационная и она на ревьюере: контракт описал каталог
static/ в структуре пакета, но в разделе об эндпоинтах не назвал, кто его
отдаёт. Обе стороны выполнили написанное и всё равно не собрались.
Подключены StaticFiles и корневой маршрут. Проверено исполнением:
/ -> 200 (13.5 КБ), /app.js -> 200 (51 КБ), /style.css -> 200 (21 КБ),
/api/health -> 200, /api/snapshot -> 200 (98 КБ).
Тесты: 325 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Правки при приёмке A15. Веб-API и вынесение действий приняты, но
в сданном виде не работали ни то, ни другое.
1. Десктоп был уничтожен. При выносе действий из hermes_hub_app.py
пропало объявление class HermesHubApp вместе с 13 методами каркаса:
__init__, _build_layout, _create_view, _show_view, _refresh_data и
другими. Оставшиеся 14 методов оказались вложены внутрь функции
_load_saved_theme после её return — синтаксически валидный
недостижимый код, поэтому модуль импортировался и дефект выглядел
безобидно. launch_hub() при этом падал бы с NameError.
hermes_hub_app.py восстановлен из main; задание прямо требовало
десктоп не ломать.
2. Дублирование убрано правильным способом: десктоп импортирует пять
do_* из action_handler, второй реализации в проекте нет.
3. Веб-API падал с 500 на обоих значимых эндпоинтах: get_auth_token и
run_server читали config.hub, а такого атрибута у RouterConfig нет.
Настройки живут в hub_settings.json. Работал только /api/health, у
которого нет проверки авторизации, — из-за чего сервер и выглядел
поднявшимся.
4. do_save_settings при переносе потеряла атомарную запись через
os.replace, ensure_ascii=False и вызов set_refresh_interval, то есть
интервал обновления квот из настроек перестал применяться.
Восстановлено.
5. Импорт адаптера был убран внутрь do_test_profile, что делало функцию
неподменяемой в тестах. Поднят на уровень модуля.
6. Версия в /api/health была зашита как "1.0.0" вместо настоящей.
Проверено исполнением: /api/snapshot отдаёт 200 и 12 ключей, полностью
совпадающих с docs/web-api/snapshot.example.json; секретов в ответе нет;
неизвестное действие даёт 404. Тесты: 319 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Работа A15 выполнена, но не закоммичена: git в его окружении был
недоступен. Восстановлена ревьюером из рабочего каталога.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Работа A11 была выполнена, но осталась незакоммиченной в рабочем каталоге
на машине владельца: в origin ушла пустая ветка. Восстановлена ревьюером
из рабочего дерева и зафиксирована здесь.
Содержание:
- agy_subprocess: зонд обнаружения моделей переведён с разбора ошибки
заведомо неверной модели на штатную команду `agy models`;
- antigravity_adapter: убран выдуманный запасной список gemini-2.5-*,
моделей с такими именами у провайдера не существует;
- codex_oauth: добавлен refresh_codex_token — обновление по refresh_token,
которого не было вовсе;
- quota_collector: source больше не заявляет provider_api там, где ни
одна корзина не измерена; применено к antigravity и opencode-go;
- hermes_plugin: правка обработки роли, поверх сохранённой 2d62d39.
Исправлено при фиксации: тест test_opencode_shows_published_limits
закреплял прежнюю семантику source и падал. Приведён к честной:
source описывает происхождение чисел, а не факт ответа провайдера;
информация об ответе сохраняется в unavailable_reason.
Проверено исполнением: `agy models` сейчас нестабилен и висит даже при
прямом вызове (rc=124 по таймауту 100 с) — зонд честно возвращает пусто
и сохраняет кэш, а не выдумывает список.
Тесты: 303 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Проверено исполнением на живой конфигурации владельца.
Принято:
- миграция конфигурации работает: 16 -> 22 профиля, claude и grok
получили по 3 слота, find_free_slot возвращает существующие профили по
всем пяти провайдерам. Это снимает корень жалобы «при подключении
грока ошибка»;
- резервная копия router_profiles.yaml.bak_<ts> создаётся, десять
профилей antigravity не изменены ни в одном поле, комментарии не
потеряны;
- квоты grok и opencode-go честно отдают None с причиной вместо
правдоподобных чисел;
- флаг /repair и /reinstall задействован (строка 979), предупреждение
CS0219 при сборке исчезло;
- граница зоны Codex не нарушена, правка плагина 2d62d39 сохранена.
Исправлено при слиянии:
1. Служба обнаружения моделей была недостижима. A9 создал
model_discovery_service.py, интерфейс импортирует model_discovery.
Импорт обёрнут в except ImportError, поэтому расхождение не давало
ошибки — выбор моделей просто оставался пустым навсегда. Добавлена
согласованная точка входа model_discovery.py.
2. Служба отдаёт discovered_at, каталог искал fetched_at/updated_at.
Каталог научен понимать discovered_at.
3. tests/test_ui_routing_graph.py закреплял выдуманный список моделей
("grok-3"). A9 верно убрал литералы, и тест начал падать. Тест
приведён к честному поведению: до обнаружения профиль остаётся без
моделей. Файл в зоне Codex, которому A9 запрещено было её трогать.
Тесты: 307 passed, 2 skipped, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex остановился на исчерпании лимитов. Влито то, что сделано:
- выбор модели: ui/model_catalog.py с честным пустым состоянием;
- карточка кликабельна целиком (cursor=hand2 + Button-1), «три точки»
перестали быть единственным входом;
- окно настроек роли: _open_agent_settings_modal;
- правая панель «Статус в реальном времени» убрана, центр расширен;
- жёсткие срезы providers[:3] и agents[:5] в диаграмме устранены;
- причины у части Н/Д через unavailable_reason.
Не сделано и уходит в задание: компактные карточки аккаунтов
(accounts_view.py не тронут) и разбор дублирующих разделов
«Провайдеры»/«Квоты» (quotas_view.py не тронут).
Проверено: слияние без конфликтов, правка плагина 2d62d39 сохранена,
290 passed, ruff чисто. Падает только известный нестабильный Tk-тест,
воспроизводится на чистом main.
Каталог моделей сейчас честно пуст: он импортирует
router/model_discovery.py, которого в репозитории нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hub подключён к Hermes как middleware llm_execution и срабатывает на
каждом обращении к модели. Но Hermes роль не передаёт: в kwargs есть
model, provider, session_id, task_id — role нет. resolve_role поэтому
сваливается в роль по умолчанию, и КАЖДЫЙ вызов Hermes маршрутизируется
как orchestrator.
Цепочка orchestrator у владельца исчерпана целиком:
ag-orch-fallback skipped_unhealthy
codex-orch 429 «account is not active, check billing»
opengo-3 No API key found
ag-w1/ag-w3 agy authentication failed or timed out
Роутер возвращал «⚠️ Hermes Router Failover Exhausted» как ответ
ассистента, и Hermes показывал это вместо ответа модели, хотя его
собственный провайдер работал. Это и есть «основной оркестратор не
выбрался» из отчёта владельца.
Теперь при router_error вызов уходит дальше по цепочке (next_call),
а отказ пишется в журнал уровнем warning с полным следом. Плагин обязан
быть незаметным при отказе: он может улучшить маршрутизацию, но не имеет
права сделать Hermes хуже, чем без него.
Проверено исполнением: Hermes получает ответ провайдера, а не текст
ошибки. Тесты: 287 passed (падает только известный нестабильный Tk-тест,
воспроизводится на чистом main).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Сводит две работы: A8 (Antigravity — запуск, развёртывание, самопроверка)
и codex/usability-fixes (B6 граф маршрутизации + B7 дефекты живого прогона,
плюс живой сбор квот).
Проверено исполнением на реальных аккаунтах владельца: квоты Antigravity
теперь приходят от провайдера (source=provider_api) по всем шести
авторизованным профилям с разными числами — ag-w2 показывает 37.4%
остатка недельного пула Claude/GPT. OpenCode Go честно отдаёт None
с причиной.
Разрешение конфликтов:
1. do_test_profile — оба агента чинили P0-3 по-разному. Сохранены обе
правки: проверка просроченной авторизации (A8) поверх локальной
проверки runtime без вызова модели (Codex).
2. _finish в мастере — взята содержательная версия Codex (проверка слота,
создание определения профиля, внесение в маршрутизацию, сброс
cooldown), но её хвост обёрнут так, чтобы сбой в журналировании или
on_complete не оставлял окно открытым. Регрессия 7090c8a закрыта
тестом и продолжает проходить.
Исправлено при слиянии: A8 проверял status.get("expired"), тогда как ключ
называется is_expired. Проверка была мертва изначально — её прикрывал
контроль в адаптере, и это вскрылось только когда Codex убрал вызов
адаптера из «Теста»: протухший аккаунт получал зелёную галочку.
Тесты: 288 passed, 2 skipped, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
_finish первой же строкой вызывал EventLogService.log_event — метода с
таким именем у сервиса нет, есть log(category, message, details, level).
AttributeError уходил в обработчик Tk, а под pythonw консоли нет, поэтому
для владельца кнопка просто не работала: окно оставалось открытым,
on_complete не вызывался, аккаунт не попадал в маршрутизацию.
Вызов приведён к настоящему API. Журналирование и обратный вызов
обёрнуты так, чтобы сбой в них не запирал пользователя в мастере, —
закрытие окна не должно зависеть от побочных действий.
Воспроизведено и проверено исполнением: до правки winfo_exists=1 и
on_complete не вызван, после — окно уничтожено, результат передан.
Тесты: 252 passed в venv Hermes. Единственный сбой
(test_oauth_lifecycle::test_f_copy_before_open_browser, TclError) и
FAILED релизного гейта воспроизводятся на чистом main и к этой правке
отношения не имеют.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>