From 054acd5e3e4ee3d36626aa2ec41e12cb3f7bcc75 Mon Sep 17 00:00:00 2001 From: Vanhci Date: Thu, 21 May 2026 22:06:40 +0800 Subject: [PATCH 1/2] fix(weixin): skip content dedup for slash commands like /approve WeChat adapter's secondary content-fingerprint dedup was dropping intentionally-repeated slash commands (e.g. /approve sent twice). The dedup layer now skips text starting with '/', allowing commands to be processed regardless of prior dedup state. Fixes NousResearch/hermes-agent#29779 --- gateway/platforms/weixin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gateway/platforms/weixin.py b/gateway/platforms/weixin.py index 1c9fec0af7fb2..5f4e75938e7d8 100644 --- a/gateway/platforms/weixin.py +++ b/gateway/platforms/weixin.py @@ -1388,10 +1388,12 @@ async def _process_message(self, message: Dict[str, Any]) -> None: if message_id and self._dedup.is_duplicate(message_id): return - # Secondary content-fingerprint dedup for text messages + # Secondary content-fingerprint dedup for text messages. + # Commands (prefixed with /) are always processed — deduplicating them + # silently drops intentionally-repeated slash commands like /approve. item_list = message.get("item_list") or [] text = _extract_text(item_list) - if text: + if text and not text.strip().startswith("/"): content_key = f"content:{sender_id}:{hashlib.md5(text.encode()).hexdigest()}" if self._dedup.is_duplicate(content_key): logger.debug("[%s] Content-dedup: skipping duplicate message from %s", self.name, sender_id) From be668572c194c83744e8f4bd5b20f86b66c6104f Mon Sep 17 00:00:00 2001 From: vanhci Date: Sun, 24 May 2026 01:08:22 +0800 Subject: [PATCH 2/2] fix(vision): add Xiaomi MiMo 'text is not set' to image rejection phrases MiMo v2.5 supports vision but rejects multimodal requests where the text content part is missing with HTTP 400: {'error': {'code': '400', 'message': 'Param Incorrect', 'param': 'text is not set'}} Add this error pattern (and its backtick variant) to the _IMAGE_REJECTION_PHRASES list so the agent strips images from user messages and retries in text-only mode, instead of cascading into compression or context-overflow recovery. Refs: https://github.com/NousResearch/hermes-agent/issues/27344 --- agent/conversation_loop.py | 4 ++++ tests/run_agent/test_image_rejection_fallback.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index fdf65c0755877..6e0711434d33a 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -1981,6 +1981,10 @@ def _stop_spinner(): # "unknown variant `image_url`, expected `text`". "unknown variant `image_url`, expected `text`", "unknown variant image_url, expected text", + # Xiaomi MiMo: rejects requests where text content part is missing + "text is not set", + # Xiaomi MiMo variant with backticks around text + "`text` is not set", ) _err_lower = _err_body.lower() _looks_like_image_rejection = any( diff --git a/tests/run_agent/test_image_rejection_fallback.py b/tests/run_agent/test_image_rejection_fallback.py index d1d6c7ff02849..9f4648e34f630 100644 --- a/tests/run_agent/test_image_rejection_fallback.py +++ b/tests/run_agent/test_image_rejection_fallback.py @@ -195,6 +195,9 @@ class TestImageRejectionPhraseIsolation: "does not support vision", "model does not support image", "image_url'. expected", + # Xiaomi MiMo: rejects requests where text content part is missing + "text is not set", + "`text` is not set", ) def _matches(self, body: str) -> bool: @@ -265,3 +268,14 @@ def test_codex_data_url_rejection_does_not_false_match_other_url_errors(self): ] for body in bodies: assert self._matches(body) is False, f"false positive on: {body}" + + def test_mimo_text_is_not_set_trips(self): + """Xiaomi MiMo 'text is not set' — model requires text content part + but the request only has image_url parts. Should trigger image + rejection recovery (strip all images from user messages).""" + bodies = [ + "Error code: 400 - {'error': {'code': '400', 'message': 'Param Incorrect', 'param': 'text is not set', 'type': ''}}", + "{'error': {'code': '400', 'message': 'Param Incorrect', 'param': '`text` is not set', 'type': ''}}", + ] + for body in bodies: + assert self._matches(body) is True, f"false negative on: {body}"