feat(bitrix24): scope Open Channel mention gate to group-style connectors only#1295
Merged
clark-cant merged 1 commit intoJun 28, 2026
Merged
Conversation
…tors only [B24:2794]
Before: every Open Channel message required a bot @mention before the agent
picked it up — including external-customer turns on 1-to-1 connectors
(Facebook Messenger, Zalo OA, …) where the customer has no syntactic way
to produce a mention. The gate, designed for group-style connectors
(Zalo personal pools multiple customers into one Open Channel chat),
was silently dropping every legitimate customer message on every other
connector.
This change scopes the gate by connector:
- Internal staff (IS_CONNECTOR=N) — STILL gate. Operators can @-mention
and we don't want the bot jumping into operator-to-operator chatter
in the same session.
- External customer (IS_CONNECTOR=Y) on a whitelisted connector
(default: synity_zalo_personal) — STILL gate. Multiple customers
share the chat, so untagged turns would otherwise spam them.
- External customer on any other connector (Facebook, Zalo OA, the long
tail of 1-to-1 connectors, legacy payloads without CHAT_ENTITY_ID) —
do NOT gate. Every message is addressed to the bot by construction.
The whitelist is overridable via BITRIX24_REQUIRE_MENTION_CONNECTORS
(comma-separated connector codes — the leading token of CHAT_ENTITY_ID).
Default keeps synity_zalo_personal gated so a fresh deployment is safe.
Adding a future group-style connector (e.g. a Zalo group flavour) is an
env edit + restart, not a code change.
- mention_gate.go (new):
connectorCodeFromEntityID() parses "synity_zalo_personal|20|grp|960"
into "synity_zalo_personal".
requireMentionConnectorSet() reads the env once (sync.Once cache)
and falls back to the hardcoded default {synity_zalo_personal}.
shouldRequireMentionForOpenline() is the policy matrix.
- mention_gate_test.go (new): pins the parser, the env-override path,
and every row of the policy matrix.
- handle.go: replaces the unconditional Open Channel mention gate with
a call to shouldRequireMentionForOpenline(). Adds chat_entity_id to
the drop log so operators can see which connector tripped the gate.
- handle_test.go:
Update existing "ConnectorWithoutMentionDropped" test to set
ChatEntityID to the whitelisted connector — that's what the gate now
keys on.
Add new "OneToOneConnectorWithoutMentionForwarded" test pinning the
new behavior: facebook (not whitelisted) forwards every customer
message without a mention.
Surface parity: backend-only. No API contract / web UI / CLI change.
The env var follows the same operational model as the other BITRIX24_*
vars in docker-compose.yml; documenting it there is a no-op until the
default needs overriding.
clark-cant
approved these changes
Jun 28, 2026
clark-cant
left a comment
Contributor
There was a problem hiding this comment.
Review: LGTM ✅
Code quality: Clean implementation. Well-scoped fix that replaces a blanket mention gate with a per-connector policy. The business logic is sound:
- Internal staff → always gate (they can @-mention, don't want bot barging into operator chatter)
- Group-style connectors (Zalo personal) → gate (multiple customers share one chat)
- 1-to-1 connectors (Facebook, Zalo OA) → don't gate (every message is bot-addressed by construction)
What I checked:
mention_gate.go: Correct policy matrix,sync.Oncefor env parsing, clean connector code extraction from pipe-delimitedCHAT_ENTITY_IDmention_gate_test.go: Comprehensive coverage — parser, env override, full policy matrix including edge cases (empty entity ID, whitespace, legacy payloads)handle.go: Minimal diff, correctly delegates toshouldRequireMentionForOpenline, addschat_entity_idto drop log for operator visibilityhandle_test.go: Updated existing test to set the whitelisted connector code, new test pins the 1-to-1 forward path
Design notes:
- Env override via
BITRIX24_REQUIRE_MENTION_CONNECTORSis the right operational model — matches existingBITRIX24_*vars, no code change needed for future group-style connectors - Default-to-ungated for unknown/empty entity IDs is the safe choice for the long tail of legacy payloads
- No API/UI/CLI surface change — backend-only, low-risk
CI: All green (go, web, release-versioning)
Merge state: CLEAN
Approving and merging.
clark-cant
pushed a commit
that referenced
this pull request
Jun 28, 2026
…ns [B24:2794] (#1296) Follow-up to #1295. After scoping the Open Channel mention gate by connector, customer messages on 1-to-1 connectors (Facebook Messenger, Zalo OA, …) were STILL being silently dropped — by the second mention-gate stanza further down in handleMessage, which checks the channel-level c.RequireMention() flag without distinguishing Open Channel traffic from native group chats (CRM deal/task chats). For a channel like nguyen-dao-openline configured with RequireMention=true, the flow was: 1. isOpenChannel block → shouldRequireMentionForOpenline("facebook|...") returns false (facebook isn't in the whitelist) → no drop. 2. isGroup block (isOpenChannel implies isGroup) → c.RequireMention()=true AND mentioned=false → DROP. Net effect: the gate-by-connector decision was overridden one stanza later, and every Facebook customer turn disappeared. Fix: scope the drop check inside the isGroup block to native-group chats only (!isOpenChannel). Open Channel sessions already own their mention policy in the earlier isOpenChannel block; re-applying the channel-level RequireMention flag here second-guesses that decision. Importantly, the text-processing that follows the drop check (MessageOriginal fallback, stripMention, bxConvertUserMentionsToReadable) stays unconditional, so an Open Channel STAFF turn that does come in with "[USER=<botID>]Bot[/USER]" BBCode still gets cleaned up the same way it always did. The only thing the new guard suppresses is the drop itself. Verified live: Facebook Messenger customer turn (IS_CONNECTOR=Y, CHAT_ENTITY_ID=facebook|34|…) now reaches the agent loop and the bot replies, matching the intent from #1295. Surface parity: backend-only. No API contract / web UI / CLI change. Co-authored-by: DangTinh311 <dangtinh31193@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Today every Open Channel inbound requires a bot
@mentionbefore the agent reacts:The gate was added for group-style connectors like Zalo personal, where multiple external customers share a single Bitrix Open Channel chat — without the gate, the bot would barge into customer-to-customer chatter. But the gate fires on all Open Channel traffic, including 1-to-1 connectors (Facebook Messenger, Zalo OA, …) where the customer has no syntactic way to produce a mention. Result: legitimate customer turns on those connectors are silently dropped.
Observed in this deployment: every Facebook Messenger / Zalo OA customer message disappears unless an operator first manually
@-tags the bot — which customers never do because there's no UI for it on their side.Fix
Scope the gate by connector code (the leading token of
CHAT_ENTITY_ID):IS_CONNECTOR=N) — still gate. Operators can@-mention and we don't want the bot jumping into operator-to-operator chatter inside the same session.synity_zalo_personal) — still gate. Multiple customers share the chat; untagged turns would spam.CHAT_ENTITY_ID) — do not gate. Every message is addressed to the bot by construction; gating drops legitimate turns.The whitelist is overridable via
BITRIX24_REQUIRE_MENTION_CONNECTORS(comma-separated codes). Default keepssynity_zalo_personalgated so a fresh deployment is safe by construction. A future group-style connector flips on via env + restart, no code change.Changes
internal/channels/bitrix24/mention_gate.go(new)connectorCodeFromEntityID("synity_zalo_personal|20|grp|960") → "synity_zalo_personal"requireMentionConnectorSet()reads env once (sync.Once); falls back to{synity_zalo_personal}shouldRequireMentionForOpenline(fromConnector, chatEntityID)is the policyinternal/channels/bitrix24/mention_gate_test.go(new) — pins the parser, the env-override path, and every row of the policy matrixinternal/channels/bitrix24/handle.go— replaces the unconditional gate withshouldRequireMentionForOpenline; addschat_entity_idto the drop log so operators can see which connector tripped itinternal/channels/bitrix24/handle_test.goConnectorWithoutMentionDroppedtest now setsChatEntityIDto the whitelisted connector — that's what the gate keys on.OneToOneConnectorWithoutMentionForwardedpins the new behavior: Facebook (not whitelisted) forwards every customer message without a mention.Verification
Surface parity
Backend-only. No API contract / web UI / CLI change. Env var follows the same operational model as the other
BITRIX24_*vars already indocker-compose.yml.[B24:2794]