mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
Group DMs (multi-person DMs, channel_type=mpim) were never delivered to
the Slack bot. The adapter already classifies mpim as a DM and replies
ambiently (adapter.py:2526, is_dm = channel_type in {im, mpim}), but the
generated app manifest only subscribed to message.im / im:history — the
1:1 DM pair. Without the message.mpim event subscription Slack drops
group-DM messages before the adapter ever sees them, so 1:1 DMs worked
while group-DM ambient mode was dead.
Add message.mpim to bot_events and mpim:history (the scope that event
requires per Slack docs) + mpim:read (mirrors im:read for the
conversations.info classification call) to bot_scopes. Update the
SLACK_BOT_TOKEN / SLACK_APP_TOKEN setup-help strings and the Slack docs
(EN + zh-Hans: scope table, event table, troubleshooting) so existing
installs are told to add the new scopes and reinstall.
Reported by an enterprise customer. Note: this is a manifest/scope
change, so it only takes effect after the app is reinstalled and the
new scopes are accepted.
Tests: assert message.mpim + mpim:history + mpim:read are in the
manifest (with and without assistant mode); both fail on current main
and pass with this change.
114 lines
5.1 KiB
Python
114 lines
5.1 KiB
Python
"""Tests for Slack CLI helpers."""
|
|
|
|
import argparse
|
|
|
|
from hermes_cli.slack_cli import _build_full_manifest
|
|
from hermes_cli.subcommands.slack import build_slack_parser
|
|
|
|
|
|
def _parse_slack_args(argv):
|
|
"""Build the real `hermes slack` parser and parse argv against it."""
|
|
parser = argparse.ArgumentParser()
|
|
subparsers = parser.add_subparsers(dest="command")
|
|
build_slack_parser(subparsers, cmd_slack=lambda _args: 0)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
class TestSlackManifestArgparse:
|
|
"""The `--no-assistant` flag wires through argparse to `no_assistant`."""
|
|
|
|
def test_no_assistant_flag_defaults_false(self):
|
|
args = _parse_slack_args(["slack", "manifest"])
|
|
assert getattr(args, "no_assistant", False) is False
|
|
|
|
def test_no_assistant_flag_sets_true(self):
|
|
args = _parse_slack_args(["slack", "manifest", "--no-assistant"])
|
|
assert args.no_assistant is True
|
|
|
|
|
|
|
|
class TestSlackFullManifest:
|
|
"""Generated full Slack app manifest used by `hermes slack manifest`."""
|
|
|
|
def test_app_home_messages_are_writable(self):
|
|
manifest = _build_full_manifest("Hermes", "Your Hermes agent on Slack")
|
|
|
|
assert manifest["features"]["app_home"] == {
|
|
"home_tab_enabled": False,
|
|
"messages_tab_enabled": True,
|
|
"messages_tab_read_only_enabled": False,
|
|
}
|
|
|
|
def test_private_channel_directory_scope_is_included(self):
|
|
manifest = _build_full_manifest("Hermes", "Your Hermes agent on Slack")
|
|
|
|
bot_scopes = manifest["oauth_config"]["scopes"]["bot"]
|
|
assert "groups:read" in bot_scopes
|
|
|
|
def test_group_dm_scopes_and_event_are_included(self):
|
|
"""Group DMs (mpim) need message.mpim + mpim:history or Slack never
|
|
delivers them — the adapter classifies mpim as a DM and replies
|
|
ambiently, but only if the event reaches the bot at all."""
|
|
manifest = _build_full_manifest("Hermes", "Your Hermes agent on Slack")
|
|
|
|
bot_scopes = manifest["oauth_config"]["scopes"]["bot"]
|
|
bot_events = manifest["settings"]["event_subscriptions"]["bot_events"]
|
|
|
|
# The event is the load-bearing piece: without message.mpim Slack
|
|
# drops group-DM messages before the adapter sees them.
|
|
assert "message.mpim" in bot_events
|
|
# mpim:history is the scope message.mpim requires (per Slack docs);
|
|
# mpim:read mirrors im:read for conversations.info classification.
|
|
assert "mpim:history" in bot_scopes
|
|
assert "mpim:read" in bot_scopes
|
|
|
|
def test_group_dm_surface_present_without_assistant_mode(self):
|
|
"""Dropping assistant mode must not strip the group-DM surface."""
|
|
manifest = _build_full_manifest(
|
|
"Hermes", "Your Hermes agent on Slack", include_assistant=False
|
|
)
|
|
|
|
bot_scopes = manifest["oauth_config"]["scopes"]["bot"]
|
|
bot_events = manifest["settings"]["event_subscriptions"]["bot_events"]
|
|
assert "message.mpim" in bot_events
|
|
assert "mpim:history" in bot_scopes
|
|
|
|
def test_assistant_features_remain_enabled(self):
|
|
manifest = _build_full_manifest("Hermes", "Your Hermes agent on Slack")
|
|
|
|
assert "assistant_view" in manifest["features"]
|
|
assert "assistant:write" in manifest["oauth_config"]["scopes"]["bot"]
|
|
bot_events = manifest["settings"]["event_subscriptions"]["bot_events"]
|
|
assert "assistant_thread_started" in bot_events
|
|
|
|
def test_no_assistant_omits_assistant_pieces(self):
|
|
manifest = _build_full_manifest(
|
|
"Hermes", "Your Hermes agent on Slack", include_assistant=False
|
|
)
|
|
|
|
# assistant_view feature is gone -> Slack renders a flat DM, not the
|
|
# Assistant thread pane (where bare slash commands don't dispatch).
|
|
assert "assistant_view" not in manifest["features"]
|
|
assert "assistant:write" not in manifest["oauth_config"]["scopes"]["bot"]
|
|
bot_events = manifest["settings"]["event_subscriptions"]["bot_events"]
|
|
assert "assistant_thread_started" not in bot_events
|
|
assert "assistant_thread_context_changed" not in bot_events
|
|
|
|
def test_no_assistant_preserves_core_surface(self):
|
|
"""Dropping assistant mode must NOT strip the regular messaging surface."""
|
|
manifest = _build_full_manifest(
|
|
"Hermes", "Your Hermes agent on Slack", include_assistant=False
|
|
)
|
|
|
|
# Flat DM still needs the Messages tab writable.
|
|
assert manifest["features"]["app_home"]["messages_tab_enabled"] is True
|
|
# Slash commands and Socket Mode are independent of assistant mode.
|
|
assert manifest["features"]["slash_commands"]
|
|
assert manifest["settings"]["socket_mode_enabled"] is True
|
|
# Channel + DM scopes/events survive so the bot still works everywhere.
|
|
bot_scopes = manifest["oauth_config"]["scopes"]["bot"]
|
|
for scope in ("commands", "channels:history", "groups:read", "im:history"):
|
|
assert scope in bot_scopes
|
|
bot_events = manifest["settings"]["event_subscriptions"]["bot_events"]
|
|
for event in ("message.im", "message.channels", "message.groups", "app_mention"):
|
|
assert event in bot_events
|