fix(slack): per-thread sessions for DMs by default

Each top-level Slack DM now gets its own Hermes session, matching the
per-thread behavior channels already have. Previously all top-level DM
messages shared one continuous session because thread_ts was None,
causing context to accumulate across unrelated conversations.

The behavior is controlled by platforms.slack.extra.dm_top_level_threads_as_sessions
in config.yaml (default: true). Set to false to restore legacy behavior.

Based on PR #10789 by helix4u. Changes from original:
- Default flipped to true (was opt-in, now opt-out)
- Removed env var fallback (config.yaml only per project policy)
- Tests updated to cover both default and opt-out paths
This commit is contained in:
helix4u
2026-04-16 04:22:07 -07:00
committed by Teknium
parent daef0519e9
commit bd7e272c1f
2 changed files with 54 additions and 8 deletions

View File

@@ -1678,11 +1678,11 @@ class TestProgressMessageThread:
msg_event = captured_events[0]
source = msg_event.source
# For a top-level DM: source.thread_id should remain None
# (session keying must not be affected)
assert source.thread_id is None, (
"source.thread_id must stay None for top-level DMs "
"so they share one continuous session"
# With default dm_top_level_threads_as_sessions=True, source.thread_id
# should equal the message ts so each DM thread gets its own session.
assert source.thread_id == "1234567890.000001", (
"source.thread_id must equal the message ts for top-level DMs "
"so each reply thread gets its own session"
)
# The message_id should be the event's ts — this is what the gateway
@@ -1707,6 +1707,34 @@ class TestProgressMessageThread:
"ensuring progress messages land in the thread"
)
@pytest.mark.asyncio
async def test_dm_toplevel_shares_session_when_disabled(self, adapter):
"""Opting out restores legacy single-session-per-DM-channel behavior."""
adapter.config.extra["dm_top_level_threads_as_sessions"] = False
event = {
"channel": "D_DM",
"channel_type": "im",
"user": "U_USER",
"text": "Hello bot",
"ts": "1234567890.000001",
}
captured_events = []
adapter.handle_message = AsyncMock(side_effect=lambda e: captured_events.append(e))
with patch.object(adapter, "_resolve_user_name", new=AsyncMock(return_value="testuser")):
await adapter._handle_slack_message(event)
assert len(captured_events) == 1
msg_event = captured_events[0]
source = msg_event.source
assert source.thread_id is None, (
"source.thread_id must stay None when "
"dm_top_level_threads_as_sessions is disabled"
)
@pytest.mark.asyncio
async def test_channel_mention_progress_uses_thread_ts(self, adapter):
"""Progress messages for a channel @mention should go into the reply thread."""