fix(Slack): resolve Slack channels by raw ID and enumerate joined channels

send_message(target='slack:<channel_id>') failed with "Could not
resolve" because _parse_target_ref had no Slack branch — Slack's
uppercase alphanumeric IDs fell through to channel-name resolution,
which only matched by name. As a fallback, the agent would retry with
bare target='slack' and post to the home channel instead.

Three fixes:

- _parse_target_ref recognizes Slack IDs (C/G/D/U/W prefix) as
  explicit targets so the name-resolver is bypassed entirely.
- resolve_channel_name tries a case-sensitive raw-ID match before
  the existing name match, so any platform's IDs resolve cleanly.
- _build_slack now actually calls users.conversations against each
  workspace's AsyncWebClient (paginated), instead of only returning
  session-history entries. This populates the directory with public
  and private channels the bot has joined, so action='list' shows
  them and they can also be addressed by name. Errors from one
  workspace don't block others.

build_channel_directory becomes async (Slack web calls require it).
The two async-context callers in gateway/run.py are awaited; the
cron ticker thread call bridges via asyncio.run_coroutine_threadsafe.

Slack bot needs channels:read and groups:read scopes for full
enumeration; missing scopes degrade gracefully per-workspace.

addressing #15927
This commit is contained in:
hhuang91 2026-04-26 03:51:20 -04:00 committed by Teknium
parent 541cd732e8
commit 802c7acb81
5 changed files with 272 additions and 19 deletions

View file

@ -810,6 +810,40 @@ class TestParseTargetRefE164:
assert _parse_target_ref("matrix", "+15551234567")[2] is False
class TestParseTargetRefSlack:
"""_parse_target_ref recognizes Slack channel/user IDs as explicit."""
def test_public_channel_id_is_explicit(self):
chat_id, thread_id, is_explicit = _parse_target_ref("slack", "C0B0QV5434G")
assert chat_id == "C0B0QV5434G"
assert thread_id is None
assert is_explicit is True
def test_private_channel_id_is_explicit(self):
assert _parse_target_ref("slack", "G123ABCDEF")[2] is True
def test_dm_id_is_explicit(self):
assert _parse_target_ref("slack", "D123ABCDEF")[2] is True
def test_user_id_is_explicit(self):
assert _parse_target_ref("slack", "U123ABCDEF")[2] is True
assert _parse_target_ref("slack", "W123ABCDEF")[2] is True
def test_whitespace_is_stripped(self):
chat_id, _, is_explicit = _parse_target_ref("slack", " C0B0QV5434G ")
assert chat_id == "C0B0QV5434G"
assert is_explicit is True
def test_lowercase_or_short_id_is_not_explicit(self):
assert _parse_target_ref("slack", "c0b0qv5434g")[2] is False
assert _parse_target_ref("slack", "C123")[2] is False
assert _parse_target_ref("slack", "X0B0QV5434G")[2] is False
def test_slack_id_not_explicit_for_other_platforms(self):
assert _parse_target_ref("discord", "C0B0QV5434G")[2] is False
assert _parse_target_ref("telegram", "C0B0QV5434G")[2] is False
class TestSendDiscordThreadId:
"""_send_discord uses thread_id when provided."""