feat(simplex): groups, native attachments, text batching, auto-accept - #27978
feat(simplex): groups, native attachments, text batching, auto-accept#27978jooray wants to merge 1 commit into
Conversation
205e59c to
6d3b391
Compare
b457790 to
82b1c5a
Compare
…emon Four independent bugs prevented end-to-end SimpleX messaging against a real simplex-chat daemon: 1. WS listener never sent /_start, so the daemon stored inbound messages but never pushed events to the subscriber. 2. newChatItems batch events nest `chatItems` under `resp`, not at the top level — every batched message was silently dropped. 3. Group sender was read from the legacy `chatItemMember` key; current simplex-chat reports it under `chatItem.chatDir.groupMember`, so sender_id fell back to the chat_id and failed allowlist matching. Falls back to chatItemMember for older payloads. 4. Outbound send used `@[id]`/`#[id]` bracket syntax the daemon reads as a literal contact name; switched to the `/_send @id text` / `/_send #id text` API form in both send() and _standalone_send(). Bugs 1, 2 and 4 mirror upstream PR NousResearch#26433 (issue NousResearch#30150). Bug 3 is not covered there; the chatDir.groupMember approach matches PRs NousResearch#4666/NousResearch#27978. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch send() and _standalone_send() from the /_send <ref> text shorthand to the structured /_send <ref> json <ComposedMessage[]> form for both group and DM sends. The text shorthand truncates the body at the first newline, so multi-line agent replies were silently cut off; the json form escapes newlines, backslashes and other special characters correctly. This also aligns outbound text with the approach taken upstream in NousResearch#4666 / NousResearch#27978, easing a future rebase. Adds a regression test asserting a multi-line body is escaped rather than truncated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ening Adds a 2026-05-29 part-2 update: sender fix upstreamed as issue NousResearch#35045 / PR NousResearch#35046; rationale for NOT opening a group-send PR (already covered by NousResearch#4666/NousResearch#27978 with a more robust json form); production branch hardened to the /_send <ref> json form (commit d31a043) to stop multi-line reply truncation; and a rebase note for the eventual NousResearch#26433 merge conflict. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends the SimpleX Chat platform plugin (NousResearch#27???) with the features we had been carrying out-of-tree against the older core-modifying SimpleX integration. All additions live entirely inside ``plugins/platforms/simplex/`` — no edits to any core file — and pass the existing ``tests/gateway/test_simplex_plugin.py`` suite unchanged. What's new - **Group support** via ``SIMPLEX_GROUP_ALLOWED``. Comma-separated group IDs, or ``*`` for any. Omitted (default) keeps the safer behavior of ignoring group messages entirely — a bot in a group otherwise processes every member's traffic. Inbound items from groups are surfaced with ``chat_id="group:<id>"`` and ``chat_type="group"``. - **Real inbound file/voice ingestion** via the daemon's XFTP flow. On ``rcvFileDescrReady`` we send ``/freceive`` immediately so the download starts; voice notes whose file hasn't landed yet are deferred (``_pending_file_transfers``) and replayed once ``rcvFileComplete`` fires. The previous "sleep 2s and scan ``~/Downloads``" stub was race-prone on large files. Files are classified by extension into image / audio / document and emitted as ``MessageType.PHOTO`` / ``VOICE`` / ``TEXT + document``. - **Real outbound attachments**: ``send_image`` (including ``file://`` and ``http(s)://`` URLs with Pillow / ImageMagick conversion + inline thumbnail), ``send_image_file``, ``send_voice`` (native voice notes via ``msgContent.type="voice"``), ``send_video``, ``send_document``. All use the structured ``/_send … json`` form addressed by numeric ID so groups with non-unique display names don't get the wrong target. Agent replies may also embed ``MEDIA:<path>`` tags which the adapter strips from the text and dispatches as voice or document depending on extension. - **Text-burst batching** (``HERMES_SIMPLEX_TEXT_BATCH_DELAY``, default 0.8s). Rapid-fire inbound text messages within the quiet window are concatenated into one ``MessageEvent`` before reaching the agent — same pattern as Telegram's text batching. Prevents the gateway from dropping all-but-the-first message when a user pastes a multi-line prompt. - **Auto-accept contact requests** (``SIMPLEX_AUTO_ACCEPT``, default ``true``). Incoming ``contactRequest`` events trigger ``/accept <id>``. Disable explicitly if you want to gate every new contact behind an allowlist. - **Send / fire-and-forget split**. ``send()`` is now fire-and-forget at the WebSocket level — text replies don't serialise behind a 30-second timeout when the daemon skips the corr-id reply. The ``_send_command`` path with corr-id tracking is reserved for media commands where we genuinely need acknowledgement. The bracket form ``@[<id>]`` / ``#[<id>]`` is used for single-line text; multi-line content drops to ``/_send … json`` so newlines survive the daemon's command parser. Plugin manifest ``plugin.yaml`` bumps to ``1.1.0`` and surfaces the new env vars (``SIMPLEX_AUTO_ACCEPT``, ``SIMPLEX_GROUP_ALLOWED``, ``HERMES_SIMPLEX_TEXT_BATCH_DELAY``) in the ``hermes config`` UI. ``_env_enablement`` seeds matching keys into ``PlatformConfig.extra`` so env-only setups show up in ``hermes gateway status`` without instantiating the WebSocket client. Docs ``website/docs/user-guide/messaging/simplex.md`` documents the new env vars, the group-chat addressing scheme, and the inbound/outbound attachment behaviour. Compatibility All 27 existing ``tests/gateway/test_simplex_plugin.py`` tests pass unchanged: ``_make_corr_id`` still tracks ``_pending_corr_ids`` with the same trim invariant, ``send()`` still produces ``@[<id>]`` / ``#[<id>]`` for single-line text, ``_standalone_send`` keeps its text-only contract, and ``register()`` retains the same kwargs.
82b1c5a to
3d08a34
Compare
|
+1 on the WS-native direction. One empirical data point on the recurring "does the Tested against simplex-chat v6.5.1.1: it does push. With a single passive WebSocket client connected (nothing else talking to the daemon) and no The reason it so easily looks like the daemon doesn't push: items are nested under One note for the end-to-end file path: this PR adds the adapter-side media methods, but the |
|
A follow-up from a deeper read of the SimpleX adapter (alongside the inbound-document fix in #35490). Four issues land in regions this PR rewrites — documenting them here, and I've since opened standalone PRs for each against current
This PR rewrites these same regions but doesn't address any of the four, so they're complementary, not duplicates — cross-linking so the overlap is visible and whoever merges second has the context. |
Salvage of PR #27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Salvage of PR NousResearch#27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Salvage of PR #27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Salvage of PR NousResearch#27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Salvage of PR NousResearch#27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Salvage of PR NousResearch#27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Salvage of PR NousResearch#27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Salvage of PR NousResearch#27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Salvage of PR NousResearch#27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Salvage of PR NousResearch#27978 cherry-picked onto current main, resolving conflicts with main's intervening SimpleX plugin fixes (resp-envelope normalization, health-monitor reconnect-churn fix, bare-form DM addressing). What's new: - Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*'); inbound items surface chat_id=group:<id> + chat_type=group. Disabled by default so a bot in a group doesn't process every member's traffic. - Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred through _pending_file_transfers, replayed on rcvFileComplete. Voice notes -> MessageType.VOICE. - Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice (msgContent.type=voice), send_video, send_document. All addressed by numeric ID via /_send ... json [...]. - MEDIA:<path> tags in agent replies stripped and dispatched as voice/document. - Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s). - Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true). - Group send path uses structured /_send #<id> json form (the bracket #[<id>] form is parsed as display-name lookup and silently drops). plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/ - no core edits. Co-authored-by: Juraj Bednar <juraj@bednar.io>
Summary
Extends the SimpleX Chat platform plugin merged in #27???/
09d9724a0with the features our deployment was carrying out-of-tree against the older core-modifying SimpleX integration (#2558/#4870). All additions live insideplugins/platforms/simplex/— no edits to any core file — and the existingtests/gateway/test_simplex_plugin.pysuite (27 tests) passes unchanged except for two that asserted send-format details we believe are wrong (see Test changes below).This PR is a new PR, not a refresh of #4870. #4870 was the original core-modifying version. After the plugin landed upstream we rebuilt the work on top of the plugin contract, which is the architecture this codebase is clearly moving towards. #4870 should be closed in favour of this one.
Where the current plugin falls short
The current
plugins/platforms/simplex/adapter is a faithful first port from the original PR description, but in a real deployment it has a few gaps:chat_type="group"on the event.SIMPLEX_GROUP_ALLOWEDenv var (comma-separated IDs or*); inbound items surface withchat_id="group:<id>"+chat_type="group". Omitted by default — a bot in a group otherwise processes every member's traffic._fetch_filedoes/freceivethensleep(2)and scans~/Downloads,~/.simplex/files,/tmp/simplex_filesfor the filename. Race-prone on large files; voice notes that need >2s for XFTP transfer get dropped.rcvFileDescrReadyto send/freceiveimmediately on file announcement; defers the chat item via_pending_file_transfersand replays it oncercvFileCompletefires. Distinguishes voice notes (→MessageType.VOICE), photos, and documents.send_imageis a stub: postsf"{caption}\n{image_url}"as plain text. Nosend_voice,send_video,send_document.send_image_file/send_image(PNG/JPEG with Pillow or ImageMagick conversion + inline thumbnail),send_voice(nativemsgContent.type="voice"so the receiving client renders the inline voice-note player rather than a generic file download),send_video,send_document. All addressed by numeric ID via the structured/_send … json [...]form so groups with non-unique display names don't get the wrong target.MEDIA:<path>tags in agent repliesMessageEvents, and only the first reaches the agent before the rest get queued/dropped.HERMES_SIMPLEX_TEXT_BATCH_DELAY(default 0.8s). Rapid-fire inbound texts within the quiet window are concatenated into oneMessageEvent— same pattern as Telegram's text batching.contactRequestevents are silently ignored, so every new contact has to be approved manually.SIMPLEX_AUTO_ACCEPT(defaulttrue). Disable explicitly to gate every new contact behind an allowlist.cmd_str = f"#[{group_id}] {content}". This silently doesn't deliver in production unless the group's display name is literally the numeric ID — the daemon parses#[...]as display-name lookup. Confirmed by sending messages togroup:1(display nameVictoria-H) and watching them never arrive in the chat while the gateway reported "Sending response (... chars) to group:1".cmd_str = f"/_send #{group_id} json [...]". Structured form; addresses by numeric ID;json.dumpsescapes newlines and quotes correctly.cmd_str = f"@[{chat_id}] {content}"— also bracket form.cmd_str = f"@{chat_id} {content}"— bare form, matches what's worked in production for months.Test changes
test_send_dmandtest_send_grouppreviously asserted the bracket form. Both expectations were updated to match the formats above; this is in the same commit so the suite still passes 27/27.If you'd rather keep the bracket-form expectations, the daemon-side parser is the place to fix it — a
#[ID] textpayload addressed by numeric ID would Just Work and would be consistent with the rest of the plugin's/_sendcalls. Happy to follow that thread separately.What's not in this PR
tools/send_message_toolfor out-of-process (cron) sends including media. The plugin'sstandalone_sender_fncontract is text-only, and that's preserved here. If we want media forhermes cronrunning as a separate process fromhermes gateway, that's a follow-up that probably belongs in the plugin contract.plugin.yaml(version bumped to 1.1.0) and documented inwebsite/docs/user-guide/messaging/simplex.md.Test plan
pytest tests/gateway/test_simplex_plugin.py— 27 passeddeliver=simplex)rcvFileCompleteMEDIA:<path>tagsThe bracket-form regression I mentioned above was caught the same day we deployed this PR's branch and is the reason the send paths look the way they do here.