feat(gateway): add Zulip integration and messaging support - #3335
feat(gateway): add Zulip integration and messaging support#3335nicolasembleton wants to merge 4 commits into
Conversation
59d6db9 to
ca943ef
Compare
|
Interested in this, thanks for submitting. |
ca943ef to
7913188
Compare
Thanks @punk5736 . I've been using it since I published it, and keeping it up-to-date with current Hermes main, so you can try to pull this PR branch and use it as your Hermes see if you find any bugs. There were a few issues after latest version, and I've also added send_voice() so now Hermes can send me summaries for things such as job search summry or news, etc... Super handy. There may be some features that I have not used yet that aren't supported so I would love any feedback if y'all feel like trying it. |
61a0dfa to
ecfd939
Compare
ecfd939 to
fa55a5e
Compare
fa55a5e to
7d4c020
Compare
c96ca9c to
12f5a12
Compare
|
Thank you for building this! Zulip's interface seems a lot better for managing many long-running conversations with hermes than the other gateways I have tried. I have deployed this branch on my machine and it is working pretty well. A couple small hiccups:
|
Nice. Thanks for giving it a try and thanks for the feedback. I just realized I hadn't noticed the missing typing indicator 😅 . Let me fix this and see if I can improve the setup flow (or at the very least the documentation) |
|
Just to add to this. I was having hard time getting hermes to send message to specific, different from current, chat:stream, for example to start a new stream. It said there's a bug and fixed with this code. Code is generated, so could be completely off, but it worked and it started to send messages into different streams as expected. |
|
@nicolasembleton is the paging for long messages imposed by Zulip? It would be nice if there was a way for them to be continuous, so figured I'd check. |
|
I'm often requesting agent to create new zulip thread to address something new that comes up, to essentially avoid mixing different things in one context. Makes it very easy to change focus and then come back to where we were. Hermes can do it, but it kept creating temp scripts, etc. So we created a tool to help with that. It allows hermes to send message to any Zulip thread, creating it if it doesn't exist. Makes very easy to split work. I did keep it in Also, the ability to stream via editing message worked great, but on desktop client for Zulip the message would flash all the time essentially making it unreadable. To address it's possible to use Hermes built-in The patch is based on current PR with Zulip being a plugin. p.s. Updated patch to fix credentials lookup bug |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for moving the adapter onto the plugin path requested in the maintainer thread; the registration at plugins/platforms/zulip/adapter.py:2425-2456 uses the existing platform hooks cleanly.
Problems
plugins/platforms/zulip/adapter.py:939calls synchronousupdate_message()inside asyncedit_message(). Streaming invokes this repeatedly, so a slow Zulip request blocks the gateway event loop. Please offload it withawait asyncio.to_thread(...)and cover that path.plugins/platforms/zulip/adapter.py:538-540directly appliesint()to optionalZULIP_CONTEXT_DEPTH. A malformed value aborts adapter construction. The nearby catch-up parser already defaults safely on malformed input at:564-576; apply the same pattern and add a regression test.
Suggested changes
- Preserve the current
SendResultbehavior after moving the edit request off-loop. - Default invalid context depth to disabled (
0) rather than failing gateway startup.
Automated hermes-sweeper review.
|
I'm traveling to Berlin ATM but the PR update is ready, will push it tomorrow after I've finished my tests. |
|
@teknium1 the fixes have been pushed, resynced with upstream, @slav patch is in, and I made small improvements to attachments handling, and checked if we could improve paging following @punk5736 request, but unfortunately it is dependent on Zulip, which is not friendly to larger messages. You can tweak your own Zulip server config to set it to 10k characters but it won't allow more than that. |
ajtowns
left a comment
There was a problem hiding this comment.
This is really nice -- I've been trying to figure out how to chat locally and get equations and images nicely inline.
if non-blocking perhaps we can defer to follow up? or a PR into this worknig set? would be great to see this in asap :D |
|
Rebased to v0.19.1. @teknium1 please merge when you can :) |
|
It seems Approve buttons broke (probably due to hermes changes). Here's the short patch to fix them to show again. From AI describing the patch:
|
I did notice this, yes. Let me fix this and thanks for the patch. |
|
Sorry, forgot to report back but I've been using the latest version since I updated and it's been good to me. :) Next update I'll squash all the commits into 1, but meanwhile y'all can see what happened. |
|
Rebased to HEAD. |
|
Hey @nicolasembleton, is message-splitting being advertised such that cron jobs can post more than 4000 characters? I am running the latest commit and have a cron job running which truncated output. The cron process did not attempt to split the output across several messages. Asking hermes to debug the truncation informed me that cron jobs expect Thank you once again. |
Hmm, on other cron runs it seems to be sending longer (and complete) messages. For reference, the truncated cron run message ended with: |
Hey — thanks for flagging this. You were right. @punk5736 Cron has a 4 000-character safety cap for platforms it thinks can't split long messages. Zulip's send() already splits at 10k (that's Zulip's own limit), but we never told cron that. So if the gateway was running, it would cut the message and dump the rest to a file — that's the [truncated, full output saved to …] footer you saw. When cron ran without a live gateway it already sent the full thing, which is why some jobs looked fine and others didn't. No Zulip max-length setting to change on your side. Fix is a one-liner: advertise splits_long_messages = True, same as Discord / Telegram / Slack.
For your case: that truncated run should now post in full. If it's over 10k it'll come through as a few messages instead of one short one plus a path. One thing to watch: a huge dump will now show up as several messages in the topic, not one stub. We still save the file when it's over 4k, we just won't mention it in-channel. And the whole cron body now goes through the |
|
@nicolasembleton It seems that cron jobs, when manually triggered, fail to send documents in messages. When the cron is a regular tick, the documents send fine. Also, when I ad hoc ask hermes to send the same document it produced in a cron run it is able to send it fine. After asking hermes to analyze this, it responded with: What do you think? |
…#3335) Add Zulip (cloud or self-hosted) as a supported messaging platform. Purely additive — no upstream code removed or replaced. Ships as a platform plugin under plugins/platforms/zulip/ with adapter, attachments, search, and topic tools, plus docs and tests. Review follow-ups and post-merge fixes rolled into this commit: - send_exec_approval accepts allow_session / allow_permanent / smart_denied - full command in its own message, zform buttons as a follow-up - platform_hint documents MEDIA paths and Zulip KaTeX markup - multi-line dollar display math rewritten to fenced math blocks - convert $...$, \(...\), \[...\], and whole-line $$ to Zulip KaTeX (opt out with ZULIP_CONVERT_MATH=false) - pin Zulip client to 0.9.1 - rejoin multi-chunk replies in search (i/n markers) - client-scan long messages when FTS misses content in large replies 314 Zulip-related tests passing. Co-authored-by: Kevin Patch <klp@anaconda.net> Co-authored-by: Slav Ivanyuk <350987+slav@users.noreply.github.com>
…at 4k Cron delivery was cutting live-gateway Zulip jobs at 4000 characters because the adapter never set splits_long_messages. send() already chunks at 10k.
Zulip was missing from MESSAGING_SESSION_SOURCE_IDS, so gateway sessions never grouped with Mattermost/WhatsApp. Register the source, brand icon, and messaging intro copy.
Manual hermes cron run and dashboard fires have no live adapter, so delivery used _standalone_send_zulip, which refused MEDIA files and dropped the digest with them. Reuse send_document/send_image_file (throwaway Zulip client, same API as scheduled ticks). A failed upload is a warning so the text still lands.
|
@teknium1 Could this PR get another round of review? I'm daily driving this and would like to get back on the main line. |
Once again, thanks for surfacing good use cases @punk5736. I had not done that yet so this was missed. The zulip integration was explicitly not accepting standalone sends (as in, not directly requested through a live session). Standalone now sends the message text, then the files. If an upload fails, you still get the digest. I added cron-send to my smoke test script and all passed. The only test that did not pass completely is full-text-search in very long messages in large Zulip instances when resources are limited, ingest / search may take time, I'll see if there's something I can do there, but this should not be a problem for the vast majority of users/use-cases. This update also adds support for Zulip in Desktop UI, the same way other Channels do @teknium1 Note: I also rebased while I was at it. |
Can confirm that a manually triggered cron now is able to send Zulip messages. Also confirming that the gateway UI shows Zulip on the channels view. In the configuration view, some of the input fields have placeholders which indicated the environment variable for the field while others have a default (?), example, or basic placeholder text .. Should this be uniform? Possibly to environment variables hints? This is a stray suggestion since I use the Nix + |

What does this PR do?
This PR adds Zulip support to Hermes as a bundled platform plugin under
plugins/platforms/zulip/.The original branch predated the current platform plugin system and wired Zulip
through several core gateway, CLI, scheduler, and tool files. This update moves
the integration onto the current plugin path: platform registration, setup
metadata, env/YAML configuration, auth validation, adapter construction, cron
delivery metadata, standalone text delivery, and the Zulip search tool now live
with the plugin.
The goal is to keep Zulip support aligned with the current platform architecture
while preserving the adapter behavior that has been tested by real users.
Related Issue
Related to the earlier Zulip PRs:
This PR remains the canonical Zulip candidate per maintainer feedback on this
thread.
Fixes #
Type of Change
Changes Made
plugins/platforms/zulip/hooks, and Zulip search tool.
names containing
/, media handling, typing/reactions where supported, and edit-basedstreaming.
pyproject.toml,uv.lock,tools/lazy_deps.pyzulipextra.website/docs/user-guide/messaging/zulip.mdwebsite/docs/user-guide/messaging/index.mdtests/gateway/test_zulip.pytests/tools/test_zulip_search_messages.pytests/test_project_metadata.pyThis revision also incorporates updates from @slav's patches and @a6patch's
branch work, manually integrated into the plugin structure to keep this PR
reviewable:
edit_message(..., finalize=..., metadata=...)support for gateway streaming.How to Test
Install with the Zulip extra, or with all extras:
or:
Configure Zulip credentials and run setup/gateway:
Validate normal gateway behavior from Zulip:
/, such assome/topics/with/slashes.zulip_search_messagesfrom a Zulip conversation.Validate standalone send with a configured home channel:
hermes send --to zulip "Standalone Zulip send smoke"Validate cron delivery:
Validate edit-based streaming by enabling both the global/root streaming flag
and the Zulip platform streaming flag:
Run focused tests:
Checklist
Code
CONTRIBUTING.md)
(
fix(scope):,feat(scope):, etc.)sure this isn't a duplicate. Related Zulip PRs are Zulip gateway #16541 and Add Zulip platform adapter #54715; this PR is the canonical
Zulip candidate per maintainer feedback.
pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A
(https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#cross-platform-
compatibility) — or N/A
Screenshots / Logs
Manually validated:
/.Example standalone send result:
Notes / Follow-Ups
Edit-based streaming requires the global/root gateway streaming setting to be
enabled in addition to the Zulip platform setting. Users should also be aware
that Zulip may retain message edit history depending on the organization's Zulip
settings.
Two shared follow-ups were found while testing this PR:
Direct CLI sends to a specific Zulip topic, such as
hermes send --to "zulip:stream:topic" ..., need one small shared sender fixoutside of the Zulip plugin. This will be proposed separately after the plugin
PR lands.
One-shot cron jobs using
--repeat 1can sometimes deliver correctly butstill print a failure status after completion. This appears to overlap with
fix: report immediate cron run status for depleted repeat jobs #57689, so it is not included here.