# Dev Report: orchestrator — живой Telegram-трекер задачи (Вариант B+) Дата: 2026-06-04 Статус: DONE ## Задача Заменить ~15 отдельных ТГ-сообщений на задачу одним живым сообщением-трекером (editMessageText по стадиям). Отдельными сообщениями с пингом — только approve-gate, deploy-fail, agent-fail, error. ## Результат (кратко) - **PR:** #21 — https://git.mva154.duckdns.org/admin/orchestrator/pulls/21 - **Ветка:** `feat/telegram-live-tracker` от `main` (2801983) - **Commit:** `9a0298d` - **pytest:** `259 passed, 9 failed` (те же off-limits HMAC/401 в test_webhooks.py). Baseline на этом хосте — `244 passed, 9 failed`; +15 новых проходящих тестов, 0 новых падений. (В ТЗ значился baseline 243+10 — на этом хосте фактически 244+9, набор падений идентичный и не тронут.) - НЕ смержен — на ревью Стрим. ## Изменённые файлы (6, +893/-35) - `src/db.py` — idempotent ALTER-ы: `tasks.tracker_message_id`, `tasks.title`, `tasks.brd_review_started_at`, `tasks.brd_review_ended_at`, `agent_runs.model`. Хелперы: `get/set_tracker_message_id`, `mark_brd_review_started/ended`. - `src/usage.py` — `short_model_name()` (отрезает провайдер-префикс и `claude-`); `_extract_model()` парсит модель из result-JSON (`modelUsage`); `record_usage` сохраняет модель (`COALESCE`, не затирает существующую). - `src/notifications.py` — ядро: - `render_task_tracker(task_id)` — stateless рендер из `agent_runs` + task. - `update_task_tracker(task_id)` — sendMessage→store id→editMessageText, fallback на новое сообщение при фейле edit, всегда `disable_notification=true`. - `edit_telegram()`, `send_telegram()` теперь возвращает message_id. - `notify_*` переведены в режим «только трекер / только лог», кроме 4 алертов. - `src/stage_engine.py` — штамп `brd_review_ended` на переходе analysis→architecture. - `src/webhooks/plane.py` — сохранение `title` задачи при создании. - `tests/test_telegram_tracker.py` — новые тесты (см. ниже). ## Формат трекера ### В процессе (deploy идёт) ``` 🛠️ ET-012 · Треки с зума z5 ━━━━━━━━━━━━━━━━━━━━━━ ✅ Analysis 10м · 1.1M↓/39.6k↑ · $2.38 · opus-4-8 ⏸️ Ревью БРД 8м · твоё время ✅ Architecture 9м · 1.5M↓/34.4k↑ · $2.24 · opus-4-8 ✅ Development 11м · 8.4M↓/45.8k↑ · $7.29 · opus-4-8 ✅ Review 3м · 1.2M↓/12.9k↑ · $1.53 · sonnet-4.6 ✅ Testing 5м · 1.2M↓/19.5k↑ · $1.51 · sonnet-4.6 🔄 Deploy … · идёт ━━━━━━━━━━━━━━━━━━━━━━ 💰 13.4M↓ / 152.2k↑ · $14.95 ``` (пока Deploy ещё не финишировал — токены deployer-а не учтены в Итого; на финише учтутся.) Пока идёт ожидание ревью БРД, строка показывает `· твоё время ⏳`. ### На финише ``` 🎉 ET-012 · Треки с зума z5 — ГОТОВО ━━━━━━━━━━━━━━━━━━━━━━ ✅ Analysis 10м · 1.1M↓/39.6k↑ · $2.38 · opus-4-8 ⏸️ Ревью БРД 8м · твоё время ✅ Architecture 9м · 1.5M↓/34.4k↑ · $2.24 · opus-4-8 ✅ Development 11м · 8.4M↓/45.8k↑ · $7.29 · opus-4-8 ✅ Review 3м · 1.2M↓/12.9k↑ · $1.53 · sonnet-4.6 ✅ Testing 5м · 1.2M↓/19.5k↑ · $1.51 · sonnet-4.6 ✅ Deploy 6м · 1.6M↓/22.4k↑ · $1.73 · opus-4-8 ━━━━━━━━━━━━━━━━━━━━━━ 💰 15.0M↓ / 174.6k↑ · $16.68 ⏱️ Всего 56м · агенты 44м · твоё 8м 🔗 PR #24 · 📦 deployed ``` (Точные значения совпадают с макетом из ТЗ; токены in = input+cache_read+cache_creation, out — отдельно; модель — короткая; время в минутах, <1м → `<1м`.) ## Что теперь НЕ шлётся отдельными сообщениями (только трекер / лог) - Старт агента (`notify_agent_started`) → refresh трекера. - Завершение агента (`notify_agent_finished`) → refresh трекера. - Переход стадии (`notify_stage_change`) → refresh трекера. - QG-pending / QG-failed (`notify_qg_failure`, `notify_qg_result`) → только лог (CI state: pending — не ошибка). - QG-passed → только лог. - Тех-мусор (run_id, exit_code, пути логов) — убран из ТГ-текста. ## Что ОСТАЁТСЯ отдельным сообщением (с пингом, disable_notification=false) - 📋 Approve-gate (`notify_approve_requested`) — текст про перевод в Approved (устаревший `:approved:` заменён). Заодно стартует «твоё время» (BRD review clock). - 🚨 Deploy упал / откат (`send_telegram` в launcher.py и stage_engine.py) — без изменений. - ❌ Агент упал, exit_code != 0 (`send_telegram` в launcher) — путь к логам только в лог-файл (в коде launcher уже так; текст ТГ не содержит run_id-мусора по новым правилам трекера). - 🔴 Ошибка задачи (`notify_error`). ## Тесты (tests/test_telegram_tracker.py) - `short_model_name`: tokenator/claude-opus-4-8→opus-4-8, vibecode/claude-sonnet-4.6→sonnet-4.6, None/'' → ''. - парсинг модели из `modelUsage`. - `render_task_tracker`: строки этапов (in↓/out↑·cost·model), строка Ревью БРД (твоё время / ⏳ при ожидании), активная стадия 🔄 идёт, блок Итого 💰, финиш ⏱️ (три времени) + 🔗/📦, экранирование <>& в названии, опускание модели если неизвестна. - send/edit/fallback: первое сообщение → send + store id (silent); переход → editMessageText существующего; edit-фейл → новое сообщение + обновлённый id. - алерты: approve-gate и error шлются ОТДЕЛЬНО (disable_notification=false); stage-change / agent-start / QG-pending НЕ шлют отдельных сообщений. ## Проблемы и решения - **f-string + backslash на Python 3.10 (прод-хост):** литерал с `\uXXXX` внутри `{...:<13}` ломал парсинг. Решено — вынес метку «Ревью БРД» в константу `_BRD_LABEL`. - **локальный `import httpx` в `_done_link`** мешал моку → перешёл на module-level `httpx`. - **Источник модели:** конфиг launcher задаёт `--model` не для всех агентов; надёжный источник — `modelUsage` из result-JSON → сохраняю в `agent_runs.model`. - **title задачи** в `tasks` не хранился → добавил колонку + populate при создании. ## НЕ тронуто (по ТЗ) - usage_comment / Plane-комменты (формат fix #20), PLANE_STATES, launcher.py deployer-guard, HMAC/queue/cost-расчёт, conftest, nginx/.env. ## Деплой Код в проде НЕ задеплоен (образ собирается из src в image, не из репо) — это задача мержа/CI после ревью. Миграции БД idempotent (`_ensure_column`), безопасны на живой проде при следующем `init_db()`.