fix(slack): preserve typed command integrity across enrichment paths

Commands typed in Slack could be mangled by every enrichment layer the
adapter applies to normal messages:

- Block Kit / unfurl / attachment-notice / text-file injection could
  prepend or append content around a command, moving the command token
  away from character zero or polluting its arguments. Commands are now
  restored from canonical authored input after all enrichment
  (final is_command_text guard before MessageEvent construction).
- @bot /cmd (typed slash behind a mention) was never classified as a
  command; the mention-strip branch now re-probes for both slash and
  bang forms.
- The Slack Agent-view context label ([Slack app context: ...]) was
  prepended to command events too; now command-exempt.
- Native slash payload arguments were strip()ed, destroying meaningful
  spacing inside/after arguments; only the command delimiter is
  nonsemantic now.
- Slash payload thread identity (thread_ts/message_ts, top-level or
  nested in message/container) is preserved onto SessionSource so
  session-scoped commands hit the same thread session.
- /queue and /steer queued fallbacks now propagate channel_context so a
  command that triggered first-entry thread backfill doesn't lose the
  history when re-queued.

Adapted from PR #66310 to the post-#69320 channel_context design (thread
history already rides MessageEvent.channel_context, never text).
This commit is contained in:
Pavel Tajduš 2026-07-22 08:29:43 -07:00 committed by Teknium
parent e7ef57f8c3
commit c2d306c4c2
No known key found for this signature in database
5 changed files with 279 additions and 16 deletions

View file

@ -175,6 +175,25 @@ async def test_queue_preserves_reply_context():
assert queued.reply_to_author_name == "alice"
@pytest.mark.asyncio
async def test_queue_preserves_channel_context_backfill():
"""A queued Slack thread command must retain first-entry history."""
runner, adapter = _make_runner(_session_entry())
sk = _running(runner)
context = "[Thread context]\nAlice: earlier request"
event = MessageEvent(
text="/queue follow up",
source=_make_source(),
message_id="q-context",
channel_context=context,
)
result = await runner._handle_message(event)
assert result is not None and "queued" in result.lower()
assert adapter._pending_messages[sk].channel_context == context
@pytest.mark.asyncio
async def test_queue_no_text_no_media_returns_usage():
runner, adapter = _make_runner(_session_entry())