-
-
Notifications
You must be signed in to change notification settings - Fork 69.2k
feat(mattermost): carry thread context (replyToId) to non-inbound-triggered deliveries #39759
Description
Problem
When a Mattermost session is started by a channel message with replyToMode: "all", the agent correctly replies in-thread for direct responses to that message. However, two delivery paths fail to carry the thread context:
Case 1 — TUI/WebUI messages to an active Mattermost session:
If the user sends a follow-up message via TUI or WebUI (not Mattermost), the agent's response is correctly routed back to Mattermost, but it arrives in the channel root instead of the session's thread.
Case 2 — Agent-initiated messages:
When the agent sends proactively (e.g. tool call callbacks, subagent responses, message tool calls within a session), the same problem occurs — the message arrives in the channel instead of the thread.
Root Cause
effectiveReplyToId is computed as a closure variable when processing an inbound Mattermost message and correctly used in the deliver callback for direct replies. However, when responses are triggered from other surfaces (TUI, WebUI) or agent-initiated, they go through the channel plugin's sendText/sendMedia path in channel.ts. This path receives replyToId from payload.replyToId, which is not populated for non-inbound-Mattermost-triggered deliveries.
Inbound Mattermost message:
effectiveReplyToId = threadRootId ?? post.id ← computed here
ctxPayload.ReplyToId = effectiveReplyToId ← stored in session ctx
deliver() callback → uses effectiveReplyToId ← WORKS ✅
TUI/WebUI or agent-initiated:
channel.ts sendText({ replyToId }) ← replyToId = undefined ❌
→ message goes to channel root
Proposed Solution
Maintain a Map<sessionKey, string> (thread context cache) in the monitor:
- Write: when processing an inbound Mattermost message, store
threadContextMap.set(sessionKey, effectiveReplyToId) - Read: in the outbound delivery path (
sendText/sendMediainchannel.ts, or via a hook inmonitor.ts), look up the cachedreplyToIdas fallback whenpayload.replyToIdis not set - Cleanup: entries can be evicted after a configurable TTL or LRU limit
Alternatively: if the core already forwards ctxPayload.ReplyToId as payload.replyToId for all session deliveries (including TUI-triggered ones), it may be sufficient to ensure the Mattermost channel plugin uses payload.replyToId as its primary thread reference.
How to Reproduce
- Configure
replyToMode: "all"on a Mattermost account - Send a message to the bot in a channel — it correctly creates a thread and replies in it
- Send a follow-up message to the same session via TUI (
openclaw tui) or WebUI - Observe: agent reply arrives in channel root, not in the thread
- For Case 2: trigger a subagent from within the session and have it report back — response arrives in channel root
Notes
- This is a companion issue to feat(mattermost): add replyToMode support (off | first | all) #29587 (replyToMode) and feat(mattermost): block streaming edit-in-place (rebased) #33506 (block streaming)
- Cases 1 and 2 share the same root cause and can be fixed in a single PR
- Related: in feat(mattermost): add replyToMode support (off | first | all) #29587 the
delivercallback correctly useseffectiveReplyToId; the gap is specifically the non-inbound-triggered delivery paths