Files
obsidian-vault/personal/plans/mm-tool-progress-thread-anchor.md
T

6.1 KiB
Executable File
Raw Blame History

План: tool-progress бабблы в тред (Mattermost)

Задача Alex 2026-09-29: «надо починить MM интеграцию: tool calls сыпятся в основной канал а не в тред». Статус: ПЛАН, ждёт подтверждения.

Симптом

Alex пишет @whale в треде → финальный ответ приходит в тред правильно, а бабблы tool-progress (🔍/⚙️/emoji + имя тула) падают в корень канала.

Доказательство из ~/.hermes/hermes-whale/logs/gateway.log (2026-09-29):

13:34:11 inbound message: platform=mattermost user=admin chat=a6nyh77eyjbn5c1owh1i8eebee msg='Почему'
13:35:09 response ready: platform=mattermost chat=a6nyh77eyjbn5c1owh1i8eebee ... response=805 chars

Session key: agent:main:mattermost:channel:a6nyh77eyjbn5c1owh1i8eebee:pcoiddzrqty3ipqtozgcs9ttte → тред-часть в session_key есть (pcoiddzrqty3ipqtozgcs9ttte), значит финал адресуется верно. Проблема только в progress-канале.

Причина (найдена в коде)

gateway/run.py, строки 17153-17170 — расчёт анкора для progress-бабблов:

if source.platform in (Platform.SLACK, Platform.MATTERMOST):
    _progress_thread_id = source.thread_id or event_message_id
else:
    _progress_thread_id = source.thread_id
_progress_metadata = (...) if _progress_thread_id else None
_progress_reply_to = (
    event_message_id
    if source.platform in (Platform.FEISHU, Platform.MATTERMOST)
       and source.thread_id and event_message_id
    else None
)

🔴 Ключевая строка — _progress_reply_to: требуется source.thread_id. Но source.thread_id заполняется только когда триггер-сообщение само внутри треда. Когда @whale написан в корне канала (root_id пустой) — source.thread_id = None → _progress_reply_to = None → adapter.send(..., reply_to=None) → пост уходит новым корневым постом канала.

При этом _progress_metadata считается по другой ветке (thread_id or event_message_id) и тоже может получать якорь — то есть два поля рассинхронизированы: metadata иногда есть, reply_to нет.

_send_progress_text() (строка 17273) передаёт оба:

result = await adapter.send(
    chat_id=source.chat_id,
    content=text,
    reply_to=_progress_reply_to,      # ← здесь None
    metadata=_progress_metadata,
)

MM-адаптер в send() (строка 290) уважает анкор только так:

if reply_to and self._reply_mode == "thread":
    resolved_root = await self._resolve_root_id(reply_to)
    payload["root_id"] = resolved_root

→ при reply_to=None ветка не срабатывает вообще. MATTERMOST_REPLY_MODE=thread уже стоит (.env), т.е. адаптер готов — до него не доезжает анкор.

Сравнение с остальными платформами

Платформа Правило анкора progress Ок?
Slack thread_id or event_message_id для metadata так же как MM
Feishu reply_to только при thread_id тот же дефект
Mattermost metadata ← thread_id or event_message_id, reply_to ← только thread_id 🔴 рассинхрон

Предлагаемый фикс (1 строка + симметрия)

Выровнять _progress_reply_to под ту же логику, что metadata: опираться на уже вычисленный _progress_thread_id, а не на сырой source.thread_id.

_progress_reply_to = (
    event_message_id
    if source.platform in (Platform.FEISHU, Platform.MATTERMOST)
       and _progress_thread_id and event_message_id
    else None
)

Что меняется: при посте в корне канала _progress_thread_id = event_message_id → _progress_reply_to = event_message_id → адаптер резолвит root и кладёт progress-баббл в тред, созданный от этого поста. При посте внутри треда поведение не меняется (source.thread_id уже непустой).

⚠️ Не трогаю: reply_mode, REQUIRE_MENTION, логику меншн-гейта (патч 9b46ba791a), edit_message-путь (там metadata передаётся отдельно, строка 17243).

Шаги

  1. Бэкап gateway/run.py → backups/<ts>-mm-thread-progress/run.py (+ md5).
  2. Правка строки 17166-17170 в run.py (скриптом в файле, не sed/inline).
  3. pytest tests/gateway/ -k mattermost + tests/gateway/test_stream_events.py если есть.
  4. Прогон шире: pytest tests/gateway/ -q -o 'addopts='.
  5. Коммит fix(mattermost): anchor tool-progress bubbles to thread.
  6. Alex рестартует gateway (gateway run --replace из моей сессии убьёт её же).

Приёмка (Alex)

Пишу @whale в корне канала hermes-maintenance → и финальный ответ, и бабблы tool-progress видны в треде под моим постом, в корне — ничего лишнего.

Риски

Риск Оценка
Прогресс уедет в тред там, где Alex ждал в корне низкий — при reply_mode=thread это и есть желаемое
Поломка Slack/Feishu низкий — правка в общей ветке, для них _progress_thread_id считается иначе; проверить тестами
Регресс в approval-промптах нет — у них свой путь (send_exec_approval)