Skip to content

feat(gateway): add SimpleX Chat platform plugin (salvages #2558)#26232

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-79e33daf
May 15, 2026
Merged

feat(gateway): add SimpleX Chat platform plugin (salvages #2558)#26232
teknium1 merged 2 commits into
mainfrom
hermes/hermes-79e33daf

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Salvages #2558 (@Mibayy) onto current main, reshaped from a 12-file core integration into a self-contained platform plugin.

What this PR does

Adds SimpleX Chat — a private, decentralised messenger with no persistent user IDs — as a Hermes gateway platform under plugins/platforms/simplex/. The adapter connects to a local simplex-chat daemon via WebSocket and relays messages between SimpleX contacts/groups and the agent.

Plugin shape (vs. the original PR)

The original PR (#2558) touched 12 files including 11 core files (gateway enum, run.py adapter factory, prompt builder, cron scheduler, send_message_tool, channel_directory, status, gateway CLI, toolsets). This salvage delivers the same functionality with zero core edits — the platform is discovered by the existing plugin filesystem scan in gateway/config.py and wired through ctx.register_platform(), the same contract used by irc, line, google_chat, and teams plugins.

  • plugins/platforms/simplex/plugin.yaml — declares required/optional env vars; hermes config UI picks them up via the platform-plugin env-var injector.
  • plugins/platforms/simplex/adapter.pySimplexAdapter + plugin hooks (check_requirements, validate_config, is_connected, _env_enablement, _standalone_send, interactive_setup, register).
  • plugins/platforms/simplex/__init__.py — exposes register for the plugin loader.

Lazy dependency install

The websockets Python package is the only external dep. It is imported lazily inside the functions that need itSimplexAdapter.connect(), _ws_listener(), _send_ws(), and _standalone_send(). The module top imports only stdlib and Hermes' own gateway.config / gateway.platforms.base. This means:

  • The plugin loads and registers even when websockets is not installed.
  • check_requirements() returns False until both SIMPLEX_WS_URL is set AND websockets imports successfully, so the gateway never attempts to construct the adapter without its dependency.
  • install_hint="pip install websockets" is surfaced via hermes setup gateway and hermes gateway status.
  • No new pyproject extras group introduced.

Environment variables

Variable Required Description
SIMPLEX_WS_URL yes WebSocket URL of the daemon (default ws://127.0.0.1:5225)
SIMPLEX_ALLOWED_USERS recommended Comma-separated contact IDs allowed to talk to the bot
SIMPLEX_ALLOW_ALL_USERS optional Set true to allow every contact (dev only)
SIMPLEX_HOME_CHANNEL optional Default contact/group for cron delivery
SIMPLEX_HOME_CHANNEL_NAME optional Human label for the home channel

Files (7)

plugins/platforms/simplex/__init__.py           |    3 +
plugins/platforms/simplex/adapter.py            |  746 +
plugins/platforms/simplex/plugin.yaml           |   37 +
scripts/release.py                              |    1 +    # AUTHOR_MAP for louismichalot@hotmail.com
tests/gateway/test_simplex_plugin.py            |  347 +
website/docs/user-guide/messaging/simplex.md    |   99 +
website/sidebars.ts                             |    1 +

Tests

27 new tests covering: Platform enum auto-discovery, check_requirements gating, validate_config / is_connected env+extra precedence, _env_enablement seeding (including home_channel), adapter init defaults, magic-byte file extension detection, correlation ID generation and self-trim, outbound send for DMs and groups, own-echo filtering by corrId prefix, standalone send paths (missing websockets / missing URL), and register() metadata.

tests/gateway/test_simplex_plugin.py:           27 passed
tests/gateway/test_platform_registry.py:        passed
tests/gateway/test_plugin_platform_interface.py: passed
tests/gateway/test_line_plugin.py:              passed (no regression)
tests/gateway/test_irc_adapter.py:              passed (no regression)
tests/gateway/test_google_chat.py:              passed (no regression)

Credit

The adapter logic — WebSocket lifecycle, reconnect backoff with jitter, health monitor, newChatItem / newChatItems event handling, DM / group routing, @[contact-id] / #[group-id] command shape, file attachment caching — is @Mibayy's original work, cherry-picked here with their authorship preserved on the feat commit.

Closes #2557. Original PR #2558 will be closed pointing to this one.

Mibayy and others added 2 commits May 15, 2026 01:34
SimpleX Chat (https://simplex.chat) is a private, decentralised messenger
with no persistent user IDs — every contact is identified by an opaque
internal ID generated at connection time. This adds it as a Hermes
gateway platform via the plugin system.

The adapter connects to a local simplex-chat daemon via WebSocket,
listens for inbound messages, and sends replies. Originally proposed in
PR #2558 as a core-modifying integration; reshaped here as a self-
contained plugin under plugins/platforms/simplex/ with no edits to any
core file. Discovery is filesystem-based (scanned by gateway.config),
and the platform identity is resolved on demand via Platform("simplex").

Plugin contract:
- check_requirements() requires SIMPLEX_WS_URL AND the websockets package
- validate_config() / is_connected() accept env or config.yaml input
- _env_enablement() seeds PlatformConfig.extra (ws_url + home_channel)
- _standalone_send() supports out-of-process cron delivery
- interactive_setup() provides a stdin wizard for hermes gateway setup
- register() wires the adapter into the registry with required_env,
  install_hint, cron_deliver_env_var, allowed_users_env, and a
  platform_hint for the LLM.

Lazy dependency: the websockets Python package is imported inside the
functions that need it. The plugin is importable and discoverable even
when websockets is missing — check_requirements() simply returns False
until `pip install websockets` is run. No new pyproject extras are
introduced.

Environment variables:
  SIMPLEX_WS_URL             WebSocket URL of the daemon (required)
  SIMPLEX_ALLOWED_USERS      Comma-separated allowed contact IDs
  SIMPLEX_ALLOW_ALL_USERS    Set true to allow all contacts
  SIMPLEX_HOME_CHANNEL       Default contact for cron delivery
  SIMPLEX_HOME_CHANNEL_NAME  Human label for the home channel

Closes #2557.
- Adds plugins/platforms/simplex docs page to the messaging sidebar
  between LINE and Open WebUI.
- Maps louismichalot@hotmail.com -> Mibayy in scripts/release.py so the
  attribution check on the salvage PR passes.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-79e33daf 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: 8262 on HEAD, 8251 on base (🆕 +11)

🆕 New issues (8):

Rule Count
unresolved-import 4
invalid-argument-type 3
unused-type-ignore-comment 1
First entries
run_agent.py:7482: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 3 union elements`
tests/gateway/test_simplex_plugin.py:14: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_simplex_plugin.py:61: [unresolved-import] unresolved-import: Cannot resolve imported module `websockets`
run_agent.py:13750: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 3 union elements`
tests/gateway/test_simplex_plugin.py:260: [unused-type-ignore-comment] unused-type-ignore-comment: Unused blanket `type: ignore` directive
tests/gateway/test_simplex_plugin.py:313: [unresolved-import] unresolved-import: Cannot resolve imported module `websockets.client`
plugins/platforms/simplex/adapter.py:632: [unresolved-import] unresolved-import: Cannot resolve imported module `websockets`
run_agent.py:13753: [invalid-argument-type] invalid-argument-type: Argument to function `len` is incorrect: Expected `Sized`, found `(str & ~AlwaysFalsy) | (dict[Unknown, Unknown] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 3 union elements`

✅ Fixed issues (3):

Rule Count
invalid-argument-type 3
First entries
run_agent.py:13750: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `str | dict[Unknown | str, Unknown | str | dict[str, str]] | Any | ... omitted 3 union elements`
run_agent.py:13753: [invalid-argument-type] invalid-argument-type: Argument to function `len` is incorrect: Expected `Sized`, found `(str & ~AlwaysFalsy) | (dict[Unknown | str, Unknown | str | dict[str, str]] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 3 union elements`
run_agent.py:7482: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | dict[Unknown | str, Unknown | str | dict[str, str]] | Any | ... omitted 3 union elements`

Unchanged: 4308 pre-existing issues carried over.

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

@teknium1 teknium1 merged commit 47614db into main May 15, 2026
16 of 18 checks passed
@teknium1 teknium1 deleted the hermes/hermes-79e33daf branch May 15, 2026 08:41
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins labels May 15, 2026
@robertbeckey

Copy link
Copy Markdown

I've got SimpleX connected and environment variables configured, but I receive no responses when I message my agent.

linxule pushed a commit to linxule/hermes-agent that referenced this pull request Jun 1, 2026
Bundled platform plugin following the LINE precedent (NousResearch#23197) and the
more recent SimpleX precedent (NousResearch#26232). KimiClaw is Moonshot AI's
agentic bot platform on kimi.com (launched Feb 2026). Distinct from
the Moonshot LLM provider (kimi-for-coding profile in
plugins/model-providers/kimi-coding/, aliased in
hermes_cli/providers.py) — this PR adds a separate chat platform and
does not touch that integration.

The adapter bridges two channels under one bot identity:
- DM via Zed ACP over WebSocket (sentinel session im:kimi:main)
- Group rooms via Connect RPC Subscribe server-stream over HTTP/1.1
  long-poll (chat_id room:<uuid>); outbound replies via unary
  Connect RPC SendMessage

Deployment model: Moonshot's intended bot deployment uses the
official OpenClaw runtime (claw-install.sh, V2026.4.5+) to own the
wire connection. This plugin instead speaks the wire protocol
directly from Python while sending the OpenClaw-shaped runtime
metadata headers kimi.com gates group-room participation on
("OpenClaw 3.13 or above"). Identity layers are honest:
User-Agent is hermes-kimi-adapter/1.0, X-Kimi-Claw-ID is
prefixed hermes-kimi-, X-Kimi-OpenClaw-Skills is suppressed by
default, and X-Kimi-OpenClaw-Version is set to the documented
group-gate floor (2026.3.13) — not a "real install" value. All
five OpenClaw-shaped headers are overridable via config.extra.
See plugins/platforms/kimiclaw/adapter.py:23-28 (module docstring)
and :103-112 (_GROUP_GATE_DEFAULTS) for the rationale.

Production wear: the same adapter code has run on one Raspberry Pi
gateway under daily user traffic since 2026-04-27. The adapter
previously shipped (and continues to ship) as an external plugin
at linxule/hermes-kimi-plugin; this PR upstreams it at version
1.0.0 — a stable snapshot intentionally decoupled from the external
repo's bleeding-edge stream.

Files:
- plugins/platforms/kimiclaw/{__init__,adapter,plugin}.{py,yaml}
- tests/gateway/test_kimiclaw_plugin.py
- website/docs/user-guide/messaging/kimiclaw.md (setup + deployment
  model + known limitations)
- website/docs/reference/environment-variables.md (+ KIMI_* table)
- website/docs/user-guide/messaging/index.md (capability row)
- website/sidebars.ts (sidebar entry)
- cli-config.yaml.example (kimiclaw: bridge block)
- pyproject.toml + uv.lock (declares websockets==15.0.1 in
  [messaging] extra for the DM ACP socket)
- scripts/release.py (AUTHOR_MAP attribution entry)

Internal class names retained as KimiAdapter / check_kimi_requirements
— implementation detail; matches the QQAdapter precedent. Env-var
names retained as KIMI_* because credentials are issued by
kimi.com itself.

261 tests passing (pytest tests/gateway/test_kimiclaw_plugin.py).
Includes regression coverage for the tool_only DM-inflight closure
path (test_3b_4, test_3b_5), bug-fixes for 429 retryability and
upload-path exception handling in the standalone send code path,
and the registry-gate (_check_for_registry) requiring KIMI_BOT_TOKEN
before auto-enable so messaging-equipped installs don't light up
KimiClaw without credentials.
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/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Add SimpleX Chat, Chat Adapter

4 participants