fix(slack): preserve thread context for commands via channel_context

Prepending cold-start thread backfill directly onto the message text
moved a recognized command (e.g. a bang-normalized "!queue ...") away
from character zero, so downstream command routing misclassified it as
conversational text and the command silently didn't run.

Route the backfill through MessageEvent.channel_context instead —
gateway.run already prepends channel_context after command dispatch
("[New message]" framing), so commands keep their COMMAND type while
the recovered history stays available to the agent.

Supersedes #68020, which dropped the fetched context entirely for
commands instead of preserving it out-of-band.

Salvaged from #66069 by @LevSky22.
This commit is contained in:
LevSky22 2026-07-22 04:18:38 -07:00 committed by Teknium
parent c8089dabcd
commit fd433e046a
2 changed files with 57 additions and 1 deletions

View file

@ -3825,6 +3825,12 @@ class SlackAdapter(BasePlatformAdapter):
# When entering a thread for the first time (no existing session),
# fetch thread context so the agent understands the conversation.
#
# Keep recovered history separate from ``text``. Prepending it here
# moves a recognized command away from character zero, so downstream
# command routing can misclassify it as conversational text.
# ``channel_context`` is prepended only after command dispatch.
channel_context = None
if is_thread_reply and not self._has_active_session_for_thread(
channel_id=channel_id,
thread_ts=event_thread_ts,
@ -3838,7 +3844,7 @@ class SlackAdapter(BasePlatformAdapter):
team_id=team_id,
)
if thread_context:
text = thread_context + text
channel_context = thread_context
# Determine message type
msg_type = MessageType.TEXT
@ -4176,6 +4182,7 @@ class SlackAdapter(BasePlatformAdapter):
media_types=media_types,
reply_to_message_id=thread_ts if thread_ts != ts else None,
channel_prompt=_channel_prompt,
channel_context=channel_context,
reply_to_text=reply_to_text,
auto_skill=_auto_skill,
metadata={

View file

@ -1483,6 +1483,55 @@ class TestBangPrefixCommands:
# same thread.
assert msg_event.source.thread_id == "1111111111.000001"
@pytest.mark.asyncio
async def test_bang_queue_survives_first_thread_context_backfill(self, adapter):
"""Backfill stays out of command text while remaining available."""
adapter._has_active_session_for_thread = MagicMock(return_value=False)
adapter._fetch_thread_context = AsyncMock(
return_value=(
"[Slack thread context — earlier messages]\n"
"Alice: prior request\n"
"[End of thread context]\n\n"
)
)
adapter._fetch_thread_parent_text = AsyncMock(return_value="prior request")
evt = self._make_event(
"!queue follow up after the current task",
thread_ts="1111111111.000001",
)
await adapter._handle_slack_message(evt)
msg_event = adapter.handle_message.call_args[0][0]
assert msg_event.text == "/queue follow up after the current task"
assert msg_event.message_type == MessageType.COMMAND
assert msg_event.get_command() == "queue"
assert msg_event.get_command_args() == "follow up after the current task"
assert msg_event.channel_context.startswith("[Slack thread context")
assert "prior request" in msg_event.channel_context
@pytest.mark.asyncio
async def test_non_command_thread_backfill_uses_channel_context(self, adapter):
"""Normal thread text remains separate without losing its backfill."""
adapter._has_active_session_for_thread = MagicMock(return_value=False)
adapter._fetch_thread_context = AsyncMock(
return_value="[Slack thread context]\nAlice: earlier note\n"
)
adapter._fetch_thread_parent_text = AsyncMock(return_value="earlier note")
evt = self._make_event(
"follow up",
thread_ts="1111111111.000001",
)
await adapter._handle_slack_message(evt)
msg_event = adapter.handle_message.call_args[0][0]
assert msg_event.text == "follow up"
assert msg_event.message_type == MessageType.TEXT
assert msg_event.channel_context == (
"[Slack thread context]\nAlice: earlier note\n"
)
@pytest.mark.asyncio
async def test_bang_unknown_token_passes_through_unchanged(self, adapter):
"""``!nice work`` is just a casual message — must NOT be rewritten."""