Files
wiki/tasks/orchestrator/reports/dev-2026-06-04-tracker-edit-fix.md
2026-06-04 13:30:01 +03:00

7.0 KiB
Raw Blame History

Dev Report: orchestrator — фикс трекера (дубли сообщений + отставание)

Дата: 2026-06-04 Статус: DONE (PR на ревью, НЕ смержен)

Задача

Живой Telegram-трекер на ET-013 слал НОВОЕ сообщение вместо редактирования одного (21 editMessageText / 7 sendMessage). Дубли + старый трекер осиротевал и отставал. Корень: edit_telegram возвращал False на ЛЮБОМ 400 (включая Telegram-ответ "message is not modified"), а update_task_tracker трактовал False как «edit упал» → слал новое сообщение + перезаписывал tracker_message_id.

Сделано

  • Правка 1 — edit_telegram: различимый результат + not_modified=успех
  • Правка 2 — update_task_tracker: новое сообщение только при gone
  • Правка 3 — render_task_tracker: «попытка N» у активной перезапущенной стадии
  • +13 тестов (мокают httpx)
  • Baseline подтверждён, полный pytest зелёный кроме 9 off-limits
  • Ветка fix/tracker-edit-not-modified, commit ec9aa74, PR #22 (НЕ смержен)

Сдача

  • PR #22admin/orchestrator#22
  • Ветка: fix/tracker-edit-not-modified (от актуального main 3e5c74c)
  • Commit: ec9aa74
  • pytest: 272 passed, 9 failed (те же off-limits HMAC/401 webhook-тесты; baseline был 259 passed + 9 failed → +13 новых тестов, 0 регрессий)

Изменённые файлы

  • src/notifications.py:
    • edit_telegram(message_id, text) -> str (было -> bool). Возвращает EDIT_OK | EDIT_NOT_MODIFIED | EDIT_GONE | EDIT_FAILED. Разбор description из тела ответа: маркеры not-modified ("message is not modified", "exactly the same") → not_modified (лог DEBUG); маркеры gone ("message to edit not found", "message can't be edited", "message_id_invalid") → gone; прочий 400 / 5xx / исключение → failed.
    • update_task_tracker: ok/not_modified → выход без send; failed → лог + выход без send; gone → send нового + set_tracker_message_id.
    • render_task_tracker: активная стадия = текущая стадия задачи И есть in-flight (незавершённый) ран её агента (либо нет завершённого вообще). Если прогонов агента ≥2 → 🔄 Review · попытка N … идёт, иначе старая строка 🔄 Review … идёт. Завершённые строки не тронуты.
  • tests/test_telegram_tracker.py: обновлены 2 существующих теста под новый контракт edit_telegram (EDIT_OK/EDIT_GONE вместо True/False) + 13 новых.

Каждая из 3 правок

Правка 1 — edit_telegram: not_modified = успех, НЕ дубль

Раньше: return bool(data.get("ok")) → любой 400 = False. Теперь: при ok:false парсим description и классифицируем: not_modified (успех) / gone / failed. not_modified логируется на DEBUG.

Правка 2 — fallback на новое сообщение ТОЛЬКО при gone

Раньше: if edit_telegram(...) == False -> send_telegram (новое) + перезапись id. Теперь:

  • ok / not_modifiedreturn (ничего не делаем, дубля нет).
  • failed (сеть/таймаут/5xx/неизвестный 400) → лог DEBUG + return (НЕ дубль; трекер дорисуется на следующем переходе).
  • gonesend_telegram(...) + set_tracker_message_id. Стиль fire-and-forget сохранён, функция никогда не падает.

Правка 3 — повторные циклы стадии видимы

У активной перезапущенной стадии показывается 🔄 <Stage> · попытка N … идёт, где N = число agent_runs этого агента по задаче. Это делает текст уникальным между циклами review↔development (меньше not-modified) и честно показывает переработку. Активная стадия определяется как «текущая стадия задачи + есть in-flight ран её агента» — поэтому just-finished снимок (агент завершил, задача ещё не сдвинулась) по-прежнему показывает , а не 🔄. Завершённые строки не тронуты.

До/после по логике (когда шлётся новое сообщение)

  • До: новое сообщение слалось при ЛЮБОМ не-True результате edit, включая not-modified и временные сбои → дубли + осиротевший отстающий трекер.
  • После: новое сообщение слётся ТОЛЬКО при gone (сообщение реально удалено/слишком старое/невалидный id). not_modified и временные ошибки оставляют существующий трекер на месте.

Тесты (добавлено 13)

  • edit_telegram: ok / not_modified / "exactly the same" / not_found→gone / can't-be-edited→gone / unknown400→failed / timeout→failed / 5xx→failed.
  • update_task_tracker: not_modified → НЕ зовёт send (id не меняется); gone → зовёт send + обновляет id; failed → НЕ зовёт send (id не меняется).
  • render: «попытка 2» при 2 review-ранах; без «попытка N» при 1 ране; завершённые строки без «попытка N».
  • Обновлены под новый контракт: test_second_call_edits_existing_message (EDIT_OK), test_fallback_to_new_message_when_edit_gone (EDIT_GONE).

Проблемы и решения

  • На хосте нет scp/sshpass scp → файлы переносил через base64 | ssh 'base64 -d'.
  • Первая версия Правки 3 ломала test_render_omits_model_when_unknown: активная стадия перекрывала для just-finished рана. Решение: активной считается только стадия с in-flight раном (или вообще без завершённого) — just-finished показывает .

Следующий шаг

PR #22 на ревью у Стрима. Сам НЕ мержу.