121 lines
6.1 KiB
Markdown
Executable File
121 lines
6.1 KiB
Markdown
Executable File
|
||
# План: 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`) |
|