mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-27 11:22:03 +00:00
feat(slack): add --no-assistant flag to manifest generation
By default `hermes slack manifest` opts the app into Slack's AI Assistant container (assistant_view feature + assistant:write scope + assistant_thread_* events). Slack then renders DMs as the right-hand Assistant split-pane, where every exchange is a thread and bare slash commands (/help, /new, ...) are not delivered as normal command events — they only work when the bot is @mentioned. There was no way to opt out short of hand-editing the generated JSON. Add --no-assistant to emit a flat-DM manifest that omits those three pieces, so DMs render as a normal chat and slash commands dispatch inline. The regular messaging surface (Messages tab, slash commands, Socket Mode, channel + DM scopes/events) is preserved in both modes. Default behaviour is unchanged (assistant mode still on). Tests: cover both manifest modes and the argparse wiring.
This commit is contained in:
parent
70d28b62fb
commit
da80ac0042
3 changed files with 137 additions and 41 deletions
|
|
@ -23,7 +23,11 @@ import sys
|
|||
from pathlib import Path
|
||||
|
||||
|
||||
def _build_full_manifest(bot_name: str, bot_description: str) -> dict:
|
||||
def _build_full_manifest(
|
||||
bot_name: str,
|
||||
bot_description: str,
|
||||
include_assistant: bool = True,
|
||||
) -> dict:
|
||||
"""Build a full Slack manifest merging display info + our slash list.
|
||||
|
||||
The slash-command list is always generated from ``COMMAND_REGISTRY`` so
|
||||
|
|
@ -31,12 +35,71 @@ def _build_full_manifest(bot_name: str, bot_description: str) -> dict:
|
|||
(display info, OAuth scopes, socket mode) are set to sensible defaults
|
||||
for a Hermes deployment — users can tweak them in the Slack UI after
|
||||
pasting.
|
||||
|
||||
When ``include_assistant`` is True (default) the manifest opts the app
|
||||
into Slack's AI Assistant container: the ``assistant_view`` feature, the
|
||||
``assistant:write`` scope, and the ``assistant_thread_*`` events. Slack
|
||||
then renders DMs as the right-hand Assistant split-pane, where every
|
||||
exchange is a thread and bare slash commands are not delivered as normal
|
||||
``command`` events. Pass ``include_assistant=False`` (``--no-assistant``)
|
||||
to omit those three pieces and get a flat DM surface where ``/help``,
|
||||
``/new``, etc. work inline.
|
||||
"""
|
||||
from hermes_cli.commands import slack_app_manifest
|
||||
|
||||
partial = slack_app_manifest()
|
||||
slashes = partial["features"]["slash_commands"]
|
||||
|
||||
features = {
|
||||
"app_home": {
|
||||
"home_tab_enabled": False,
|
||||
"messages_tab_enabled": True,
|
||||
"messages_tab_read_only_enabled": False,
|
||||
},
|
||||
"bot_user": {
|
||||
"display_name": bot_name[:80],
|
||||
"always_online": True,
|
||||
},
|
||||
"slash_commands": slashes,
|
||||
}
|
||||
|
||||
bot_scopes = [
|
||||
"app_mentions:read",
|
||||
"channels:history",
|
||||
"channels:read",
|
||||
"chat:write",
|
||||
"commands",
|
||||
"files:read",
|
||||
"files:write",
|
||||
"groups:history",
|
||||
"groups:read",
|
||||
"im:history",
|
||||
"im:read",
|
||||
"im:write",
|
||||
"users:read",
|
||||
]
|
||||
|
||||
bot_events = [
|
||||
"app_mention",
|
||||
"message.channels",
|
||||
"message.groups",
|
||||
"message.im",
|
||||
]
|
||||
|
||||
if include_assistant:
|
||||
features["assistant_view"] = {
|
||||
"assistant_description": "Chat with Hermes in threads and DMs.",
|
||||
}
|
||||
bot_scopes.append("assistant:write")
|
||||
bot_events.extend(
|
||||
[
|
||||
"assistant_thread_context_changed",
|
||||
"assistant_thread_started",
|
||||
]
|
||||
)
|
||||
bot_scopes.sort()
|
||||
bot_events.sort()
|
||||
|
||||
return {
|
||||
"_metadata": {
|
||||
"major_version": 1,
|
||||
|
|
@ -47,51 +110,15 @@ def _build_full_manifest(bot_name: str, bot_description: str) -> dict:
|
|||
"description": (bot_description or "Your Hermes agent on Slack")[:140],
|
||||
"background_color": "#1a1a2e",
|
||||
},
|
||||
"features": {
|
||||
"app_home": {
|
||||
"home_tab_enabled": False,
|
||||
"messages_tab_enabled": True,
|
||||
"messages_tab_read_only_enabled": False,
|
||||
},
|
||||
"bot_user": {
|
||||
"display_name": bot_name[:80],
|
||||
"always_online": True,
|
||||
},
|
||||
"slash_commands": slashes,
|
||||
"assistant_view": {
|
||||
"assistant_description": "Chat with Hermes in threads and DMs.",
|
||||
},
|
||||
},
|
||||
"features": features,
|
||||
"oauth_config": {
|
||||
"scopes": {
|
||||
"bot": [
|
||||
"app_mentions:read",
|
||||
"assistant:write",
|
||||
"channels:history",
|
||||
"channels:read",
|
||||
"chat:write",
|
||||
"commands",
|
||||
"files:read",
|
||||
"files:write",
|
||||
"groups:history",
|
||||
"groups:read",
|
||||
"im:history",
|
||||
"im:read",
|
||||
"im:write",
|
||||
"users:read",
|
||||
],
|
||||
"bot": bot_scopes,
|
||||
},
|
||||
},
|
||||
"settings": {
|
||||
"event_subscriptions": {
|
||||
"bot_events": [
|
||||
"app_mention",
|
||||
"assistant_thread_context_changed",
|
||||
"assistant_thread_started",
|
||||
"message.channels",
|
||||
"message.groups",
|
||||
"message.im",
|
||||
],
|
||||
"bot_events": bot_events,
|
||||
},
|
||||
"interactivity": {
|
||||
"is_enabled": True,
|
||||
|
|
@ -113,16 +140,21 @@ def slack_manifest_command(args) -> int:
|
|||
--description DESC Override the bot description
|
||||
--slashes-only Emit only the ``features.slash_commands`` array (for
|
||||
merging into an existing manifest manually)
|
||||
--no-assistant Omit Slack AI Assistant mode (assistant_view feature,
|
||||
assistant:write scope, assistant_thread_* events) so
|
||||
DMs render as a flat chat where bare slash commands
|
||||
work inline instead of the Assistant thread pane.
|
||||
"""
|
||||
name = getattr(args, "name", None) or "Hermes"
|
||||
description = getattr(args, "description", None) or "Your Hermes agent on Slack"
|
||||
include_assistant = not getattr(args, "no_assistant", False)
|
||||
|
||||
if getattr(args, "slashes_only", False):
|
||||
from hermes_cli.commands import slack_app_manifest
|
||||
|
||||
manifest = slack_app_manifest()["features"]["slash_commands"]
|
||||
else:
|
||||
manifest = _build_full_manifest(name, description)
|
||||
manifest = _build_full_manifest(name, description, include_assistant=include_assistant)
|
||||
|
||||
payload = json.dumps(manifest, indent=2, ensure_ascii=False) + "\n"
|
||||
|
||||
|
|
|
|||
|
|
@ -57,4 +57,12 @@ def build_slack_parser(subparsers, *, cmd_slack: Callable) -> None:
|
|||
help="Emit only the features.slash_commands array (for merging "
|
||||
"into an existing manifest manually).",
|
||||
)
|
||||
slack_manifest.add_argument(
|
||||
"--no-assistant",
|
||||
action="store_true",
|
||||
help="Omit Slack AI Assistant mode (assistant_view, assistant:write "
|
||||
"scope, assistant_thread_* events). DMs then render as a flat chat "
|
||||
"where bare slash commands (/help, /new) work inline instead of "
|
||||
"Slack's Assistant thread pane.",
|
||||
)
|
||||
slack_parser.set_defaults(func=cmd_slack)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,30 @@
|
|||
"""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:
|
||||
|
|
@ -28,3 +52,35 @@ class TestSlackFullManifest:
|
|||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue