Skip to content

Add Slack extension hooks for plugins - #20936

Closed
dandacompany wants to merge 2 commits into
NousResearch:mainfrom
dandacompany:slack-extension-hooks
Closed

Add Slack extension hooks for plugins#20936
dandacompany wants to merge 2 commits into
NousResearch:mainfrom
dandacompany:slack-extension-hooks

Conversation

@dandacompany

@dandacompany dandacompany commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a small Slack Block Kit extension surface for Hermes plugins. Plugins can now register:

  • native Slack slash commands that ACK immediately and are handled outside the generic text-command path;
  • Slack Block Kit action handlers for buttons, selects, and other interactive elements;
  • Slack modal view submission handlers.

The goal is to make Slack-native interactive plugin UIs possible without requiring each plugin to patch gateway/platforms/slack.py.

Why

PluginContext.register_command() is the right API for cross-platform text commands, but it cannot cover Slack Block Kit workflows. Interactive Slack UIs need adapter-level Bolt registrations: immediate slash command ACKs, app.action(...), app.view(...), and controlled access to the Slack adapter/client context.

Examples of plugin workflows that need this surface:

  • an approval console with Approve / Request changes buttons;
  • a task or incident dashboard with filters and detail modals;
  • a triage flow that opens Slack modals and updates the original message;
  • any plugin that needs Slack buttons/selects rather than plain text replies.

Today those workflows must be hardcoded in the Slack adapter. This PR gives plugin authors a supported extension point while keeping built-in Slack handlers authoritative.

What changed

  • Adds SlackSlashCommand, SlackActionHandler, and SlackViewHandler data classes.
  • Adds PluginContext.register_slack_extension(...).
  • Stores plugin-provided Slack commands/actions/views in PluginManager.
  • Includes Slack extension slash commands in slack_native_slashes() so hermes slack manifest --write can emit them.
  • Wires extension commands/actions/views into SlackAdapter.connect().
  • Keeps extension slash commands out of the generic slash-command regex so they are handled only by their native handler.
  • Adds tests for registration, collision behavior, manifest inclusion, and handler invocation.
  • Documents when to use register_slack_extension() versus register_command().

Notes

  • Built-in command names still win. A plugin cannot register /model, /help, etc.
  • Duplicate Slack extension command/action/view registrations are skipped with warnings.
  • Built-in Slack action IDs remain reserved.
  • Text-only plugin commands should continue to use ctx.register_command() so they work across CLI and every gateway.
  • This PR intentionally does not add a new product feature. It only adds the extension hook needed for Slack Block Kit plugin UIs.

Tests

python3 -m py_compile hermes_cli/plugins.py hermes_cli/commands.py gateway/platforms/slack.py tests/hermes_cli/test_slack_extension_hooks.py
uv run --with pytest --with pytest-asyncio --with pytest-xdist pytest tests/hermes_cli/test_slack_extension_hooks.py tests/hermes_cli/test_commands.py::TestSlackNativeSlashes tests/hermes_cli/test_commands.py::TestSlackAppManifest

Result: 18 passed.

uv emits an existing settings warning while parsing exclude-newer = "7 days" in pyproject.toml; tests still run and pass.

@alt-glitch alt-glitch added type/feature New feature or request comp/plugins Plugin system and bundled plugins comp/gateway Gateway runner, session dispatch, delivery platform/slack Slack app adapter P3 Low — cosmetic, nice to have labels May 6, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Supersedes #20589 (which only exposed register_slack_action_handler). This PR provides the full extension surface: slash commands, action handlers, and view handlers.

@dandacompany
dandacompany marked this pull request as ready for review May 9, 2026 00:04
@dandacompany

Copy link
Copy Markdown
Contributor Author

Hi @teknium1 — thanks again for cherry-picking #20503 yesterday, really appreciated the careful authorship preservation.

This PR (#20936) just moved out of draft. It adds a small Slack Block Kit extension surface for plugins (register_slack_extension(...) for native slash commands, action handlers, and view submissions) so plugin authors can build interactive Slack UIs without patching gateway/platforms/slack.py directly.

  • 540 / -4 across 6 files
  • 18 tests pass (tests/hermes_cli/test_slack_extension_hooks.py plus the existing slash/manifest suites)
  • Built-in command names and Slack action IDs remain reserved; duplicates are skipped with warnings
  • No new product surface — purely an extension hook

Whenever you have a moment, would you mind taking a look or pointing it to the right reviewer? No rush. Happy to rebase / split if anything looks off.

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Thanks for this @dandacompany — the broader surface here (native slash commands + Block Kit actions + modal view_submission handlers) is genuinely the direction we want, and the manifest integration is thoughtful.

We're going with #20589 (@bcsmith528) as the baseline for the plugin Slack action API — salvaged onto current main in #44664 — for one concrete reason: the wiring in connect() here doesn't survive slack_bolt's dispatch. The three loops capture the loop variable as a default argument:

async def handle_slack_extension_command(ack, command, _extension_command=_extension_command):
    await self._invoke_slack_extension_handler(_extension_command.handler, ...)

slack_bolt resolves listener args by name, not by Python defaults — for any param name it doesn't recognize it logs "_extension_command is not a valid argument" and injects None. Verified against the real slack_bolt.kwargs_injection.utils.build_required_kwargs with a live BoltRequest:

required arg names: ['ack', 'command', '_extension_command']
_extension_command resolved to: None
(stderr) "_extension_command is not a valid argument"

So at the first real command / click / modal submit, _extension_command (and _extension_action / _extension_view) is None, and None.handler raises AttributeError. All three loops hit this. The 18 tests pass because the only adapter-level test calls _invoke_slack_extension_handler directly and never drives the connect() loops through Bolt's dispatcher. #20589 avoids this with a closure factory (its second commit is literally fix(gateway): keep plugin action wrapper signature to (ack, body, action)).

The slash-command + view/modal surface is worth landing, though. Would you be up for re-submitting it as a focused follow-up on top of #44664 — loops switched to a closure factory, plus a test that drives connect() through slack_bolt's real dispatcher? Happy to review that.

Closing as superseded for now. Thanks again for the work here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have platform/slack Slack app adapter type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants