diff --git a/tasks/orchestrator/DEV_TASK_TRACKER_EDIT_FIX.md b/tasks/orchestrator/DEV_TASK_TRACKER_EDIT_FIX.md new file mode 100644 index 0000000..c9e7f2a --- /dev/null +++ b/tasks/orchestrator/DEV_TASK_TRACKER_EDIT_FIX.md @@ -0,0 +1,92 @@ +# DEV TASK: orchestrator — фикс трекера (дубли сообщений + отставание) + +Репо: `slin@82.22.50.71:/home/slin/repos/orchestrator` (пароль `motoZ@yaz2010`). +Push в main запрещён (pre-receive hook) → **только PR в Gitea**. +Gitea токен: `docker exec orchestrator printenv ORCH_GITEA_TOKEN`. +Одна ветка `fix/tracker-edit-not-modified` от актуального main, **один PR**. +Baseline pytest на этом проде: **259 passed + 9 failed** (9 = off-limits HMAC/401, НЕ чинить). + +--- + +## КОНТЕКСТ / БАГ (подтверждён на живой задаче ET-013) +Живой Telegram-трекер (PR #21, `src/notifications.py`) на боевом прогоне ET-013 повёл себя так: +- За прогон: **21 editMessageText, но 7 sendMessage** — трекер 7 раз прислал НОВОЕ сообщение + вместо редактирования одного. Слава жалуется: «сообщение не одно, а при изменении приходит новое». +- Часть `editMessageText` падает с **HTTP 400 Bad Request**. Это НЕ сбой Telegram — это ответ + `"message is not modified"` (текст трекера не изменился между переходами, напр. повторный + цикл review→development→review рисует ту же строку). + +### Корень (точно) +`edit_telegram` (notifications.py ~76-97): +```python +data = resp.json() +return bool(data.get("ok")) # 400 "not modified" -> ok:false -> возвращает False +``` +Возвращает `False` на ЛЮБОМ 400. А `update_task_tracker` (~374-392) трактует `False` как +«edit не удался» → вызывает `send_telegram(...)` (новое сообщение) и перезаписывает +`tracker_message_id`. Итог: дубли + старый трекер «осиротевает» и больше не обновляется +(отстаёт от реальности — застрял на снимке Review, хотя был ещё цикл dev→review). + +--- + +## ЧТО СДЕЛАТЬ (3 правки) + +### Правка 1 — `edit_telegram`: "not modified" = успех, НЕ дубль +В `edit_telegram` разобрать тело ответа Telegram. Если `ok:false`, но +`description` содержит `"message is not modified"` (или `"exactly the same"`) — +**вернуть `True`** (редактировать нечего, всё в порядке, дубль НЕ нужен). +Логировать на DEBUG, не на WARNING. + +### Правка 2 — fallback на новое сообщение ТОЛЬКО при реально пропавшем сообщении +Сейчас `update_task_tracker`: `if edit_telegram(...) == False -> send_telegram (новое)`. +Изменить логику так, чтобы новое сообщение слалось **только** когда исходное реально +нельзя редактировать: +- `"message to edit not found"` / `"message can't be edited"` / `"MESSAGE_ID_INVALID"` → + сообщение пропало/удалено → fallback на новое (как сейчас), обновить tracker_message_id. +- Любой другой провал edit (сеть, таймаут, временный 5xx, неизвестный 400) → + **НЕ слать новое**, просто залогировать и выйти (трекер дорисуется на следующем переходе). + Плодить дубли на временных ошибках нельзя. + +Реализация: пусть `edit_telegram` возвращает не голый bool, а различимый результат — +например enum/строку (`"ok"` | `"not_modified"` | `"gone"` | `"failed"`), либо tuple +`(ok: bool, gone: bool)`. `update_task_tracker` шлёт новое сообщение только при `gone`. +`"ok"` и `"not_modified"` → ничего не делать. `"failed"` → лог + выход без нового сообщения. +Никогда не падать (текущий стиль fire-and-forget сохранить). + +### Правка 3 — повторные циклы стадии видимы (чтобы текст реально менялся) +Чтобы трекер не «застывал» на повторных проходах одной стадии (review↔development), +у АКТИВНОЙ стадии в `render_task_tracker` показывать признак повторного захода/попытки, +например: `🔄 Review · попытка 2 … идёт` (номер попытки = число записей agent_runs этого +агента по задаче, или attempts из jobs если доступно). Завершённые стадии (✅) НЕ трогать — +формат финальной строки прежний. Это и делает текст уникальным между циклами (меньше +not-modified), и честно показывает Славе, что идёт переработка. +- Если данных о попытке нет — рисовать как раньше (`🔄 Review … идёт`), без «попытка N». +- Считать попытку аккуратно: повторный запуск ТОЙ ЖЕ стадии (например 2-й review), а не + суммарно все агенты. Бери число прогонов агента этой стадии для текущей задачи. + +### НЕ ТРОГАТЬ +- Формат завершённых строк (`✅ Stage Nм · in↓/out↑ · $ · model`), блок Итого, «Ревью БРД», + short_model_name, миграции, usage_comment/Plane-комменты, PLANE_STATES, HMAC, queue, + launcher deployer-guard, отдельные алерты (approve/deploy-fail/agent-fail/error). +- disable_notification трекера (всегда silent). + +--- + +## ТЕСТЫ (обязательно, мокать httpx) +- `edit_telegram`: ответ 400 `"message is not modified"` → трактуется как успех (не gone, не дубль). +- `edit_telegram`: 400 `"message to edit not found"` → gone. +- `edit_telegram`: 200 ok → ok. +- `edit_telegram`: таймаут/5xx → failed (не gone). +- `update_task_tracker`: edit not_modified → НЕ зовёт send_telegram (нет дубля). +- `update_task_tracker`: edit gone → зовёт send_telegram + обновляет tracker_message_id. +- `update_task_tracker`: edit failed (временная) → НЕ зовёт send_telegram. +- `render_task_tracker`: повторный заход стадии (2 review-рана) → `попытка 2` в активной строке; + один заход → без «попытка N»; завершённые строки без изменений. +- Полный pytest зелёный кроме тех же 9 off-limits. + +## СДАЧА +- Ветка `fix/tracker-edit-not-modified`, один PR в Gitea. НЕ мержить сам — на ревью Стрим. +- Отчёт: `tasks/orchestrator/reports/dev-2026-06-04-tracker-edit-fix.md` — + commit-хеш, PR-номер, вывод pytest, что изменилось в каждой из 3 правок, + до/после по логике (когда теперь шлётся новое сообщение, а когда нет). +- Сообщить: PR-номер, результат pytest, краткое описание фикса.