fix(slack): expose shared-thread author mention target
In shared Slack threads the model saw only [sender name] prefixes, with no verifiable current-author Slack user ID — so 'mention me again' requests could bind to a stale or unrelated <@U...> pulled from names, memory, or prior history (#17916). Two cooperating changes: - The shared-session sender prefix on Slack now carries the current author's ID from the event envelope: '[Alice | Slack user <@U123>] ...' — per-turn data, so it does not touch the cached system prompt. - The Slack platform notes gain a shared-thread instruction to use the current turn's sender prefix as the only verified mention target and never guess or reuse historical mentions. Fixes #17916. Salvaged from #18711 by @LeonSGP43 (asdigitos), rebased over the sender-name neutralization added on main (the ID is appended after neutralizing the display name; the ID itself comes from the Slack event, not user-editable text).
This commit is contained in:
@@ -11818,6 +11818,16 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
# (mirrors the same field's treatment in
|
||||
# build_session_context_prompt via _format_untrusted_prompt_value).
|
||||
_safe_user_name = neutralize_untrusted_inline_text(source.user_name)
|
||||
# On Slack, expose the current author's verifiable user ID next to
|
||||
# the display name (#17916): "mention me again" requests need a
|
||||
# trusted `<@U...>` target for the CURRENT speaker — display names
|
||||
# are ambiguous and historical mentions may point at someone else.
|
||||
# The user_id comes from the Slack event envelope (not
|
||||
# user-editable text), so it does not need neutralization.
|
||||
if source.platform == Platform.SLACK and source.user_id:
|
||||
_safe_user_name = (
|
||||
f"{_safe_user_name} | Slack user <@{source.user_id}>"
|
||||
)
|
||||
message_text = f"[{_safe_user_name}] {message_text}"
|
||||
|
||||
# Prepend channel context from history backfill (if any). This
|
||||
|
||||
@@ -526,6 +526,13 @@ def build_session_context_prompt(
|
||||
"current message's Slack block/attachment payload when available, but "
|
||||
"you still cannot call Slack APIs yourself."
|
||||
)
|
||||
if context.shared_multi_user_session:
|
||||
lines.append(
|
||||
"In shared Slack threads, use the current turn's sender prefix "
|
||||
"as the only verified current-author mention target. Do not "
|
||||
"guess or reuse `<@U...>` mentions from names, memory, or prior "
|
||||
"conversation history."
|
||||
)
|
||||
elif context.source.platform == Platform.DISCORD:
|
||||
# Inject the Discord IDs block only when the agent actually has
|
||||
# Discord tools loaded this session — i.e. the user opted into
|
||||
|
||||
@@ -299,6 +299,49 @@ class TestBuildSessionContextPrompt:
|
||||
assert "pin" in prompt.lower()
|
||||
assert "current message's slack block/attachment payload" in prompt.lower()
|
||||
|
||||
def test_shared_slack_prompt_warns_against_guessed_self_mentions(self):
|
||||
"""Shared Slack threads must instruct the agent to bind mention
|
||||
targets to the current turn's sender prefix (#17916)."""
|
||||
config = GatewayConfig(
|
||||
platforms={
|
||||
Platform.SLACK: PlatformConfig(enabled=True, token="fake"),
|
||||
},
|
||||
)
|
||||
source = SessionSource(
|
||||
platform=Platform.SLACK,
|
||||
chat_id="C123",
|
||||
chat_name="team-channel",
|
||||
chat_type="group",
|
||||
user_id="U123",
|
||||
user_name="Alice",
|
||||
thread_id="171.000",
|
||||
)
|
||||
ctx = build_session_context(source, config)
|
||||
prompt = build_session_context_prompt(ctx)
|
||||
|
||||
assert "current turn's sender prefix" in prompt
|
||||
assert "Do not guess or reuse `<@U...>` mentions" in prompt
|
||||
|
||||
def test_non_shared_slack_prompt_omits_self_mention_guidance(self):
|
||||
"""1:1 Slack DMs are single-user: the shared-thread mention guidance
|
||||
must not appear."""
|
||||
config = GatewayConfig(
|
||||
platforms={
|
||||
Platform.SLACK: PlatformConfig(enabled=True, token="fake"),
|
||||
},
|
||||
)
|
||||
source = SessionSource(
|
||||
platform=Platform.SLACK,
|
||||
chat_id="D123",
|
||||
chat_type="dm",
|
||||
user_id="U123",
|
||||
user_name="Alice",
|
||||
)
|
||||
ctx = build_session_context(source, config)
|
||||
prompt = build_session_context_prompt(ctx)
|
||||
|
||||
assert "current turn's sender prefix" not in prompt
|
||||
|
||||
def test_discord_prompt_with_channel_topic(self):
|
||||
"""Channel topic should appear in the session context prompt."""
|
||||
config = GatewayConfig(
|
||||
|
||||
@@ -68,3 +68,64 @@ async def test_preprocess_keeps_plain_text_for_default_group_sessions():
|
||||
)
|
||||
|
||||
assert result == "hello"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_preprocess_includes_slack_author_mention_for_shared_thread():
|
||||
"""Shared Slack threads expose the current author's verifiable user ID
|
||||
next to the display name so 'mention me again' requests can bind the
|
||||
mention to the CURRENT speaker (#17916)."""
|
||||
runner = _make_runner(
|
||||
GatewayConfig(
|
||||
platforms={
|
||||
Platform.SLACK: PlatformConfig(enabled=True, token="fake"),
|
||||
},
|
||||
)
|
||||
)
|
||||
source = SessionSource(
|
||||
platform=Platform.SLACK,
|
||||
chat_id="C123",
|
||||
chat_name="team-channel",
|
||||
chat_type="group",
|
||||
user_id="U123",
|
||||
user_name="Alice",
|
||||
thread_id="171.000",
|
||||
)
|
||||
event = MessageEvent(text="mention me again", source=source)
|
||||
|
||||
result = await runner._prepare_inbound_message_text(
|
||||
event=event,
|
||||
source=source,
|
||||
history=[],
|
||||
)
|
||||
|
||||
assert result == "[Alice | Slack user <@U123>] mention me again"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_preprocess_slack_shared_thread_without_user_id_keeps_name_only():
|
||||
"""No user_id on the source → fall back to the plain name prefix."""
|
||||
runner = _make_runner(
|
||||
GatewayConfig(
|
||||
platforms={
|
||||
Platform.SLACK: PlatformConfig(enabled=True, token="fake"),
|
||||
},
|
||||
)
|
||||
)
|
||||
source = SessionSource(
|
||||
platform=Platform.SLACK,
|
||||
chat_id="C123",
|
||||
chat_name="team-channel",
|
||||
chat_type="group",
|
||||
user_name="Alice",
|
||||
thread_id="171.000",
|
||||
)
|
||||
event = MessageEvent(text="hello", source=source)
|
||||
|
||||
result = await runner._prepare_inbound_message_text(
|
||||
event=event,
|
||||
source=source,
|
||||
history=[],
|
||||
)
|
||||
|
||||
assert result == "[Alice] hello"
|
||||
|
||||
Reference in New Issue
Block a user