Skip to content

refactor(gateway): migrate Slack adapter to bundled plugin - #41160

Closed
teknium1 wants to merge 2 commits into
mainfrom
hermes/hermes-87ac0a8e
Closed

refactor(gateway): migrate Slack adapter to bundled plugin#41160
teknium1 wants to merge 2 commits into
mainfrom
hermes/hermes-87ac0a8e

Conversation

@teknium1

@teknium1 teknium1 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Slack is now a bundled plugin (plugins/platforms/slack/) discovered via the platform registry instead of hardcoded Platform.SLACK touchpoints in core — the same shape as the Discord (#24356) and Home Assistant (#40709) migrations. Advances #41112 / #3823.

Changes

  • Adapter relocated gateway/platforms/slack.pyplugins/platforms/slack/adapter.py (git rename, history preserved) + __init__.py / plugin.yaml.
  • register(ctx) replaces the Platform.SLACK elif in gateway/run.py::_create_adapter().
  • _standalone_send() replaces the legacy _send_slack() in tools/send_message_tool.py; deliver=slack cron delivery now flows through the registry's standalone_sender_fn. mrkdwn formatting moved into the plugin.
  • _apply_yaml_config() owns the config.yaml slack:SLACK_* env bridge (require_mention, strict_mention, allow_bots, free_response_channels, reactions, allowed_channels), replacing the hardcoded block in gateway/config.py.
  • interactive_setup() replaces hermes_cli/setup.py::_setup_slack + the static _PLATFORMS["slack"] dict in hermes_cli/gateway.py; setup metadata is discovered dynamically.
  • is_connected() probes SLACK_BOT_TOKEN; max_message_length=39000 on the PlatformEntry (registry fallback covers it — dropped the _MAX_LENGTHS entry).
  • Env→PlatformConfig seeding and the _is_user_authorized allowlist maps stay in core (same as Discord/HA/Mattermost).

Bug fixed during migration

The registry-driven plugin-enable pass in _apply_env_overrides re-enabled any plugin platform whose is_connected() passed, ignoring an explicit enabled: false. Slack is the first plugin with an enabled-false-wins test, so it exposed this latent bug (Discord had no such test). Added an explicit-disable guard (_enabled_explicit + enabled=False → skip) and switched the slack env-block to read (not pop) the flag so the guard can see it; the flag is still cleared in the final per-platform cleanup.

Validation

Result
Targeted suites (slack / config / setup / registry / send / media) 442 passing, 0 failing
test_explicit_{top_level,platforms}_slack_enabled_false_wins restored (were broken by the migration, now green)
Live E2E (real plugin discovery → registry resolves SlackAdapter, env-only enable, standalone sender wired, YAML bridge, dynamic setup discovery) 18/18

Test imports rewritten across 11 files (gateway.platforms.slackplugins.platforms.slack.adapter). The _setup_slack home-channel tests moved to tests/gateway/test_slack_plugin_setup.py (exercising interactive_setup); the slack-formatting tests in test_send_message_tool now patch the registry standalone_sender_fn and assert the mrkdwn text reaches the wire.

Infographic

Slack to bundled-plugin migration

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-87ac0a8e vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10002 on HEAD, 10002 on base (➖ 0)

🆕 New issues (18):

Rule Count
unresolved-import 5
invalid-method-override 4
invalid-assignment 3
invalid-return-type 2
unresolved-attribute 2
call-non-callable 1
invalid-raise 1
First entries
tools/send_message_tool.py:830: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["warnings"]` and value of type `list[str | Unknown]` on object of type `dict[str, str]`
plugins/platforms/slack/adapter.py:3414: [invalid-return-type] invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `str`
plugins/platforms/slack/adapter.py:1709: [invalid-method-override] invalid-method-override: Invalid override of method `send_image_file`: Definition is incompatible with `BasePlatformAdapter.send_image_file`
plugins/platforms/slack/adapter.py:89: [unresolved-import] unresolved-import: Cannot resolve imported module `slack_bolt.async_app`
plugins/platforms/slack/adapter.py:832: [call-non-callable] call-non-callable: Object of type `<special-form 'typing.Any'>` is not callable
tests/tools/test_signal_media.py:186: [invalid-assignment] invalid-assignment: Object of type `((...) -> Awaitable[dict[Unknown, Unknown]]) | None` is not assignable to attribute `standalone_sender_fn` on type `PlatformEntry | None`
plugins/platforms/slack/adapter.py:3470: [invalid-return-type] invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `bytes`
plugins/platforms/slack/adapter.py:3472: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
plugins/platforms/slack/adapter.py:1891: [invalid-method-override] invalid-method-override: Invalid override of method `send_document`: Definition is incompatible with `BasePlatformAdapter.send_document`
tests/tools/test_signal_media.py:174: [invalid-assignment] invalid-assignment: Object of type `AsyncMock` is not assignable to attribute `standalone_sender_fn` on type `PlatformEntry | None`
plugins/platforms/slack/adapter.py:90: [unresolved-import] unresolved-import: Cannot resolve imported module `slack_bolt.adapter.socket_mode.async_handler`
tests/tools/test_signal_media.py:173: [unresolved-attribute] unresolved-attribute: Attribute `standalone_sender_fn` is not defined on `None` in union `PlatformEntry | None`
plugins/platforms/slack/adapter.py:1935: [invalid-raise] invalid-raise: Cannot raise object of type `None | Exception`: Not an instance or subclass of `BaseException`
plugins/platforms/slack/adapter.py:1833: [invalid-method-override] invalid-method-override: Invalid override of method `send_video`: Definition is incompatible with `BasePlatformAdapter.send_video`
plugins/platforms/slack/adapter.py:1135: [invalid-method-override] invalid-method-override: Invalid override of method `send_private_notice`: Definition is incompatible with `BasePlatformAdapter.send_private_notice`
plugins/platforms/slack/adapter.py:91: [unresolved-import] unresolved-import: Cannot resolve imported module `slack_sdk.web.async_client`
plugins/platforms/slack/adapter.py:3644: [unresolved-import] unresolved-import: Cannot resolve imported module `aiohttp`
plugins/platforms/slack/adapter.py:1055: [unresolved-attribute] unresolved-attribute: Attribute `client` is not defined on `None` in union `Any | None`

✅ Fixed issues (14):

Rule Count
unresolved-import 5
invalid-method-override 4
invalid-return-type 2
call-non-callable 1
invalid-raise 1
unresolved-attribute 1
First entries
gateway/platforms/slack.py:1709: [invalid-method-override] invalid-method-override: Invalid override of method `send_image_file`: Definition is incompatible with `BasePlatformAdapter.send_image_file`
gateway/platforms/slack.py:89: [unresolved-import] unresolved-import: Cannot resolve imported module `slack_bolt.async_app`
gateway/platforms/slack.py:1891: [invalid-method-override] invalid-method-override: Invalid override of method `send_document`: Definition is incompatible with `BasePlatformAdapter.send_document`
gateway/platforms/slack.py:92: [unresolved-import] unresolved-import: Cannot resolve imported module `aiohttp`
gateway/platforms/slack.py:90: [unresolved-import] unresolved-import: Cannot resolve imported module `slack_bolt.adapter.socket_mode.async_handler`
gateway/platforms/slack.py:3470: [invalid-return-type] invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `bytes`
gateway/platforms/slack.py:3414: [invalid-return-type] invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `str`
gateway/platforms/slack.py:832: [call-non-callable] call-non-callable: Object of type `<special-form 'typing.Any'>` is not callable
gateway/platforms/slack.py:1833: [invalid-method-override] invalid-method-override: Invalid override of method `send_video`: Definition is incompatible with `BasePlatformAdapter.send_video`
gateway/platforms/slack.py:1135: [invalid-method-override] invalid-method-override: Invalid override of method `send_private_notice`: Definition is incompatible with `BasePlatformAdapter.send_private_notice`
gateway/platforms/slack.py:91: [unresolved-import] unresolved-import: Cannot resolve imported module `slack_sdk.web.async_client`
gateway/platforms/slack.py:3472: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
gateway/platforms/slack.py:1935: [invalid-raise] invalid-raise: Cannot raise object of type `None | Exception`: Not an instance or subclass of `BaseException`
gateway/platforms/slack.py:1055: [unresolved-attribute] unresolved-attribute: Attribute `client` is not defined on `None` in union `Any | None`

Unchanged: 5176 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/refactor Code restructuring, no behavior change comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins platform/slack Slack app adapter P3 Low — cosmetic, nice to have labels Jun 7, 2026
Move gateway/platforms/slack.py into plugins/platforms/slack/ following the
Discord (#24356) and Home Assistant (#40709) migrations. Advances #41112 /
hardcoded Platform.SLACK touchpoints in core.

  - Adapter file renamed via git mv (history preserved).
  - register() exposes the platform via ctx.register_platform() instead of the
    Platform.SLACK elif in gateway/run.py::_create_adapter().
  - _standalone_send() replaces the legacy _send_slack() helper in
    tools/send_message_tool.py; out-of-process cron delivery (deliver=slack)
    now flows through the registry's standalone_sender_fn. mrkdwn formatting
    moved into the plugin (was applied in _send_to_platform before chunking).
  - _apply_yaml_config() owns the config.yaml slack: -> SLACK_* env bridge
    (require_mention, strict_mention, allow_bots, free_response_channels,
    reactions, allowed_channels), replacing the hardcoded block in
    gateway/config.py.
  - interactive_setup() replaces hermes_cli/setup.py::_setup_slack +
    _write_slack_manifest_and_instruct and the static _PLATFORMS["slack"] dict
    in hermes_cli/gateway.py; setup metadata is discovered dynamically.
  - is_connected() probes SLACK_BOT_TOKEN via hermes_cli.gateway.get_env_value.
  - max_message_length=39000 on the PlatformEntry; the registry fallback in
    send_message_tool covers it (dropped the _MAX_LENGTHS entry).

The SLACK_BOT_TOKEN/SLACK_HOME_CHANNEL env->PlatformConfig seeding and the
_is_user_authorized allowlist maps stay in core (same as Discord/HA/Mattermost).

Bug fixed during migration: the registry-driven plugin-enable pass in
_apply_env_overrides re-enabled any plugin platform whose is_connected()
passed, ignoring an explicit enabled: false. Slack is the first plugin with an
enabled-false-wins test, so it exposed this latent bug (Discord had no such
test). Added an explicit-disable guard (_enabled_explicit + enabled=False ->
skip) and changed the slack env-block to read the flag instead of popping it so
the guard can see it; the flag is still cleared in the final per-platform
cleanup. Restores test_explicit_{top_level,platforms}_slack_enabled_false_wins.

Test imports rewritten across 11 files (gateway.platforms.slack ->
plugins.platforms.slack.adapter). The _setup_slack home-channel tests moved to
tests/gateway/test_slack_plugin_setup.py exercising interactive_setup. The
test_send_message_tool slack-formatting tests now patch the registry
standalone_sender_fn (via _patch_slack_standalone_sender) and assert the
mrkdwn-formatted text reaches the wire.

Validation: 706 targeted tests pass (slack/config/setup/registry/send/media
suites); 18/18 live E2E checks pass (real plugin discovery + registry resolves
SlackAdapter, env-only enable, standalone sender wired, YAML bridge, dynamic
setup discovery).
@teknium1
teknium1 force-pushed the hermes/hermes-87ac0a8e branch from afe5b88 to c2eae47 Compare June 7, 2026 14:05
…ge (#f6f363662)

Pre-existing main breakage inherited via rebase, not slack-migration fallout:
f6f3636 made discord component-button auth fail closed when no allowlist is
set but only updated test_discord_component_auth.py. test_discord_model_picker
and two test_discord_clarify_buttons tests drove the authorized-proceed path
with an empty allowlist, which now correctly rejects. Give the interacting
user an allowlist entry so the proceed path runs (dedicated *_unauthorized_*
tests still cover rejection). Same fix as #41284; both PRs un-break main's
currently-red CI.
@teknium1

teknium1 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

Folded into #41284 — the slack migration commit was cherry-picked onto the combined all-platforms branch (authorship preserved in git log), so the entire gateway-channel → plugin transition lands as one PR per Teknium's request. Closing this in favor of #41284

@teknium1 teknium1 closed this Jun 7, 2026
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/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants