8.8 KiB
Dev Report: Конвейер UX (4 фичи)
Дата: 2026-06-03 Статус: DONE (PR открыт, НЕ мержён, НЕ задеплоен)
Задача
4 UX-фичи оркестратора в ветке feature/pipeline-ux:
- Старт по статусу (In Progress), не по created
- Вердикт-статусы Approved/Rejected (вариант B), комменты остаются рабочими
- Видимость стадий на доске (6 новых UUID статусов)
- Расход токенов по агентам
Реальный формат Plane webhook на смену статуса
Поймал в таблице events (event id=160, 28 issue-событий, есть action=updated):
{
"event": "issue",
"action": "updated",
"data": {
"id": "<issue-uuid>",
"state": {"id": "<state-uuid>", "name": "In Progress", "group": "started"},
"project": "7a79f0a9-5278-49cd-9007-9a338f238f9c",
"sequence_id": 2
},
"activity": {
"field": "state",
"new_value": "<new-state-uuid>",
"old_value": "<old-state-uuid>",
"actor": {...}
}
}
Ключ: event="issue", action="updated", новый статус в data.state.id (== activity.new_value).
Также поддержан вариант data.state как голая строка-UUID и алиас work_item.updated.
Таблица: фича → файлы/функции
| Фича | Файлы | Ключевые функции |
|---|---|---|
| 1. Старт по статусу | src/webhooks/plane.py |
plane_webhook (dispatch issue/updated), handle_issue_updated, handle_status_start, start_pipeline (вынесено из handle_work_item_created), _qg0_errors; handle_work_item_created → только soft-лог |
| 2. Вердикт-статусы | src/webhooks/plane.py, src/plane_sync.py |
handle_verdict (Approved→_try_advance_stage, Rejected→_rollback_stage), _rollback_stage (общий с :rejected:-коммент-веткой); UUID approved/rejected в PLANE_STATES |
| 3. Видимость стадий | src/plane_sync.py |
PLANE_STATES +6 UUID, STAGE_VISIBILITY_STATE, STAGE_TO_STATE (arch/dev/review/testing → свои статусы), set_issue_stage_state |
| 4. Токены | src/agents/launcher.py, src/db.py, src/usage.py (новый) |
--output-format json в claude-cmd; ALTER agent_runs (+input/output/cache_read/cost_usd, идемпотентно); _monitor_agent парсит usage; _post_usage_comments; usage.py: parse_usage_from_text/log, record_usage, usage_comment, task_summary_comment |
Идемпотентность / защита handle_comment (Фича 1)
handle_status_start проверяет get_task_by_plane_id(plane_id) — если задача уже есть, лог + return (НЕ дублит, НЕ перезапускает analyst). Поэтому set_issue_in_progress из handle_comment (approve/ответы) не триггерит повторный старт — задача к тому моменту уже в БД.
Token usage — формат коммента (вариант A)
Под именем агента (через add_comment(author=role)):
💻 Developer готов · 45.2k in / 12.1k out · $0.21
Сводка Deployer на done (вариант B, SUM по agent_runs GROUP BY agent):
📊 Итого по задаче: 1.5k токенов вход / 300 выход · $0.15
• Developer: 1.0k in / 200 out · $0.10
• Tester: 500 in / 100 out · $0.05
Боевой usage-парс (CLI 2.1.142, реальный вывод)
Запустил claude --print --output-format json "Say hi" в контейнере, прогнал parse_usage_from_log:
PARSED: {'input_tokens': 6, 'output_tokens': 15, 'cache_read_tokens': 18500, 'cost_usd': 0.0560175}
AGENT COMMENT: 💻 Developer готов · 6 in / 15 out · $0.06
Реальные поля JSON: total_cost_usd, usage.input_tokens, usage.output_tokens, usage.cache_read_input_tokens — подтверждены вживую.
Тесты
Baseline: 166 passed, 9 pre-existing 401 (test_webhooks.py). Итог: 190 passed, 9 failed (те же 9 baseline, не тронуты). Новые (+24):
tests/test_status_trigger.py— created не стартует; In Progress стартует analyst; повтор идемпотентенtests/test_verdict_status.py— Approved→advance, Rejected→rollback; комменты:approved:/:rejected:живыtests/test_stage_visibility.py— UUID в PLANE_STATES; set_issue_stage_state ставит верный UUID; analysis/deploy no-op; Needs Input/In Review/Blocked приоритетныtests/test_usage.py— парс реального CLI-JSON (в т.ч. с текстом перед JSON); NULL при битом JSON; запись; формат токенов k/M; сводка по задаче
Изменены существующие тесты под новый триггер (created→issue/updated In Progress):
tests/test_m6_sequence.py, tests/test_plane_webhook.py, tests/test_webhook_dedup.py.
Git
- Ветка
feature/pipeline-uxиз свежего main (e9fd305). - 4 коммита (Conventional Commits):
a4668c0feat(plane): stage visibility on board + verdict status UUIDs09b1c5efeat(webhook): start pipeline on In Progress status (not on create)38a741dfeat(webhook): verdict via Approved/Rejected statuses (variant B)9a702a0feat(metrics): per-agent token/cost accounting
- Remote проверен (ORCH-7 грабля):
git log origin/main..origin/feature/pipeline-uxпоказывает все 4 коммита. - PR #10 → main: admin/orchestrator#10
Ограничения соблюдены
- БД Plane / nginx / openclaw.json / deploy-хук / HMAC / очередь / get_next_work_item_id / M-6 / per-agent authorship — НЕ тронуты (используются).
- ORCH-6 резолв проектов НЕ переделан; оставлен TODO(ORCH-10) в PLANE_STATES (статусы per-project).
- НЕ деплоил, НЕ мержил — мерж за Стримом после живой проверки.
.env.bak-*(стрэй-бэкап) НЕ закоммичен.
ДОП-ФИКС: mute Telegram в тестах (срочный, выполнен ПЕРЕД остальным)
Проблема: прогон pytest на проде слал РЕАЛЬНЫЕ Telegram Славе (тесты двигают стадию → notify_stage_change → send_telegram, читает живой .env).
Фикс: tests/conftest.py (новый) — autouse-фикстура _no_telegram глушит send_telegram.
- Патчит источник
src.notifications.send_telegram(покрывает всеnotify_*и локальныеfrom .notifications import send_telegramвнутри функций — webhooks/plane, launcher, queue_worker, main). - Дополнительно патчит
src.stage_engine.send_telegram— там import на уровне модуля (3 прямых вызова), source-патч его НЕ перехватил бы. raising=Falseвезде.
Доказательство (не на доверии): прогнал полную сюиту с ФЕЙКОВЫМИ telegram-кредами (ORCH_TELEGRAM_BOT_TOKEN=fake-leak, CHAT_ID=99999) + трип-вайр на httpx.post (через sitecustomize), бросающий BaseException (чтобы except Exception в send_telegram НЕ проглотил) при любом вызове api.telegram.org:
- БЕЗ conftest → трип-вайр срабатывает (
TelegramLeak: ...botfake-leak/sendMessage) → утечка реальна. - С conftest → 190 passed, 9 baseline, 0 обращений к api.telegram.org (leak-файл не создан). Славе ничего не прилетело.
Коммит: 7fd6529 test(conftest): mute Telegram in all tests to stop prod leakage — в том же PR #10.
Замечание для Стрима (живая проверка)
- Rejected-вердикт (и
:rejected:коммент) теперь перезапускает агента предыдущей стадии (черезSTAGE_AUTHORS[prev_stage]) — это унифицировано для обоих механизмов (раньше коммент-ветка для non-analysis только откатывала без перезапуска). Так выполнено требование ТЗ «откат + перезапуск агента». - Стадийные статусы видны на доске только в проекте enduro (UUID per-project). При онбординге новых проектов — ORCH-10.