fix(email): enable splits_long_messages to avoid 4000-char truncation cap - #62000
fix(email): enable splits_long_messages to avoid 4000-char truncation cap#62000liuhao1024 wants to merge 1 commit into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for isolating the missing capability declaration. The current-head premise is valid: gateway/delivery.py:431 truncates when the flag is false, while EmailAdapter.send() forwards its body to MIMEText and smtp.send_message at plugins/platforms/email/adapter.py:893-954.
Problems
tests/gateway/test_email.py:1778asserts only the implementation attribute. It does not exercise the reported delivery contract. The existing full-payload router coverage is generic (tests/gateway/test_delivery.py:334-351), not email-specific.- The base contract says
splits_long_messagesmeanssend()splits throughtruncate_message()(gateway/platforms/base.py:2290-2296), but email sends one full SMTP body (plugins/platforms/email/adapter.py:949-954). The new comment should not call that chunking.
Suggested changes
- Add a router-level >4000-character email regression test that captures
EmailAdapter.send()input and asserts it equals the original content. - Describe email as preserving a native long message, or broaden the capability documentation accordingly.
Automated hermes-sweeper review.
| # (SMTP handles arbitrary lengths, adapter internally defines MAX_MESSAGE_LENGTH=50_000). | ||
| # Enable native chunking so full payload reaches send() instead of being truncated | ||
| # at gateway/delivery.py's MAX_PLATFORM_OUTPUT (4000 chars) cap. | ||
| splits_long_messages = True |
There was a problem hiding this comment.
splits_long_messages is documented as send() splitting through truncate_message() in gateway/platforms/base.py:2290-2296, but this adapter sends one MIMEText body via SMTP. Please describe this as native full-message delivery (and consider broadening the base capability contract) rather than native chunking.
| class TestEmailAdapterSplitsLongMessages(unittest.TestCase): | ||
| """Verify EmailAdapter declares splits_long_messages=True to avoid truncation.""" | ||
|
|
||
| def test_splits_long_messages_enabled(self): |
There was a problem hiding this comment.
Please add a router-level regression using this adapter (with send captured) and a payload over MAX_PLATFORM_OUTPUT; asserting the class attribute alone does not prove that the email delivery route receives the full body.
…tract Implements teknium1's feedback on PR NousResearch#62000: - Clarify comment: email preserves full payload without chunking (not "native chunking") - Add router-level regression test verifying >4000-char content reaches send() intact - Test covers the actual delivery contract through DeliveryRouter, not just class attribute
a42e476 to
f79efbc
Compare
The email adapter never declared `splits_long_messages`, so the delivery router (gateway/delivery.py) treats it as a non-chunking platform and applies MAX_PLATFORM_OUTPUT = 4000 — a cap sized for Telegram's 4096-char API limit. Email has no such constraint: the adapter already defines MAX_MESSAGE_LENGTH = 50_000 (Gmail-safe body size) but never uses it. Observed: a 5,199-char scheduled-job memo was cut to the 4,000-char envelope, severing an escalation bullet, a deadline statement, and a whole closing section before it reached the mailbox. There is no config knob to opt out (issue NousResearch#61990). send() now splits at MAX_MESSAGE_LENGTH via the shared truncate_message() helper — the same pattern every peer adapter uses (Slack, Discord, Telegram, Mattermost, Matrix, Teams, Feishu) — and the class declares splits_long_messages = True so the router hands over the full payload. Note on scope vs. the existing open PRs for this bug (NousResearch#62000, NousResearch#68900): declaring splits_long_messages alone is not sufficient. That flag is a promise to the router that the adapter chunks; upstream send() does no chunking, so the flag by itself converts a 4,000-char truncation into an unbounded body handed to SMTP. This change implements the chunking the flag advertises, which is why MAX_MESSAGE_LENGTH stops being dead code. No shared code changed: the router's cap stays correct for platforms that genuinely have one. Tests: 4 regression cases in tests/gateway/test_email.py, including an end-to-end pass through the real DeliveryRouter asserting an oversized payload arrives byte-identical with the audit copy still written. Tested on: Linux (Ubuntu 24.04, Python 3.11). Fixes NousResearch#61990
What does this PR do?
Adds
splits_long_messages = TruetoEmailAdapterclass attributes.The delivery router (
gateway/delivery.py) checksadapter.splits_long_messagesat line 431 to decide whether to truncate content atMAX_PLATFORM_OUTPUT(4000 chars) or deliver the full payload. WhenTrue, the full content passes to the adapter'ssend()method, which can chunk natively. WhenFalse(the default), content above the cap is truncated with a footer pointing to a saved file.EmailAdapterwas missing this declaration, causing all email delivery to hit the truncation path even though:send()method handles the full payload viaMIMEText(body, "plain", "utf-8")MAX_MESSAGE_LENGTH = 50_000, indicating intent to support much longer messagesAll other platform adapters (Telegram, Discord, Slack, Matrix, WhatsApp, etc.) already declare
splits_long_messages = True. This change aligns email with their behavior and fixes the silent truncation bug where cron job output was cut off mid-content.Related Issue
Fixes #61990
Type of Change
Changes Made
plugins/platforms/email/adapter.py: Addedsplits_long_messages = Trueclass attribute with explanatory commenttests/gateway/test_email.py: AddedTestEmailAdapterSplitsLongMessagestest class to verify the attribute is declaredHow to Test
pytest tests/gateway/test_email.py::TestEmailAdapterSplitsLongMessages -vpython -c "from plugins.platforms.email.adapter import EmailAdapter; print(EmailAdapter.splits_long_messages)"should printTrueObserved result: The test passes, confirming that
EmailAdapter.splits_long_messagesisTrue.Checklist
Code
fix(scope):,feat(scope):, etc.)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