From 5d9e36f657f109fb3e1759bc414c9849ec0ba1bb Mon Sep 17 00:00:00 2001 From: Jerome Tabiri Date: Wed, 8 Jul 2026 03:18:03 -0400 Subject: [PATCH] fix(zulip): add _strip_html for slash command matching Zulip delivers message content as HTML (

/approve

). The gateway slash command parser expects plain text, so HTML tags prevent command matching. This helper strips HTML tags and decodes common entities (&, <, etc.). Per contract: zulip-approval-fix.prose.md Applied to: Mumuni (CT114), Tanko (CT112), Koby (CT111) --- plugins/platforms/zulip/adapter.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plugins/platforms/zulip/adapter.py b/plugins/platforms/zulip/adapter.py index a6be243..a9c6877 100644 --- a/plugins/platforms/zulip/adapter.py +++ b/plugins/platforms/zulip/adapter.py @@ -99,6 +99,23 @@ def _build_auth_header(email: str, api_key: str) -> str: return f"Basic {token}" +def _strip_html(text: str) -> str: + """Strip HTML tags and decode HTML entities from Zulip message content. + + Zulip delivers message content as rendered HTML (e.g.

/approve

). + The gateway slash command parser expects plain text, so HTML tags + prevent command matching. This helper strips tags and decodes entities. + """ + if not text: + return text + # Strip HTML tags + text = re.sub(r"<[^>]+>", "", text) + # Decode common HTML entities + text = text.replace("&", "&").replace("<", "<").replace(">", ">") + text = text.replace(""", '"').replace("'", "'").replace(" ", " ") + return text.strip() + + def _truncate(text: str, limit: int = MAX_ZULIP_MESSAGE) -> str: """Truncate to Zulip's message limit with notice.""" if len(text) <= limit: @@ -784,6 +801,8 @@ class ZulipAdapter(BasePlatformAdapter): sender_name = msg.get("sender_full_name", "Unknown") sender_id = msg.get("sender_id") content = msg.get("content", "") + # Strip Zulip HTML for slash command matching (zulip-approval-fix contract) + content = _strip_html(content) # Echo-loop prevention: skip own messages if sender_email == self._bot_email: