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

121 lines
6.1 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План: 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-бабблов:
```python
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) передаёт оба:
```python
result = await adapter.send(
chat_id=source.chat_id,
content=text,
reply_to=_progress_reply_to, # ← здесь None
metadata=_progress_metadata,
)
```
MM-адаптер в `send()` (строка 290) уважает анкор только так:
```python
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`.
```python
_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`) |