Skip to content

feat(bitrix24): scope Open Channel mention gate to group-style connectors only#1295

Merged
clark-cant merged 1 commit into
nextlevelbuilder:devfrom
tech-synity:b24-mention-gate-whitelist
Jun 28, 2026
Merged

feat(bitrix24): scope Open Channel mention gate to group-style connectors only#1295
clark-cant merged 1 commit into
nextlevelbuilder:devfrom
tech-synity:b24-mention-gate-whitelist

Conversation

@tech-synity

Copy link
Copy Markdown
Contributor

Problem

Today every Open Channel inbound requires a bot @mention before the agent reacts:

if isOpenChannel {
    if !c.isMentionedParams(&evt.Params) {
        return  // drops EVERY untagged message
    }
}

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):

  • Internal staff (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.
  • External customer on a whitelisted connector (default: synity_zalo_personal) — still gate. Multiple customers share the chat; untagged turns would spam.
  • 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; gating drops legitimate turns.

The whitelist is overridable via BITRIX24_REQUIRE_MENTION_CONNECTORS (comma-separated codes). Default keeps synity_zalo_personal gated 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 policy
  • internal/channels/bitrix24/mention_gate_test.go (new) — pins the parser, the env-override path, and every row of the policy matrix
  • internal/channels/bitrix24/handle.go — replaces the unconditional gate with shouldRequireMentionForOpenline; adds chat_entity_id to the drop log so operators can see which connector tripped it
  • internal/channels/bitrix24/handle_test.go
    • Existing ConnectorWithoutMentionDropped test now sets ChatEntityID to the whitelisted connector — that's what the gate keys on.
    • New OneToOneConnectorWithoutMentionForwarded pins the new behavior: Facebook (not whitelisted) forwards every customer message without a mention.

Verification

go build ./... && go build -tags sqliteonly ./...
go vet ./internal/channels/bitrix24/...
go test ./internal/channels/bitrix24/...   → ok 2.684s

Surface parity

Backend-only. No API contract / web UI / CLI change. Env var follows the same operational model as the other BITRIX24_* vars already in docker-compose.yml.

[B24:2794]

…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 clark-cant left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.Once for env parsing, clean connector code extraction from pipe-delimited CHAT_ENTITY_ID
  • mention_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 to shouldRequireMentionForOpenline, adds chat_entity_id to drop log for operator visibility
  • handle_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_CONNECTORS is the right operational model — matches existing BITRIX24_* 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 clark-cant merged commit 164ea2e into nextlevelbuilder:dev Jun 28, 2026
3 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants