# 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, краткое описание фикса.