feat(plugins): expose register_slack_action_handler API
Plugins that post Block Kit messages with interactive elements (buttons, overflow menus, datepickers, etc.) had no documented way to receive the resulting click events. The plugin API exposed register_tool, register_hook, register_command, register_platform, and register_context_engine, but nothing for slack_bolt action handlers. The only workaround was to monkey-patch SlackAdapter.connect from inside register(), which is fragile and breaks on every Hermes update. This change adds: * PluginContext.register_slack_action_handler(action_id, callback) — validates inputs and queues the handler on the PluginManager. action_id accepts whatever slack_bolt.App.action() accepts (literal string, compiled re.Pattern, or constraint dict). * PluginManager.get_slack_action_handlers() — accessor used by the Slack adapter at connect time. * SlackAdapter.connect — after wiring its built-in approval and slash-confirm buttons, iterates the plugin-registered handlers and registers each via self._app.action(matcher)(callback). Each callback is wrapped defensively so a misbehaving plugin cannot crash slack_bolt's dispatch loop, with a best-effort ack on exception so Slack stops retrying the click. * Defensive fallback when the plugin layer is unhealthy: a RuntimeError from get_plugin_manager() is logged and swallowed rather than blocking the gateway from starting. * Test coverage in tests/gateway/test_slack_plugin_action_handlers.py for input validation, multi-plugin registration, the connect-time wiring, defensive exception handling, and the plugin-loader- failure fallback path. * Documentation in website/docs/guides/build-a-hermes-plugin.md describing the new API alongside the existing register_command / dispatch_tool documentation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
kshitijk4poor
co-authored by
Claude Opus 4.7
parent
24f74eb888
commit
62e937bf2b
@@ -827,6 +827,39 @@ def register(ctx):
|
||||
|
||||
This is the public, stable interface for tool dispatch from plugin commands. Plugins should not reach into `ctx._cli_ref.agent` or similar private state.
|
||||
|
||||
### Handle Slack Block Kit button clicks
|
||||
|
||||
Plugins that post Block Kit messages with interactive elements (buttons, overflow menus, datepickers, etc.) can register the click handlers directly with the Slack adapter — no monkey-patching of `slack_bolt.AsyncApp` required.
|
||||
|
||||
```python
|
||||
def register(ctx):
|
||||
async def _on_approve(ack, body, action):
|
||||
# ack within 3 seconds — slack_bolt requirement.
|
||||
await ack()
|
||||
# body["channel"]["id"], body["user"]["id"], body["message"]["ts"]
|
||||
# action["action_id"], action["value"]
|
||||
sweep_id = (action.get("value") or "").split("|", 1)[-1]
|
||||
# ...do the deterministic work, then post a follow-up.
|
||||
|
||||
ctx.register_slack_action_handler("inbox_sweep_approve", _on_approve)
|
||||
```
|
||||
|
||||
**Signature:** `ctx.register_slack_action_handler(action_id, callback) -> None`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `action_id` | `str \| re.Pattern \| dict` | Whatever `slack_bolt.App.action()` accepts: a literal `action_id`, a compiled regex matching multiple ids, or a constraint dict like `{"action_id": "...", "block_id": "..."}` |
|
||||
| `callback` | async callable | Receives `(ack, body, action)` per the slack_bolt convention |
|
||||
|
||||
**Runtime behavior:**
|
||||
|
||||
- The handler is queued at plugin-load time and wired into the adapter's `slack_bolt.AsyncApp` when the Slack platform connects.
|
||||
- Each callback is wrapped defensively: if your handler raises, the gateway logs the error and best-effort-acks the click so Slack stops retrying.
|
||||
- Standard slack_bolt rules apply — `await ack()` within 3 seconds, then do longer work.
|
||||
- For multi-workspace deployments the handler fires for clicks from any connected workspace; use `body["team"]["id"]` if you need to scope behaviour.
|
||||
|
||||
This is the public way for plugins to participate in Slack interactivity. Older plugins may patch `SlackAdapter.connect`; prefer this API instead.
|
||||
|
||||
:::tip
|
||||
This guide covers **general plugins** (tools, hooks, slash commands, CLI commands). The sections below sketch the authoring pattern for each specialized plugin type; each links to its full guide for field reference and examples.
|
||||
:::
|
||||
|
||||
Reference in New Issue
Block a user