Follow-up to lkz-de's adapter chunking commit: long Signal messages no longer truncate on ANY delivery path. - tools/send_message_tool.py: register Signal's 8000-char limit in _MAX_LENGTHS (imported from the adapter module so the two paths can't drift) so hermes send / cron standalone / MCP sends split via the shared truncate_message() pass instead of signal-cli rejecting them. Standalone-path idea credited to @5L-hermes01 (#67279). - tests: regression test proving standalone Signal sends chunk at the adapter limit with no truncation footer (fails on pre-fix main). - docs: Long Messages section on the Signal page (en + zh-Hans). Both fixes verified by sabotage A/B (tests fail with the respective half reverted to origin/main) and a real-import E2E: 27k-char message with emoji + cross-boundary bold + code blocks -> 4 chunks, all styles in-range UTF-16, lossless reassembly.
This commit is contained in:
@@ -490,6 +490,41 @@ class TestSendToPlatformChunking:
|
||||
for call in send.await_args_list:
|
||||
assert len(call.args[2]) <= 2020 # each chunk fits the limit
|
||||
|
||||
def test_signal_long_message_is_chunked(self, monkeypatch):
|
||||
"""Standalone Signal sends split at the adapter's 8000-char limit.
|
||||
|
||||
The standalone path (hermes send / cron / MCP) speaks raw JSON-RPC via
|
||||
_send_signal and bypasses SignalAdapter.send(), so the shared
|
||||
truncate_message() pass in _send_to_platform must know Signal's limit
|
||||
(regression for #67279 / #57929 — long sends were rejected whole).
|
||||
"""
|
||||
from gateway.platforms.signal import MAX_MESSAGE_LENGTH as SIGNAL_MAX
|
||||
import tools.send_message_tool as smt
|
||||
|
||||
sent = []
|
||||
|
||||
async def fake_send_signal(extra, chat_id, chunk, media_files=None):
|
||||
sent.append(chunk)
|
||||
return {"success": True, "platform": "signal", "chat_id": chat_id}
|
||||
|
||||
monkeypatch.setattr(smt, "_send_signal", fake_send_signal)
|
||||
|
||||
long_msg = "word " * ((SIGNAL_MAX // 5) + 500) # comfortably over limit
|
||||
result = asyncio.run(
|
||||
_send_to_platform(
|
||||
Platform.SIGNAL,
|
||||
SimpleNamespace(enabled=True, token=None,
|
||||
extra={"http_url": "http://localhost:8080",
|
||||
"account": "+15551234567"}),
|
||||
"+15557654321", long_msg,
|
||||
)
|
||||
)
|
||||
assert result["success"] is True
|
||||
assert len(sent) >= 2, "long Signal message must be split, not sent whole"
|
||||
assert all(len(chunk) <= SIGNAL_MAX for chunk in sent)
|
||||
# No truncation footer — content is delivered in full across chunks
|
||||
assert all("truncated, full output saved to" not in c for c in sent)
|
||||
|
||||
|
||||
def test_slack_pre_escaped_entities_not_double_escaped(self, monkeypatch):
|
||||
"""Pre-escaped HTML entities survive tool-layer formatting without double-escaping."""
|
||||
|
||||
@@ -968,6 +968,18 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
|
||||
Platform.TELEGRAM: TelegramAdapter.MAX_MESSAGE_LENGTH if _telegram_available else 4096,
|
||||
}
|
||||
|
||||
# Signal's standalone path (_send_signal) speaks raw JSON-RPC and does not
|
||||
# go through SignalAdapter.send(), so it never benefits from the adapter's
|
||||
# native chunking. Register the platform limit here so the shared
|
||||
# truncate_message() pass below splits long sends instead of signal-cli
|
||||
# rejecting them. Sourced from the adapter module so the two paths can't
|
||||
# drift (credit: @5L-hermes01 in #67279, @lkz-de in #57929).
|
||||
try:
|
||||
from gateway.platforms.signal import MAX_MESSAGE_LENGTH as _SIGNAL_MAX
|
||||
_MAX_LENGTHS[Platform.SIGNAL] = _SIGNAL_MAX
|
||||
except ImportError:
|
||||
_MAX_LENGTHS[Platform.SIGNAL] = 8000
|
||||
|
||||
# Check plugin registry for max_message_length
|
||||
if platform not in _MAX_LENGTHS:
|
||||
try:
|
||||
|
||||
@@ -181,6 +181,10 @@ Signal messages render with **native formatting** instead of literal markdown ch
|
||||
|
||||
None of this requires additional config — it ships on by default in recent signal-cli builds. If your `signal-cli` version is too old, Hermes falls back to plaintext delivery and logs a one-time warning.
|
||||
|
||||
### Long Messages
|
||||
|
||||
Signal caps a single message at **8,000 characters**. Hermes splits longer responses into numbered chunks (`(1/3)`, `(2/3)`, …) automatically instead of truncating them. This applies to every delivery path — live conversation replies, cron job deliveries, `hermes send`, and MCP `send_message` calls — and native formatting (bold, italic, code, spoilers) is preserved across chunk boundaries.
|
||||
|
||||
### Typing Indicators
|
||||
|
||||
The bot sends typing indicators while processing messages, refreshing every 8 seconds.
|
||||
|
||||
@@ -182,6 +182,10 @@ Signal 消息以**原生格式**渲染,而非显示原始 markdown 字符。
|
||||
|
||||
以上功能无需额外配置——在近期的 signal-cli 版本中默认启用。若你的 `signal-cli` 版本过旧,Hermes 会回退到纯文本投递,并记录一次性警告日志。
|
||||
|
||||
### 长消息
|
||||
|
||||
Signal 单条消息上限为 **8,000 字符**。Hermes 会自动将更长的回复拆分为带编号的分段(`(1/3)`、`(2/3)`……)发送,而不是截断内容。此行为覆盖所有投递路径——实时对话回复、cron 任务投递、`hermes send` 以及 MCP `send_message` 调用——且原生格式(加粗、斜体、代码、剧透)在分段边界处保持不变。
|
||||
|
||||
### 正在输入指示器
|
||||
|
||||
机器人在处理消息时会发送正在输入指示器,每 8 秒刷新一次。
|
||||
|
||||
Reference in New Issue
Block a user