[2026-09-29] eagle: personal/plans/mm-tool-progress-thread-anchor.md
This commit is contained in:
1 parent
b4f7ff4a30
commit
6011f9d69a
1 file changed
+120
@@ -0,0 +1,120 @@
|
||||
|
||||
# План: 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`) |
|
||||
Reference in new issue
Block a user