fix(slack): honor reply_in_thread=false for top-level channel messages

Top-level channel messages arrive at _resolve_thread_ts with
metadata.thread_id set to the message's own ts, because the inbound
handler in _handle_message_event uses 'event.ts' as a session-keying
fallback when event.thread_ts is absent. That made metadata alone
insufficient to distinguish a real thread reply from a top-level
message, so reply_in_thread=false only took effect in DMs.

Use reply_to (== incoming message_id == ts for top-level messages) as
the tiebreaker: when metadata.thread_id == reply_to the 'thread' is the
synthetic session-keying fallback, not a real parent, so we reply
directly in the channel. Real thread replies (reply_to != thread_id)
still resolve to the parent thread and preserve conversation context.

Closes #9268.
This commit is contained in:
Teknium
2026-04-26 12:01:47 -07:00
committed by Teknium
parent b1be86ef96
commit 4b5a88d714
2 changed files with 23 additions and 1 deletions

View File

@@ -334,7 +334,19 @@ def test_config_bridges_slack_reply_in_thread(monkeypatch, tmp_path):
adapter = SlackAdapter(slack_config)
assert adapter._resolve_thread_ts(reply_to="171.000", metadata={}) is None
# Top-level channel messages arrive with metadata.thread_id == reply_to
# because the inbound handler uses event.ts as a session-keying fallback.
# Those must be treated as non-threaded so reply_in_thread=false takes
# effect in channels, not just DMs.
assert adapter._resolve_thread_ts(
reply_to="171.000",
metadata={"thread_id": "171.000"},
) is None
# Real thread replies (reply_to differs from thread parent) must still
# resolve to the parent thread so conversation context is preserved.
assert adapter._resolve_thread_ts(
reply_to="171.500",
metadata={"thread_id": "171.000"},
) == "171.000"