Skip to content

refactor(gateway): migrate Mattermost adapter to bundled plugin (salvage of #30916)#31748

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-c04b7eb9
May 25, 2026
Merged

refactor(gateway): migrate Mattermost adapter to bundled plugin (salvage of #30916)#31748
teknium1 merged 1 commit into
mainfrom
hermes/hermes-c04b7eb9

Conversation

@teknium1

@teknium1 teknium1 commented May 25, 2026

Copy link
Copy Markdown
Contributor

Salvage of #30916 — re-applied onto current main so the supply-chain audit (fixed in #31744) runs cleanly. Original commit + authorship preserved (@kshitijk4poor).

Migrates the Mattermost adapter from gateway/platforms/mattermost.py (873-LOC built-in) to plugins/platforms/mattermost/{__init__.py, adapter.py, plugin.yaml} — second migration of an existing built-in into the bundled-plugin shape after Discord (#30591). Advances the umbrella refactor in #3823.

Changes

  • gateway/platforms/mattermost.py (873 LOC) → plugins/platforms/mattermost/adapter.py (git rename, R071 — appended ~320-LOC register() block + hook helpers + _standalone_send with media/thread support)
  • New plugins/platforms/mattermost/{__init__.py, plugin.yaml} — plugin shell, requires_env (URL + TOKEN) + optional_env declarations
  • Append register(ctx) block + hook implementations to adapter: check_fn, is_connected, setup_fn, standalone_sender_fn, apply_yaml_config_fn (second consumer of refactor(plugins): add apply_yaml_config_fn registry hook for YAML→env config bridges #25443 after Discord), plus registry fields
  • gateway/config.py: delete 17-LOC mattermost_cfg YAML→env bridge (moved into plugin's _apply_yaml_config)
  • gateway/run.py::_create_adapter: delete Platform.MATTERMOST elif (7 LOC) — replaced by the existing generic plugin-registry-first dispatch
  • tools/send_message_tool.py: delete _send_mattermost (22 LOC) + Platform.MATTERMOST elif in _send_to_platform (2 LOC); the else branch already routes plugin platforms through _send_via_adapter → registry's standalone_sender_fn
  • hermes_cli/setup.py: delete _setup_mattermost (44 LOC) — replaced by the plugin's interactive_setup
  • hermes_cli/gateway.py: delete _PLATFORMS["mattermost"] dict entry (3 LOC)
  • 5 test files repointed from gateway.platforms.mattermostplugins.platforms.mattermost.adapter
  • tests/tools/test_send_message_missing_platforms.py: thin compat shim around the plugin's _standalone_send

14 files, +402 / −105.

What this fixes

_send_mattermost previously lived in tools/send_message_tool.py as a one-shot REST call with no thread support, no media upload, and no proxy awareness. The new plugin _standalone_send:

  • supports thread replies via the root_id field on POST /posts
  • uploads media_files via POST /files (multipart/form-data) and attaches the returned file_id values to the post
  • honors MATTERMOST_PROXY env via the shared resolve_proxy_url + proxy_kwargs_for_aiohttp helpers (parity with Discord)

Same capability bump as Discord's migration.

Validation

Result
Plugin discovery mattermost registers alongside discord / teams / irc / line / google_chat / simplex; all 6 hooks present
Mattermost-touching tests 212/212 pass
Broader sweep (tests/gateway/ + send_message_*) 5856/5856 pass, 28.6s
Supply-chain audit (after #31744) clean (no false positive on hermes_cli/setup.py)

Salvage notes

~29 open community PRs touch gateway/platforms/mattermost.py and will need to rebase onto the new path. Same cost as Discord migration (#30591 left 80 PRs to rebase) — established as acceptable.

Closes part of #3823.

Closes #30916.

Infographic

mattermost-plugin-migration

Second migration of an existing built-in platform adapter after Discord
(PR #30591) — follows the same shape established by IRC / Teams / LINE /
Google Chat / SimpleX and the playbook in
`references/platform-plugin-migration.md`. Advances the umbrella refactor
in #3823.

Matches Discord's parity bar — adapter under `plugins/platforms/mattermost/`
with the standard `__init__.py` / `adapter.py` / `plugin.yaml` shell,
`register(ctx)` entry point, **no back-compat shim** at the old import
path, and full parity for all five hooks Discord uses plus the
`apply_yaml_config_fn` hook (mattermost is the second consumer of #25443
after Discord):

* `standalone_sender_fn` — out-of-process cron delivery via Mattermost
  REST API. Picks up the thread_id + media_files capabilities the
  legacy `_send_mattermost` lacked (parity with Discord's `_standalone_send`).
* `setup_fn` — interactive `hermes setup gateway` wizard.
* `apply_yaml_config_fn` — translates `config.yaml` `mattermost:` keys
  (`require_mention`, `free_response_channels`, `allowed_channels`) into
  `MATTERMOST_*` env vars (replaces the hardcoded block in
  `gateway/config.py`).
* `is_connected` — declares connection state from `MATTERMOST_TOKEN` +
  `MATTERMOST_URL`.
* `check_fn` — verifies aiohttp is installed and both required env vars
  are set.
* plus `allowed_users_env`, `allow_all_env`, `cron_deliver_env_var`,
  `max_message_length` (4000 — Mattermost practical limit), `emoji`,
  `required_env`, `install_hint`.

Files
-----
* `gateway/platforms/mattermost.py` (873 LOC) →
  `plugins/platforms/mattermost/adapter.py` (git rename, R071) +
  appended `register()` block, hook helpers, and `_standalone_send`
  with media upload + thread_id support.
* New `plugins/platforms/mattermost/{__init__.py, plugin.yaml}` with
  `requires_env` / `optional_env` declarations covering MATTERMOST_URL,
  MATTERMOST_TOKEN, MATTERMOST_ALLOWED_USERS, MATTERMOST_ALLOW_ALL_USERS,
  MATTERMOST_HOME_CHANNEL, MATTERMOST_REPLY_MODE,
  MATTERMOST_REQUIRE_MENTION, MATTERMOST_FREE_RESPONSE_CHANNELS,
  MATTERMOST_ALLOWED_CHANNELS.
* `gateway/config.py`: delete 17-LOC `mattermost_cfg` YAML→env bridge
  (moved into plugin's `_apply_yaml_config`).
* `gateway/run.py::_create_adapter`: delete `Platform.MATTERMOST elif` —
  replaced by the existing generic plugin-registry-first dispatch.
* `tools/send_message_tool.py`: delete `_send_mattermost` (22 LOC) +
  `Platform.MATTERMOST elif` in `_send_to_platform` — the `else` branch
  already routes plugin platforms through `_send_via_adapter`, which
  hits the registry's `standalone_sender_fn`.
* `hermes_cli/setup.py`: delete `_setup_mattermost` (44 LOC) — replaced
  by the plugin's `interactive_setup`.
* `hermes_cli/gateway.py`: delete `_PLATFORMS["mattermost"]` dict entry
  (3 LOC) — plugin's `setup_fn` is dispatched via the plugin path in
  `_configure_platform`.
* Consumer rewrite: 5 test files (test_mattermost.py,
  test_media_download_retry.py, test_send_multiple_images.py,
  test_stream_consumer.py, test_ws_auth_retry.py) get
  `gateway.platforms.mattermost` → `plugins.platforms.mattermost.adapter`
  with the bulk-rewrite recipe from the platform-plugin-migration playbook.
  Single `mock.patch` string in test_stream_consumer.py also repointed.
* `tests/tools/test_send_message_missing_platforms.py`: thin
  `(token, extra, chat_id, message)` compat shim around the plugin's
  `_standalone_send(pconfig, …)` so existing test bodies continue to
  work without rewriting every signature.

Validation
----------
* Plugin discovery: mattermost registers from `plugins/platforms/mattermost/`
  alongside discord / teams / irc / line / google_chat / simplex.
  All 9 hooks present (setup_fn, standalone_sender_fn,
  apply_yaml_config_fn, is_connected, check_fn, allowed_users_env,
  allow_all_env, cron_deliver_env_var, max_message_length=4000).
* Mattermost-touching tests: 62/62 pass
  (`test_mattermost.py` + `test_send_message_missing_platforms.py`).
* Targeted selectors (mattermost or platform_registry or stream_consumer
  or ws_auth_retry or media_download_retry or send_multiple_images or
  send_message_tool or platform_connected): 433/433 pass.
* Full sweep (`scripts/run_tests.sh tests/gateway/ tests/cron/
  tests/tools/test_send_message_tool.py tests/tools/test_send_message_missing_platforms.py
  tests/integration/`): **6220/6220 pass in 47.8s, 0 failures**.
* Lint: ruff clean on all touched files.
* Git identity verified: kshitijk4poor.
* Rename detection: R071 (similarity dropped from a hypothetical R09x
  by the ~320-line appended register block — ~36% growth over the
  873-LoC base, vs Discord's 5101 LoC base which kept R091).

Closes part of #3823.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-c04b7eb9 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: 9104 on HEAD, 9104 on base (➖ 0)

🆕 New issues (6):

Rule Count
invalid-method-override 4
unresolved-import 1
invalid-argument-type 1
First entries
plugins/platforms/mattermost/adapter.py:910: [unresolved-import] unresolved-import: Cannot resolve imported module `aiohttp`
plugins/platforms/mattermost/adapter.py:392: [invalid-method-override] invalid-method-override: Invalid override of method `send_video`: Definition is incompatible with `BasePlatformAdapter.send_video`
plugins/platforms/mattermost/adapter.py:867: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `list[str]`, found `(list[str] & ~AlwaysFalsy) | None`
plugins/platforms/mattermost/adapter.py:352: [invalid-method-override] invalid-method-override: Invalid override of method `send_image_file`: Definition is incompatible with `BasePlatformAdapter.send_image_file`
plugins/platforms/mattermost/adapter.py:379: [invalid-method-override] invalid-method-override: Invalid override of method `send_voice`: Definition is incompatible with `BasePlatformAdapter.send_voice`
plugins/platforms/mattermost/adapter.py:365: [invalid-method-override] invalid-method-override: Invalid override of method `send_document`: Definition is incompatible with `BasePlatformAdapter.send_document`

✅ Fixed issues (6):

Rule Count
invalid-method-override 4
unresolved-import 1
invalid-argument-type 1
First entries
gateway/platforms/mattermost.py:379: [invalid-method-override] invalid-method-override: Invalid override of method `send_voice`: Definition is incompatible with `BasePlatformAdapter.send_voice`
gateway/platforms/mattermost.py:365: [invalid-method-override] invalid-method-override: Invalid override of method `send_document`: Definition is incompatible with `BasePlatformAdapter.send_document`
gateway/platforms/mattermost.py:809: [unresolved-import] unresolved-import: Cannot resolve imported module `aiohttp`
gateway/platforms/mattermost.py:392: [invalid-method-override] invalid-method-override: Invalid override of method `send_video`: Definition is incompatible with `BasePlatformAdapter.send_video`
gateway/platforms/mattermost.py:867: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `list[str]`, found `(list[str] & ~AlwaysFalsy) | None`
gateway/platforms/mattermost.py:352: [invalid-method-override] invalid-method-override: Invalid override of method `send_image_file`: Definition is incompatible with `BasePlatformAdapter.send_image_file`

Unchanged: 4845 pre-existing issues carried over.

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

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 type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants