Files
wiki/tasks/orchestrator/reports/dev-2026-06-03-pipeline-ux.md
2026-06-03 18:30:01 +03:00

8.8 KiB
Raw Blame History

Dev Report: Конвейер UX (4 фичи)

Дата: 2026-06-03 Статус: DONE (PR открыт, НЕ мержён, НЕ задеплоен)

Задача

4 UX-фичи оркестратора в ветке feature/pipeline-ux:

  1. Старт по статусу (In Progress), не по created
  2. Вердикт-статусы Approved/Rejected (вариант B), комменты остаются рабочими
  3. Видимость стадий на доске (6 новых UUID статусов)
  4. Расход токенов по агентам

Реальный формат 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):
    • a4668c0 feat(plane): stage visibility on board + verdict status UUIDs
    • 09b1c5e feat(webhook): start pipeline on In Progress status (not on create)
    • 38a741d feat(webhook): verdict via Approved/Rejected statuses (variant B)
    • 9a702a0 feat(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_changesend_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.