Skip to content

About

XMPP/Jabber gateway platform plugin for Hermes Agent

Topics

Resources

Stars

12 stars

Watchers

1 watching

Forks

Repository files navigation

Hermes XMPP/Jabber plugin

Third-party XMPP/Jabber gateway platform plugin for Hermes Agent.

This packages the work from the upstream Hermes Agent XMPP PRs as an optional user plugin so self-hosters can use XMPP now instead of waiting for the core PR to land. It supports:

  • 1:1 XMPP chats
  • MUC group rooms
  • mandatory STARTTLS to the XMPP server
  • XEP-0085 typing indicators
  • XEP-0363 HTTP file upload for media/files
  • XEP-0004 data forms (e.g., clarify prompts)
  • XEP-0050 ad-hoc commands
  • XEP-0394 message markup (bold, code, lists)
  • XEP-0461 threaded message replies
  • XEP-0444 message reactions (e.g., 👀/✅/❌ for processing status)
  • XEP-0447 stateless file sharing for voice messages
  • OMEMO end-to-end encryption (optional, needs slixmpp-omemo)
  • cron and send_message delivery through a standalone sender hook
  • Hermes platform plugin registration via ctx.register_platform(...)

Traffic is encrypted to your XMPP server with TLS. When OMEMO is enabled and slixmpp-omemo is installed, 1:1 messages are also end-to-end encrypted so the server cannot read content. MUC OMEMO support depends on client/device availability.

Credits

Most adapter code is derived from Eric Lars Lee's upstream PR #17469. Mibay's PR #3105 is credited for earlier XMPP/OMEMO exploration. See ATTRIBUTION.md for the full credit note. This repo exists because waiting is annoying, not because the packager wants credit for other people's work. Tiny open-source goblin energy, responsibly attributed.

Install

From the Hermes profile you want to use:

cd ~/.hermes/plugins
git clone https://github.com/fastfinge/hermes-xmpp-plugin.git
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python -r ~/.hermes/plugins/hermes-xmpp-plugin/requirements.txt
hermes config set plugins.enabled '["hermes-xmpp-plugin"]'

If you run Hermes from a different checkout/venv, install requirements into that Python environment instead.

Restart the gateway after installing or changing env vars:

hermes gateway restart

Configure with env vars

Add these to ~/.hermes/.env for the active profile:

XMPP_JID=[email protected]
XMPP_PASSWORD=your-password
XMPP_ALLOWED_USERS=[email protected]
# Optional:
XMPP_HOST=example.org
XMPP_PORT=5222
# XMPP_DIRECT_TLS=true   # force XEP-0368 direct TLS; auto-on when XMPP_PORT=5223
# XMPP_USE_IPV6=false    # disable IPv6 (default: auto-detect a working global v6 route)
# XMPP_CONNECT_TIMEOUT_SECS=240  # default 180s; raise further on a slow/high-latency link
XMPP_MUC_ROOMS=[email protected]/hermes
XMPP_MUC_NICK=hermes
XMPP_HOME_CHANNEL=[email protected]

XMPP_ALLOWED_USERS uses bare JIDs. MUC access is gated by room membership: if the bot joins a room listed in XMPP_MUC_ROOMS, messages in that room are accepted. For quick local testing only, set XMPP_ALLOW_ALL_USERS=true.

Configure with config.yaml

You can also use config.yaml:

plugins:
  enabled:
    - hermes-xmpp-plugin

xmpp:
  enabled: true
  jid: [email protected]
  password: ${XMPP_PASSWORD}
  allowed_users:
    - [email protected]
  muc_rooms:
    - [email protected]/hermes
  home_channel: [email protected]

Env vars win when both are set.

OMEMO end-to-end encryption (optional)

Install the extra dependencies:

uv pip install --python ~/.hermes/hermes-agent/venv/bin/python slixmpp-omemo omemo

Then set in ~/.hermes/.env:

XMPP_OMEMO_ENABLED=true
# Optional — defaults to ~/.hermes/xmpp_omemo.json
XMPP_OMEMO_STORAGE_PATH=/home/fastfinge/.hermes/xmpp_omemo.json

Or in config.yaml:

xmpp:
  omemo_enabled: true
  omemo_storage_path: /home/fastfinge/.hermes/xmpp_omemo.json

On first connect, the adapter generates an OMEMO device key and publishes it to the server. This may take a few seconds — the adapter waits for initialization before sending encrypted messages. If the recipient hasn't published device keys yet, messages fall back to plaintext (with a log warning).

MUC OMEMO is supported when all participants have compatible devices, but group encryption reliability varies by client. 1:1 encryption is the primary use case.

Multiple Hermes profiles

XMPP is nice for profile isolation because you can create one JID per profile:

Each Hermes profile gets its own .env, memory, sessions, and gateway process.

Server notes

Prosody needs modules for MUC, HTTP file upload, and the new XEPs. On Debian/Ubuntu:

sudo apt install prosody
sudo prosodyctl adduser [email protected]

Enable/configure in Prosody:

  • muc — group rooms
  • http_file_share — file upload (XEP-0363)
  • mod_groups — optional, for ad-hoc command roster

For ejabberd, enable MUC plus mod_http_upload. The new features (reactions, replies, markup) use standard XMPP stanzas and should work on any modern server supporting XEP-0444, XEP-0461, and XEP-0394.

Troubleshooting

  • xmpp_auth_failed: wrong JID/password, or server auth policy issue.
  • xmpp_connect_timeout: the gateway will report the platform as connected right away (connecting doesn't block gateway startup), but a background watchdog gives up and retries if the session hasn't actually established within XMPP_CONNECT_TIMEOUT_SECS (default 180s). A common cause on home networks: DNS returns both an A and AAAA record for the server, the IPv6 route is silently blackholed, and the OS burns through its full TCP retry budget (~130s on Linux defaults) before falling back to IPv4 — which then connects instantly. Check python3 -c "import socket; print(socket.getaddrinfo('your.server', 5222))" for a AAAA record, test IPv6 reachability separately (ping6/curl -6), and either fix IPv6 routing or disable IPv6 for this host if it's not actually usable. If raising XMPP_CONNECT_TIMEOUT_SECS further fixes it, that confirms this cause. Otherwise it's a DNS/firewall/SRV/reachability issue — try XMPP_HOST/XMPP_PORT or check the server is reachable at all.
  • xmpp_connection_lost: an established session dropped unexpectedly (server restart, network blip); the gateway's reconnect watcher should retry.
  • xmpp_lock: another gateway process is already logged into this JID; stop it first, or use a separate bot account per gateway.
  • HTTP upload failure: enable XEP-0363 on the server and check max file size.
  • DM rejected: add your bare JID to XMPP_ALLOWED_USERS.
  • MUC silent: add room to XMPP_MUC_ROOMS and invite the bot.

Features in depth

Message reactions (XEP-0444)

When Hermes starts processing a request, the adapter sends a 👀 reaction. When it finishes, ✅ or ❌ is sent depending on success or error. Reactions are visible in clients that support XEP-0444 (Conversations, Dino, Gajim, etc.).

Reactions can also be sent by Hermes tools; the adapter maps the reaction to the original message using slixmpp's xep_0444.

Threaded replies (XEP-0461)

When you reply to a bot message, the adapter tracks the thread using XEP-0461 message references. Replies from Hermes are sent back into the same thread so context is preserved.

Message markup (XEP-0394)

Markdown-like syntax in Hermes responses is converted to XEP-0394 message markup:

  • **bold** → `bold
  • `code` → `code
  • ```block``` → `block

Clients that support XEP-0394 render this natively. Others get the raw text.

Data forms (XEP-0004) for clarify

When Hermes needs clarification, the adapter sends an XEP-0004 data form instead of a plain text list. The form includes:

  • A hidden clarify_id field
  • A list-single choice field with the available options
  • An "Other (type your own answer)" option for free-form responses

The message body also includes a numbered text fallback so that clients which do not render jabber:x:data forms in <message> stanzas (Conversations, ConverseJS, older Gajim) still display actionable choices. Users on those clients can reply with the number or option text as normal.

When a client does render the form and the user submits it, the adapter reads the form response, extracts the clarify_id and selected answer, and resolves the clarification back to Hermes automatically.

Disabling data forms: If you prefer to always use plain text clarify (never send XEP-0004 forms), set the environment variable XMPP_DISABLE_CLARIFY_FORMS=true or add disable_clarify_forms: true to your XMPP platform config in config.yaml. This forces the numbered text fallback path even when the xep_0004 plugin is registered.

Voice messages (XEP-0447)

Voice messages from Hermes are sent as XEP-0447 Stateless File Sharing (SFS) messages. The file is uploaded via XEP-0363 HTTP upload first, then a lightweight SFS reference is sent. This allows clients to preview metadata before downloading.

Voice calls (Jingle) are not yet supported because slixmpp does not include a Jingle RTP implementation.

Ad-hoc commands (XEP-0050)

The adapter registers ad-hoc commands on the bot's JID. Users can discover them with their client's command list (e.g., /cmd in Conversations). Currently a basic command list is exposed; future releases may add Hermes-specific commands.

Development

The test suite runs standalone — no Hermes checkout required. It uses mocks for the Hermes gateway and slixmpp internals.

python -m pytest -q

For testing against a real Hermes checkout, set PYTHONPATH:

PYTHONPATH=/path/to/hermes-agent python -m pytest -q

About

XMPP/Jabber gateway platform plugin for Hermes Agent

Topics

Resources

Stars

12 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages