Skip to content

feat: add externalIds support for visitor identification#39535

Open
ricardogarim wants to merge 41 commits intodevelopfrom
feat/whatsapp-usernames
Open

feat: add externalIds support for visitor identification#39535
ricardogarim wants to merge 41 commits intodevelopfrom
feat/whatsapp-usernames

Conversation

@ricardogarim
Copy link
Copy Markdown
Member

@ricardogarim ricardogarim commented Mar 11, 2026

Proposed changes (including videos or screenshots)

This PR adds support for external identifiers (externalIds) on livechat visitors, enabling WhatsApp BSUID support, username display, and progressive visitor enrichment.

Issue(s)

Steps to test or reproduce

  1. Apps-engine flow:
    • Use a WhatsApp app that calls livechatCreator.resolveVisitor({ userId: '<BSUID>', username: '@johndoe' }, { phone: '<phone>' })
    • Note: apps do NOT pass source - the bridge automatically injects the appId
    • Verify visitor is created/found with externalIds populated
  2. Progressive enrichment:
    • Create a visitor via phone only (no externalId)
    • Call resolveVisitor with the same phone but new externalId
    • Verify the visitor record is enriched with the externalId
  3. Email fallback:
    • Create a visitor via email only (no externalId)
    • Call resolveVisitor with { email: '<email>' } as contactData
    • Verify the visitor record is enriched with the externalId
  4. UI display:
    • Open a contact's info panel
    • Check the channels section displays username alongside phone
  5. Index verification:
    • Run the explain query in the collapsible section below
    • Verify winningPlan uses IXSCAN with indexName: 'externalIds.source_1_externalIds.userId_1'

Further comments

  • externalIds on Visitor, not Contact: external identifiers are channel-specific (e.g., WhatsApp BSUID), so they belong on the visitor. Contacts reference visitors via channels[].visitor.visitorId, and the UI fetches visitor data to display usernames.
  • Progressive enrichment: when a visitor exists by phone or email but not by externalId, we enrich the existing record rather than creating a duplicate. This handles the migration case where visitors were created before BSUID support.
  • ResolveVisitorContactData union type: the resolveVisitor() API accepts { phone: string } | { email: string } for flexible fallback, making it extensible for future contact data types without breaking changes.
  • Array structure with compound index: externalIds is an array of { source, userId, username? } objects using the attribute pattern. A compound multikey index on { "externalIds.source": 1, "externalIds.userId": 1 } enables efficient $elemMatch queries. This was chosen over a Record/wildcard index for ~1.5-2× smaller index size and tighter index scans.
  • App isolation: apps do NOT pass source - the bridge automatically injects the appId. This ensures each app can only access its own external identifiers. The method signature uses Omit<IVisitorExternalIdentifier, 'source'> to enforce this at the type level.
Index creation evidencemongosh localhost:3001/meteor meteor [direct: primary] meteor> db.rocketchat_livechat_visitor.find({ | externalIds: { | $elemMatch: { | source: "a1b2c3d4-e5f6-4890-8bcd-ef1234567890", | userId: "bsuid-123" | } | } | }).explain("executionStats")
{
  explainVersion: '1',
  queryPlanner: {
    namespace: 'meteor.rocketchat_livechat_visitor',
    indexFilterSet: false,
    parsedQuery: {
      externalIds: {
        '$elemMatch': {
          '$and': [
            {
              source: { '$eq': 'a1b2c3d4-e5f6-4890-8bcd-ef1234567890' }
            },
            { userId: { '$eq': 'bsuid-123' } }
          ]
        }
      }
    },
    queryHash: '5AE3527E',
    planCacheKey: '93651E13',
    maxIndexedOrSolutionsReached: false,
    maxIndexedAndSolutionsReached: false,
    maxScansToExplodeReached: false,
    winningPlan: {
      stage: 'FETCH',
      filter: {
        externalIds: {
          '$elemMatch': {
            '$and': [
              {
                source: { '$eq': 'a1b2c3d4-e5f6-4890-8bcd-ef1234567890' }
              },
              { userId: { '$eq': 'bsuid-123' } }
            ]
          }
        }
      },
      inputStage: {
        stage: 'IXSCAN',
        keyPattern: { 'externalIds.source': 1, 'externalIds.userId': 1 },
        indexName: 'externalIds.source_1_externalIds.userId_1',
        isMultiKey: true,
        multiKeyPaths: {
          'externalIds.source': [ 'externalIds' ],
          'externalIds.userId': [ 'externalIds' ]
        },
        isUnique: false,
        isSparse: false,
        isPartial: false,
        indexVersion: 2,
        direction: 'forward',
        indexBounds: {
          'externalIds.source': [
            '["a1b2c3d4-e5f6-4890-8bcd-ef1234567890", "a1b2c3d4-e5f6-4890-8bcd-ef1234567890"]'
          ],
          'externalIds.userId': [ '["bsuid-123", "bsuid-123"]' ]
        }
      }
    },
    rejectedPlans: [
      {
        stage: 'FETCH',
        filter: {
          externalIds: {
            '$elemMatch': {
              '$and': [
                { userId: { '$eq': 'bsuid-123' } },
                {
                  source: { '$eq': 'a1b2c3d4-e5f6-4890-8bcd-ef1234567890' }
                }
              ]
            }
          }
        },
        inputStage: {
          stage: 'IXSCAN',
          keyPattern: { '$_path': 1, 'externalIds.userId': 1 },
          indexName: 'externalIds.$**_1',
          isMultiKey: true,
          multiKeyPaths: { '$_path': [], 'externalIds.userId': [ 'externalIds' ] },
          isUnique: false,
          isSparse: false,
          isPartial: false,
          indexVersion: 2,
          direction: 'forward',
          indexBounds: {
            '$_path': [ '["externalIds.userId", "externalIds.userId"]' ],
            'externalIds.userId': [ '["bsuid-123", "bsuid-123"]' ]
          }
        }
      },
      {
        stage: 'FETCH',
        filter: {
          externalIds: {
            '$elemMatch': {
              '$and': [
                {
                  source: { '$eq': 'a1b2c3d4-e5f6-4890-8bcd-ef1234567890' }
                },
                { userId: { '$eq': 'bsuid-123' } }
              ]
            }
          }
        },
        inputStage: {
          stage: 'IXSCAN',
          keyPattern: { '$_path': 1, 'externalIds.source': 1 },
          indexName: 'externalIds.$**_1',
          isMultiKey: true,
          multiKeyPaths: { '$_path': [], 'externalIds.source': [ 'externalIds' ] },
          isUnique: false,
          isSparse: false,
          isPartial: false,
          indexVersion: 2,
          direction: 'forward',
          indexBounds: {
            '$_path': [ '["externalIds.source", "externalIds.source"]' ],
            'externalIds.source': [
              '["a1b2c3d4-e5f6-4890-8bcd-ef1234567890", "a1b2c3d4-e5f6-4890-8bcd-ef1234567890"]'
            ]
          }
        }
      }
    ]
  },
  executionStats: {
    executionSuccess: true,
    nReturned: 1,
    executionTimeMillis: 0,
    totalKeysExamined: 1,
    totalDocsExamined: 1,
    executionStages: {
      stage: 'FETCH',
      filter: {
        externalIds: {
          '$elemMatch': {
            '$and': [
              {
                source: { '$eq': 'a1b2c3d4-e5f6-4890-8bcd-ef1234567890' }
              },
              { userId: { '$eq': 'bsuid-123' } }
            ]
          }
        }
      },
      nReturned: 1,
      executionTimeMillisEstimate: 0,
      works: 3,
      advanced: 1,
      needTime: 0,
      needYield: 0,
      saveState: 0,
      restoreState: 0,
      isEOF: 1,
      docsExamined: 1,
      alreadyHasObj: 0,
      inputStage: {
        stage: 'IXSCAN',
        nReturned: 1,
        executionTimeMillisEstimate: 0,
        works: 2,
        advanced: 1,
        needTime: 0,
        needYield: 0,
        saveState: 0,
        restoreState: 0,
        isEOF: 1,
        keyPattern: { 'externalIds.source': 1, 'externalIds.userId': 1 },
        indexName: 'externalIds.source_1_externalIds.userId_1',
        isMultiKey: true,
        multiKeyPaths: {
          'externalIds.source': [ 'externalIds' ],
          'externalIds.userId': [ 'externalIds' ]
        },
        isUnique: false,
        isSparse: false,
        isPartial: false,
        indexVersion: 2,
        direction: 'forward',
        indexBounds: {
          'externalIds.source': [
            '["a1b2c3d4-e5f6-4890-8bcd-ef1234567890", "a1b2c3d4-e5f6-4890-8bcd-ef1234567890"]'
          ],
          'externalIds.userId': [ '["bsuid-123", "bsuid-123"]' ]
        },
        keysExamined: 1,
        seeks: 1,
        dupsTested: 1,
        dupsDropped: 0
      }
    }
  },
  command: {
    find: 'rocketchat_livechat_visitor',
    filter: {
      externalIds: {
        '$elemMatch': {
          source: 'a1b2c3d4-e5f6-4890-8bcd-ef1234567890',
          userId: 'bsuid-123'
        }
      }
    },
    '$db': 'meteor'
  },
  serverInfo: {
    host: 'Ricardos-MacBook-Pro.local',
    port: 3001,
    version: '7.0.16',
    gitVersion: '18b949444cfdaa88e30b0e10243bc18268251c1f'
  },
  serverParameters: {
    internalQueryFacetBufferSizeBytes: 104857600,
    internalQueryFacetMaxOutputDocSizeBytes: 104857600,
    internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
    internalDocumentSourceGroupMaxMemoryBytes: 104857600,
    internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
    internalQueryProhibitBlockingMergeOnMongoS: 0,
    internalQueryMaxAddToSetBytes: 104857600,
    internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600,
    internalQueryFrameworkControl: 'trySbeRestricted'
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1773875387, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1773875387, i: 1 })
}

Summary by CodeRabbit

  • New Features
    • Support external visitor identifiers across Livechat, including storing and propagating external IDs during visitor creation and lookup.
    • Resolve visitors by external identifier with optional phone/email fallback to match or enrich visitors.
    • Omnichannel UI surfaces show external identifier info alongside visitor details (improved display of username/phone).
  • Tests
    • Added unit and end-to-end tests covering external ID resolution, phone/email fallback, enrichment, overwrite, and edge cases.

@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot bot commented Mar 11, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 11, 2026

🦋 Changeset detected

Latest commit: 78df16b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 42 packages
Name Type
@rocket.chat/model-typings Minor
@rocket.chat/core-typings Minor
@rocket.chat/apps-engine Minor
@rocket.chat/omni-core Minor
@rocket.chat/models Minor
@rocket.chat/meteor Minor
@rocket.chat/apps Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/omnichannel-services Patch
rocketchat-services Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/fuselage-ui-kit Major
@rocket.chat/gazzodown Major
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/rest-typings Minor
@rocket.chat/ui-avatar Major
@rocket.chat/ui-client Major
@rocket.chat/ui-contexts Major
@rocket.chat/ui-voip Major
@rocket.chat/web-ui-registration Major
@rocket.chat/abac Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/instance-status Patch
@rocket.chat/server-fetch Patch
@rocket.chat/network-broker Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds external visitor identifier support (IVisitorExternalIdentifier and externalIds) across types, models, bridges, utilities, converters, registration, UI, and tests; implements resolveVisitor lookup (externalId first, phone/email fallback) with progressive enrichment of visitor.externalIds and permission-guarded bridge/API surfaces.

Changes

Cohort / File(s) Summary
Core Types & Exports
packages/core-typings/src/ILivechatVisitor.ts, packages/apps-engine/src/definition/livechat/IVisitor.ts, packages/apps-engine/src/definition/livechat/index.ts
Add IVisitorExternalIdentifier and ResolveVisitorContactData; expose types and add optional externalIds?: Record<string, IVisitorExternalIdentifier> on visitor types.
Models & Typings
packages/models/src/models/LivechatVisitors.ts, packages/model-typings/src/models/ILivechatVisitorsModel.ts
Add sparse index on externalIds.$**; add findOneByExternalId(source, externalUserId) and findOneVisitorByPhoneOrEmailAndAddExternalId(contactData, source, externalId) methods and their typings.
Resolve Utility
apps/meteor/app/livechat/server/lib/resolveVisitor.ts
New resolveVisitor({ source, externalId, contactData }) utility: lookup by externalId, fallback to phone/email lookup and add externalId when found; returns visitor or null.
Apps Bridge & Engine API
apps/meteor/app/apps/server/bridges/livechat.ts, packages/apps-engine/src/server/bridges/LivechatBridge.ts, packages/apps-engine/src/server/accessors/LivechatCreator.ts, packages/apps-engine/src/definition/accessors/ILivechatCreator.ts, packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
Add protected/abstract resolveVisitor and public doResolveVisitor (permission-guarded); LivechatCreator exposes resolveVisitor; bridge implementation delegates to server resolver and carries externalIds through visitor creation paths; test bridge updated.
Converters & Visitor Creation
apps/meteor/app/apps/server/converters/visitors.js, packages/omni-core/src/visitor/create.ts, apps/meteor/app/apps/server/bridges/livechat.ts
Converters and registration accept and propagate externalIds; registerGuest and guest upsert logic merge externalIds using dot-notation; onboarding flow includes externalIds where available.
Client UI
apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx, apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx
UI reads externalIds (and phone) to compute channel labels and short display names.
Tests (unit & e2e)
apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts, apps/meteor/tests/end-to-end/apps/app-resolve-visitor.ts, packages/omni-core/src/visitor/create.spec.ts
Add unit tests for resolver behaviors (externalId lookup, phone/email fallback, enrichment) and end-to-end tests for the resolve-visitor API; add guest-create test for dot-notation externalIds.
Misc & Packaging
apps/meteor/tests/data/apps/app-packages/index.ts, .changeset/forty-dolphins-check.md, package.json
Add test app package path export and changeset manifest updates for package bumps.

Sequence Diagram(s)

sequenceDiagram
    participant App as External App
    participant Bridge as LivechatBridge
    participant Resolver as resolveVisitor
    participant DB as LivechatVisitors

    App->>Bridge: doResolveVisitor(externalId, contactData?)
    Bridge->>Bridge: verify livechat-visitor permission
    Bridge->>Resolver: resolveVisitor({ source, externalId, contactData })

    Resolver->>DB: findOneByExternalId(source, externalId.userId)
    alt found by external ID
        DB-->>Resolver: visitor
        Resolver-->>Bridge: return visitor
    else not found & contactData provided
        Resolver->>DB: findOneVisitorByPhoneOrEmailAndAddExternalId(contactData, source, externalId)
        alt found by contact
            DB-->>Resolver: visitor (updated with externalId)
            Resolver-->>Bridge: return enriched visitor
        else no match
            Resolver-->>Bridge: return null
        end
    else not found & no contactData
        Resolver-->>Bridge: return null
    end

    Bridge-->>App: IVisitor | undefined
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: add externalIds support for visitor identification' clearly and concisely summarizes the main feature being added - support for external identifiers on visitors for identification purposes.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from linked issues: IVisitorExternalIdentifier and ILivechatVisitor extensions for external IDs CORE-1835, sparse index on externalIds CORE-1835, resolveVisitor() with externalId lookup and phone fallback [CORE-1818, CORE-1835], progressive enrichment CORE-1835, and findOneByExternalId/findOneVisitorByPhoneOrEmailAndAddExternalId methods CORE-1835.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issues' objectives: extending visitor schema with externalIds, implementing resolveVisitor logic, adding database methods, updating apps-engine APIs, and including UI enhancements for displaying external identifiers (usernames).
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ricardogarim
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 11, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai bot added the type: feature Pull requests that introduces new feature label Mar 11, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 11, 2026

Codecov Report

❌ Patch coverage is 33.33333% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.22%. Comparing base (44938fd) to head (78df16b).
⚠️ Report is 52 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #39535      +/-   ##
===========================================
- Coverage    70.55%   70.22%   -0.33%     
===========================================
  Files         3271     3280       +9     
  Lines       116782   116819      +37     
  Branches     21090    20735     -355     
===========================================
- Hits         82393    82039     -354     
+ Misses       32338    31502     -836     
- Partials      2051     3278    +1227     
Flag Coverage Δ
e2e 59.73% <20.00%> (-0.79%) ⬇️
e2e-api 47.42% <ø> (-0.71%) ⬇️
unit 71.03% <100.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/omni-core/src/visitor/create.ts (1)

28-33: ⚠️ Potential issue | 🟠 Major

Handle externalIds additively to avoid erasing previously linked identifiers.

Line 33 spreads incoming externalIds directly into the update object passed to updateOneByIdOrToken(). At line 327 of LivechatVisitors.ts, this method uses { $set: update }, which fully replaces the field instead of merging. On subsequent visitor registrations from another channel, any existing external IDs will be lost.

The codebase already provides addExternalId() (LivechatVisitors.ts:62–70) which uses $setUnion for additive writes, and resolveVisitor.ts demonstrates the correct pattern at lines 18–19. Apply the same approach here: either call addExternalId() separately after confirming the visitor match, or merge against the stored array before the upsert.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/omni-core/src/visitor/create.ts` around lines 28 - 33, The current
update builds visitorDataToUpdate and spreads externalIds directly which causes
updateOneByIdOrToken (which uses {$set: update}) to overwrite the stored
externalIds; instead handle externalIds additively by either calling
LivechatVisitors.addExternalId(visitorIdOrToken, externalId) for each new
externalId after creating/updating the visitor, or merge with the existing
visitor.externalIds before calling updateOneByIdOrToken (e.g., compute a union
of existing and incoming externalIds and set that union into
visitorDataToUpdate); locate and modify the logic around visitorDataToUpdate and
the subsequent call to updateOneByIdOrToken to use addExternalId or a union
merge to avoid erasing previously linked identifiers.
🧹 Nitpick comments (1)
apps/meteor/app/livechat/server/lib/resolveVisitor.ts (1)

18-18: Remove the inline implementation comment.

The helper name and addExternalId() call already explain the enrichment step.

As per coding guidelines: "Avoid code comments in the implementation".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/livechat/server/lib/resolveVisitor.ts` at line 18, Remove the
inline implementation comment "// Enrich existing visitor with external ID
(progressive enrichment)" that sits above the call to addExternalId() in
resolveVisitor.ts — the helper name and addExternalId() already convey the
intent; simply delete the comment (and tidy surrounding whitespace) so the code
follows the "avoid code comments in the implementation" guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/meteor/app/apps/server/bridges/livechat.ts`:
- Line 239: The deprecated createVisitor() path builds registerData without
copying visitor.externalIds so visitors created there cannot be resolved by
external ID; update createVisitor() to mirror externalIds into the registerData
object (same shape used by createAndReturnVisitor()) or explicitly reject/ignore
externalIds in that deprecated API. Locate createVisitor(), modify the
registerData construction to include ...(visitor.externalIds?.length && {
externalIds: visitor.externalIds }) so externalIds are passed through (or add
validation that throws when externalIds are provided) to ensure consistency with
createAndReturnVisitor().

In `@apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx`:
- Around line 40-46: The shortName is currently built from visitor.username
(from data.visitor.username) which for WhatsApp is the internal livechat
username; change shortName (and the similar occurrence around line 53) to prefer
the external-id username from visitor.externalIds (find externalIds[].username)
when present, falling back to visitor.username or phoneNumber as before; update
the logic that computes shortName in ContactField to mirror
ContactInfoChannelsItem by checking visitor.externalIds for a username first,
then using visitor.username or phone as fallback.

In `@packages/apps-engine/src/server/bridges/LivechatBridge.ts`:
- Around line 98-102: doResolveVisitor currently calls resolveVisitor when the
caller has only livechat-visitor.read, but resolveVisitor can call
LivechatVisitors.addExternalId (a mutating enrichment); change doResolveVisitor
to prevent mutation by either (A) gating the enrichment path behind
livechat-visitor.write—i.e., only call the code path that may call
LivechatVisitors.addExternalId if hasWritePermission('livechat-visitor') is
true—or (B) split the behavior into a pure lookup method and a separate
enrichment method so doResolveVisitor uses the pure lookup when only
hasReadPermission and only invokes the enrichment method (which calls
resolveVisitor/addExternalId) when hasWritePermission; update checks around
doResolveVisitor, resolveVisitor and references to
LivechatVisitors.addExternalId accordingly.

In `@packages/models/src/models/LivechatVisitors.ts`:
- Around line 53-69: The current findOneByExternalId and addExternalId treat
array element matches and deduplication incorrectly; change findOneByExternalId
to query using an $elemMatch on externalIds (e.g. { externalIds: { $elemMatch: {
source: source, userId: externalUserId } } }) so both fields are matched on the
same array element, and change addExternalId to an update pipeline that
deduplicates externalIds by the (source,userId) key rather than by full object
equality—compute a key (e.g. concat source + '|' + userId) for each array
element (including the incoming externalId), use $reduce/$filter to build an
array that keeps only the first element per key (or merges fields if desired),
and $set externalIds to that deduplicated array so duplicates differing only by
extra fields (like username) are collapsed by source+userId.

---

Outside diff comments:
In `@packages/omni-core/src/visitor/create.ts`:
- Around line 28-33: The current update builds visitorDataToUpdate and spreads
externalIds directly which causes updateOneByIdOrToken (which uses {$set:
update}) to overwrite the stored externalIds; instead handle externalIds
additively by either calling LivechatVisitors.addExternalId(visitorIdOrToken,
externalId) for each new externalId after creating/updating the visitor, or
merge with the existing visitor.externalIds before calling updateOneByIdOrToken
(e.g., compute a union of existing and incoming externalIds and set that union
into visitorDataToUpdate); locate and modify the logic around
visitorDataToUpdate and the subsequent call to updateOneByIdOrToken to use
addExternalId or a union merge to avoid erasing previously linked identifiers.

---

Nitpick comments:
In `@apps/meteor/app/livechat/server/lib/resolveVisitor.ts`:
- Line 18: Remove the inline implementation comment "// Enrich existing visitor
with external ID (progressive enrichment)" that sits above the call to
addExternalId() in resolveVisitor.ts — the helper name and addExternalId()
already convey the intent; simply delete the comment (and tidy surrounding
whitespace) so the code follows the "avoid code comments in the implementation"
guideline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b07474a9-2220-4d0a-aa27-524a46dcc03c

📥 Commits

Reviewing files that changed from the base of the PR and between b29fb86 and e4b108a.

📒 Files selected for processing (16)
  • apps/meteor/app/apps/server/bridges/livechat.ts
  • apps/meteor/app/apps/server/converters/visitors.js
  • apps/meteor/app/livechat/server/lib/resolveVisitor.ts
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx
  • apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx
  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
  • packages/apps-engine/src/definition/accessors/ILivechatCreator.ts
  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • packages/apps-engine/src/definition/livechat/index.ts
  • packages/apps-engine/src/server/accessors/LivechatCreator.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
  • packages/models/src/models/LivechatVisitors.ts
  • packages/omni-core/src/visitor/create.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: 📦 Build Packages
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • packages/apps-engine/src/server/accessors/LivechatCreator.ts
  • apps/meteor/app/apps/server/converters/visitors.js
  • apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx
  • packages/apps-engine/src/definition/accessors/ILivechatCreator.ts
  • apps/meteor/app/livechat/server/lib/resolveVisitor.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/omni-core/src/visitor/create.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • packages/apps-engine/src/definition/livechat/index.ts
  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
**/*.spec.ts

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.spec.ts: Use descriptive test names that clearly communicate expected behavior in Playwright tests
Use .spec.ts extension for test files (e.g., login.spec.ts)

Files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
🧠 Learnings (21)
📓 Common learnings
Learnt from: ggazzo
Repo: RocketChat/Rocket.Chat PR: 35995
File: apps/meteor/app/api/server/v1/rooms.ts:1107-1112
Timestamp: 2026-02-23T17:53:18.785Z
Learning: In Rocket.Chat PR reviews, maintain strict scope boundaries—when a PR is focused on a specific endpoint (e.g., rooms.favorite), avoid reviewing or suggesting changes to other endpoints that were incidentally refactored (e.g., rooms.invite) unless explicitly requested by maintainers.
📚 Learning: 2025-10-28T16:53:42.761Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.

Applied to files:

  • packages/apps-engine/src/server/accessors/LivechatCreator.ts
  • packages/apps-engine/src/definition/accessors/ILivechatCreator.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings (mapping subscription documents to room IDs), never undefined, even when user has no room subscriptions.

Applied to files:

  • packages/apps-engine/src/server/accessors/LivechatCreator.ts
  • packages/apps-engine/src/definition/accessors/ILivechatCreator.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings by mapping subscription documents to room IDs, never undefined, even when user has no room subscriptions.

Applied to files:

  • packages/apps-engine/src/server/accessors/LivechatCreator.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • packages/apps-engine/src/server/accessors/LivechatCreator.ts
  • packages/apps-engine/src/definition/accessors/ILivechatCreator.ts
  • apps/meteor/app/livechat/server/lib/resolveVisitor.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/omni-core/src/visitor/create.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • packages/apps-engine/src/definition/livechat/index.ts
  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • packages/apps-engine/src/server/accessors/LivechatCreator.ts
  • packages/apps-engine/src/definition/accessors/ILivechatCreator.ts
  • apps/meteor/app/livechat/server/lib/resolveVisitor.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/omni-core/src/visitor/create.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • packages/apps-engine/src/definition/livechat/index.ts
  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2025-12-18T15:18:31.688Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37773
File: apps/meteor/client/views/mediaCallHistory/MediaCallHistoryInternal.tsx:24-34
Timestamp: 2025-12-18T15:18:31.688Z
Learning: In apps/meteor/client/views/mediaCallHistory/MediaCallHistoryInternal.tsx, for internal call history items, the item.contactId is guaranteed to always match either the caller.id or callee.id in the call data, so the contact resolution in getContact will never result in undefined.

Applied to files:

  • apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx
  • apps/meteor/app/livechat/server/lib/resolveVisitor.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
📚 Learning: 2026-02-25T20:10:16.987Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38913
File: packages/ddp-client/src/legacy/types/SDKLegacy.ts:34-34
Timestamp: 2026-02-25T20:10:16.987Z
Learning: In the RocketChat/Rocket.Chat monorepo, packages/ddp-client and apps/meteor do not use TypeScript project references. Module augmentations in apps/meteor (e.g., declare module 'rocket.chat/rest-typings') are not visible when compiling packages/ddp-client in isolation, which is why legacy SDK methods that depend on OperationResult types for OpenAPI-migrated endpoints must remain commented out.

Applied to files:

  • packages/omni-core/src/visitor/create.ts
  • packages/models/src/models/LivechatVisitors.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: UserBridge.doGetUserRoomIds in packages/apps-engine/src/server/bridges/UserBridge.ts has a bug where it implicitly returns undefined when the app lacks read permission (missing return statement in the else case of the permission check).

Applied to files:

  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Utilize Playwright fixtures (`test`, `page`, `expect`) for consistency in test files

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure tests run reliably in parallel without shared state conflicts

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Maintain test isolation between test cases in Playwright tests

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2025-12-10T21:00:54.909Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 37091
File: ee/packages/abac/jest.config.ts:4-7
Timestamp: 2025-12-10T21:00:54.909Z
Learning: Rocket.Chat monorepo: Jest testMatch pattern '<rootDir>/src/**/*.spec.(ts|js|mjs)' is valid in this repo and used across multiple packages (e.g., packages/tools, ee/packages/omnichannel-services). Do not flag it as invalid in future reviews.

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `expect` matchers for assertions (`toEqual`, `toContain`, `toBeTruthy`, `toHaveLength`, etc.) instead of `assert` statements in Playwright tests

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Group related tests in the same file

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.{ts,spec.ts} : Follow Page Object Model pattern consistently in Playwright tests

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/page-objects/**/*.ts : Utilize existing page objects pattern from `apps/meteor/tests/e2e/page-objects/`

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `test.step()` for complex test scenarios to improve organization in Playwright tests

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Prefer web-first assertions (`toBeVisible`, `toHaveText`, etc.) in Playwright tests

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2026-02-24T19:22:48.358Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 38493
File: apps/meteor/tests/e2e/omnichannel/omnichannel-send-pdf-transcript.spec.ts:66-67
Timestamp: 2026-02-24T19:22:48.358Z
Learning: In Playwright end-to-end tests (e.g., under apps/meteor/tests/e2e/...), prefer locating elements by translated text (getByText) and ARIA roles (getByRole) over data-qa attributes. If translation values change, update the corresponding test locators accordingly. Never use data-qa locators. This guideline applies to all Playwright e2e test specs in the repository and helps keep tests robust to UI text changes and accessible semantics.

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
📚 Learning: 2026-03-06T18:10:15.268Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/code/CodeBlock.spec.tsx:47-68
Timestamp: 2026-03-06T18:10:15.268Z
Learning: In tests (especially those using testing-library/dom/jsdom) for Rocket.Chat components, the HTML <code> element has an implicit ARIA role of 'code'. Therefore, screen.getByRole('code') or screen.findByRole('code') will locate <code> elements even without a role attribute. Do not flag findByRole('code') as invalid in reviews; prefer using the implicit role instead of adding role="code" unless necessary for accessibility.

Applied to files:

  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
🔇 Additional comments (9)
apps/meteor/app/apps/server/converters/visitors.js (2)

40-40: LGTM!

The mapping addition follows the established pattern used for other visitor fields.


61-61: LGTM!

The conditional spread follows the same pattern used for visitorEmails and department, maintaining consistency in how optional fields are handled.

apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx (1)

33-46: Nice label fallback for channel rows.

Preferring externalIds[].username here keeps the contact-channel view aligned with the new visitor model while still falling back cleanly to the existing source label.

Also applies to: 113-113

packages/core-typings/src/ILivechatVisitor.ts (1)

17-21: Type shape matches the new persistence contract.

IVisitorExternalIdentifier plus externalIds on ILivechatVisitor lines up with the source/userId lookup model and gives the UI a typed place to read the external username from.

Also applies to: 35-35

packages/apps-engine/src/definition/livechat/index.ts (1)

19-24: Good barrel export update.

Re-exporting IVisitorExternalIdentifier here keeps the apps-engine public surface consistent with the new visitor contract.

packages/apps-engine/src/definition/livechat/IVisitor.ts (1)

4-8: Apps-engine visitor typing stays aligned with core.

Adding IVisitorExternalIdentifier and externalIds here gives apps a typed way to participate in the new resolve/enrichment flow without extra translation.

Also applies to: 23-23

packages/apps-engine/src/server/accessors/LivechatCreator.ts (1)

6-18: Thin delegation is the right shape here.

resolveVisitor() forwards the external id, optional phone, and app context without adding local policy, which keeps this accessor consistent with the rest of the bridge-backed API.

packages/apps-engine/tests/test-data/bridges/livechatBridge.ts (1)

2-2: Test bridge stays in sync with the abstract surface.

Adding the resolveVisitor() stub here avoids test-only bridge drift now that the production bridge exposes the new resolve path.

Also applies to: 65-67

packages/omni-core/src/visitor/create.ts (1)

1-20: Good entry-point plumbing for externalIds.

Accepting externalIds in RegisterGuestType and carrying it into the registration flow is the right place to hook the new visitor identifier data in.

@ricardogarim
Copy link
Copy Markdown
Member Author

ricardogarim commented Mar 12, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/apps-engine/src/server/bridges/LivechatBridge.ts (1)

204-205: Consider adding JSDoc to clarify the write permission requirement.

A brief JSDoc comment would help future maintainers understand why this method requires write permission (due to potential enrichment via addExternalId), similar to the deprecation notes on other methods.

📝 Suggested documentation
+	/**
+	 * Resolves a visitor by external identifier, falling back to phone lookup.
+	 * Requires write permission as it may enrich the visitor record with the external ID.
+	 */
 	protected abstract resolveVisitor(externalId: IVisitorExternalIdentifier, phone: string | undefined, appId: string): Promise<IVisitor | undefined>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/apps-engine/src/server/bridges/LivechatBridge.ts` around lines 204 -
205, Add a brief JSDoc comment above the abstract method
resolveVisitor(externalId: IVisitorExternalIdentifier, phone: string |
undefined, appId: string): Promise<IVisitor | undefined> explaining that the
method requires write permission because implementations may call addExternalId
to enrich or persist visitor data; note the intent and any
deprecation/permission guidance similar to other methods so future maintainers
understand why write access is needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/apps-engine/src/server/bridges/LivechatBridge.ts`:
- Around line 204-205: Add a brief JSDoc comment above the abstract method
resolveVisitor(externalId: IVisitorExternalIdentifier, phone: string |
undefined, appId: string): Promise<IVisitor | undefined> explaining that the
method requires write permission because implementations may call addExternalId
to enrich or persist visitor data; note the intent and any
deprecation/permission guidance similar to other methods so future maintainers
understand why write access is needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: be917816-6ac6-46b5-8e06-bdac3e2145e2

📥 Commits

Reviewing files that changed from the base of the PR and between e4b108a and 52cd3a9.

📒 Files selected for processing (11)
  • apps/meteor/app/apps/server/bridges/livechat.ts
  • apps/meteor/app/livechat/server/lib/resolveVisitor.ts
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx
  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
  • packages/models/src/models/LivechatVisitors.ts
  • packages/omni-core/src/visitor/create.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/meteor/tests/unit/app/livechat/server/lib/resolveVisitor.spec.ts
  • apps/meteor/app/livechat/server/lib/resolveVisitor.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx
  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
  • packages/omni-core/src/visitor/create.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
🧠 Learnings (23)
📓 Common learnings
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/app/apps/server/bridges/livechat.ts:249-249
Timestamp: 2026-03-11T16:46:50.707Z
Learning: In `apps/meteor/app/apps/server/bridges/livechat.ts`, `createVisitor()` intentionally does not propagate `externalIds` to `registerData`. This is by design: the method is deprecated (JSDoc: `deprecated Use createAndReturnVisitor instead. Note: This method does not support externalIds.`) to push callers toward `createAndReturnVisitor()`, which does support `externalIds`. Do not flag the missing `externalIds` field in `createVisitor()` as a bug.
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx:40-46
Timestamp: 2026-03-11T15:56:13.132Z
Learning: In `apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx`, `shortName` is intentionally built from the visitor's internal `username` and `phone` (not from `externalIds`). `ContactField` shows the visitor's internal system identifier. Per-channel external identifiers (e.g. WhatsApp usernames from `externalIds[].username`) are only displayed in `ContactInfoChannelsItem`, which serves a different UI context.
📚 Learning: 2026-03-11T16:46:50.707Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/app/apps/server/bridges/livechat.ts:249-249
Timestamp: 2026-03-11T16:46:50.707Z
Learning: In `apps/meteor/app/apps/server/bridges/livechat.ts`, `createVisitor()` intentionally does not propagate `externalIds` to `registerData`. This is by design: the method is deprecated (JSDoc: `deprecated Use createAndReturnVisitor instead. Note: This method does not support externalIds.`) to push callers toward `createAndReturnVisitor()`, which does support `externalIds`. Do not flag the missing `externalIds` field in `createVisitor()` as a bug.

Applied to files:

  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx
  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
  • packages/omni-core/src/visitor/create.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
📚 Learning: 2026-03-11T15:56:13.132Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx:40-46
Timestamp: 2026-03-11T15:56:13.132Z
Learning: In `apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx`, `shortName` is intentionally built from the visitor's internal `username` and `phone` (not from `externalIds`). `ContactField` shows the visitor's internal system identifier. Per-channel external identifiers (e.g. WhatsApp usernames from `externalIds[].username`) are only displayed in `ContactInfoChannelsItem`, which serves a different UI context.

Applied to files:

  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx
  • packages/models/src/models/LivechatVisitors.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
  • packages/omni-core/src/visitor/create.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
  • packages/omni-core/src/visitor/create.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • packages/apps-engine/src/definition/livechat/IVisitor.ts
  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
  • packages/omni-core/src/visitor/create.ts
  • packages/core-typings/src/ILivechatVisitor.ts
  • packages/model-typings/src/models/ILivechatVisitorsModel.ts
📚 Learning: 2025-12-18T15:18:31.688Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37773
File: apps/meteor/client/views/mediaCallHistory/MediaCallHistoryInternal.tsx:24-34
Timestamp: 2025-12-18T15:18:31.688Z
Learning: In apps/meteor/client/views/mediaCallHistory/MediaCallHistoryInternal.tsx, for internal call history items, the item.contactId is guaranteed to always match either the caller.id or callee.id in the call data, so the contact resolution in getContact will never result in undefined.

Applied to files:

  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
📚 Learning: 2026-03-11T22:04:16.136Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 39545
File: apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts:59-61
Timestamp: 2026-03-11T22:04:16.136Z
Learning: In `apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts`, the `msg.u._id === uid` early-return in the `streamNewMessage` handler is intentional: the "New messages" indicator is designed to notify about messages from other users only. Self-sent messages — including those sent from a different session/device — are always skipped, by design. Do not flag this as a multi-session regression.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2026-01-17T01:51:47.764Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2026-03-09T18:39:21.178Z
Learnt from: Harxhit
Repo: RocketChat/Rocket.Chat PR: 39476
File: apps/meteor/server/methods/addAllUserToRoom.ts:0-0
Timestamp: 2026-03-09T18:39:21.178Z
Learning: In apps/meteor/server/methods/addAllUserToRoom.ts, the implementation uses a single cursor pass (Users.find(userFilter).batchSize(100)) that collects both the full user objects (collectedUsers: IUser[]) and their usernames (usernames: string[]) in one iteration. `beforeAddUserToRoom` is then called once with the full usernames batch (preserving batch-validation semantics), and the subsequent subscription/message processing loop iterates over the same stable `collectedUsers` array — no second DB query is made. This avoids any race condition between validation and processing while preserving the original batch-validation behavior.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
📚 Learning: 2026-03-09T21:20:12.687Z
Learnt from: pierre-lehnen-rc
Repo: RocketChat/Rocket.Chat PR: 39386
File: apps/meteor/server/services/push/tokenManagement/findDocumentToUpdate.ts:12-15
Timestamp: 2026-03-09T21:20:12.687Z
Learning: In `apps/meteor/server/services/push/tokenManagement/findDocumentToUpdate.ts`, the early return `if (data.voipToken) return null` (Lines 13-15) is intentionally correct. VoIP token updates always include an `_id`, so they are handled by the `_id` lookup block above (Lines 5-9) and never reach this guard. The guard is only a safety net for edge cases where `_id` is absent or no document was found, preventing an incorrect `token + appName` fallback match for VoIP-only payloads.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
📚 Learning: 2026-02-23T17:53:18.785Z
Learnt from: ggazzo
Repo: RocketChat/Rocket.Chat PR: 35995
File: apps/meteor/app/api/server/v1/rooms.ts:1107-1112
Timestamp: 2026-02-23T17:53:18.785Z
Learning: In Rocket.Chat PR reviews, maintain strict scope boundaries—when a PR is focused on a specific endpoint (e.g., rooms.favorite), avoid reviewing or suggesting changes to other endpoints that were incidentally refactored (e.g., rooms.invite) unless explicitly requested by maintainers.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • packages/core-typings/src/ILivechatVisitor.ts
📚 Learning: 2026-02-24T19:09:09.561Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:09.561Z
Learning: In RocketChat/Rocket.Chat OpenAPI migration PRs for apps/meteor/app/api/server/v1 endpoints, maintainers prefer to avoid any logic changes; style-only cleanups (like removing inline comments) may be deferred to follow-ups to keep scope tight.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2026-03-10T08:13:52.153Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 39414
File: apps/meteor/app/api/server/v1/rooms.ts:1241-1297
Timestamp: 2026-03-10T08:13:52.153Z
Learning: In the RocketChat/Rocket.Chat OpenAPI migration PRs for endpoints under apps/meteor/app/api/server/v1/rooms.ts, the pattern `ajv.compile<void>({...})` is intentionally used for the 200 response schema even when the endpoint returns `{ success: true }`. This is an established convention across all migrated endpoints (rooms.leave, rooms.favorite, rooms.delete, rooms.muteUser, rooms.unmuteUser). Do not flag this as a type mismatch during reviews of these migration PRs.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2025-11-10T19:06:20.146Z
Learnt from: MartinSchoeler
Repo: RocketChat/Rocket.Chat PR: 37408
File: apps/meteor/client/views/admin/ABAC/useRoomAttributeOptions.tsx:53-69
Timestamp: 2025-11-10T19:06:20.146Z
Learning: In the Rocket.Chat repository, do not provide suggestions or recommendations about code sections marked with TODO comments. The maintainers have already identified these as future work and external reviewers lack the full context about implementation plans and timing.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
  • packages/core-typings/src/ILivechatVisitor.ts
📚 Learning: 2026-03-06T18:10:23.330Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/code/CodeBlock.spec.tsx:47-68
Timestamp: 2026-03-06T18:10:23.330Z
Learning: In the RocketChat/Rocket.Chat `packages/gazzodown` package and more broadly, the HTML `<code>` element has an implicit ARIA role of `code` per WAI-ARIA 1.3, and `testing-library/dom` / jsdom supports it. Therefore, `screen.getByRole('code')` / `screen.findByRole('code')` correctly locates `<code>` elements without needing an explicit `role="code"` attribute. Do NOT flag `findByRole('code')` as invalid in future reviews.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2026-02-25T20:10:16.987Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38913
File: packages/ddp-client/src/legacy/types/SDKLegacy.ts:34-34
Timestamp: 2026-02-25T20:10:16.987Z
Learning: In the RocketChat/Rocket.Chat monorepo, packages/ddp-client and apps/meteor do not use TypeScript project references. Module augmentations in apps/meteor (e.g., declare module 'rocket.chat/rest-typings') are not visible when compiling packages/ddp-client in isolation, which is why legacy SDK methods that depend on OperationResult types for OpenAPI-migrated endpoints must remain commented out.

Applied to files:

  • packages/models/src/models/LivechatVisitors.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
  • packages/omni-core/src/visitor/create.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: UserBridge.doGetUserRoomIds in packages/apps-engine/src/server/bridges/UserBridge.ts has a bug where it implicitly returns undefined when the app lacks read permission (missing return statement in the else case of the permission check).

Applied to files:

  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings (mapping subscription documents to room IDs), never undefined, even when user has no room subscriptions.

Applied to files:

  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings by mapping subscription documents to room IDs, never undefined, even when user has no room subscriptions.

Applied to files:

  • packages/apps-engine/src/server/bridges/LivechatBridge.ts
📚 Learning: 2025-10-28T16:53:42.761Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.

Applied to files:

  • packages/apps-engine/tests/test-data/bridges/livechatBridge.ts
  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2026-03-04T14:16:49.202Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39304
File: packages/ui-contexts/src/ActionManagerContext.ts:26-26
Timestamp: 2026-03-04T14:16:49.202Z
Learning: In `packages/ui-contexts/src/ActionManagerContext.ts` (TypeScript, RocketChat/Rocket.Chat), the `disposeView` method in `IActionManager` uses an intentionally explicit union `UiKit.ModalView['id'] | UiKit.BannerView['viewId'] | UiKit.ContextualBarView['id']` to document which view types are accepted, even though all constituents resolve to the same primitive. The inline `// eslint-disable-next-line typescript-eslint/no-duplicate-type-constituents` comment is intentional and should not be flagged or removed.

Applied to files:

  • apps/meteor/app/apps/server/bridges/livechat.ts
📚 Learning: 2025-11-19T12:32:29.696Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 37547
File: packages/i18n/src/locales/en.i18n.json:634-634
Timestamp: 2025-11-19T12:32:29.696Z
Learning: Repo: RocketChat/Rocket.Chat
Context: i18n workflow
Learning: In this repository, new translation keys should be added to packages/i18n/src/locales/en.i18n.json only; other locale files are populated via the external translation pipeline and/or fall back to English. Do not request adding the same key to all locale files in future reviews.

Applied to files:

  • apps/meteor/app/apps/server/bridges/livechat.ts
  • packages/core-typings/src/ILivechatVisitor.ts
📚 Learning: 2026-02-24T19:05:56.710Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 0
File: :0-0
Timestamp: 2026-02-24T19:05:56.710Z
Learning: Rocket.Chat repo context: When a workspace manifest on develop already pins a dependency version (e.g., packages/web-ui-registration → "rocket.chat/ui-contexts": "27.0.1"), a lockfile change in a feature PR that upgrades only that dependency’s resolution is considered a manifest-driven sync and can be kept, preferably as a small "chore: sync yarn.lock with manifests" commit.

Applied to files:

  • packages/core-typings/src/ILivechatVisitor.ts
🔇 Additional comments (14)
apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx (3)

13-13: LGTM!

The useVisitorInfo hook is appropriately imported and used to fetch visitor data for accessing externalIds. React Query's caching will handle repeated renders efficiently.

Also applies to: 33-34


35-44: LGTM!

The channelLabel computation correctly:

  • Safely looks up the external identifier using details.id as the key into the externalIds Record
  • Handles all optional values with proper chaining (details?.id, visitorData?.externalIds?.[...], externalId?.username)
  • Combines username and phone when both exist, or falls back to whichever is available
  • Has accurate dependency array for useMemo

This aligns with the retrieved learning that per-channel external identifiers (e.g., WhatsApp usernames) are displayed specifically in ContactInfoChannelsItem.


111-111: LGTM!

Rendering channelLabel correctly displays the combined username and phone (or fallback) in the channel item.

packages/apps-engine/src/definition/livechat/IVisitor.ts (1)

4-7: LGTM!

The IVisitorExternalIdentifier interface is well-defined with userId as the required identifier and optional username. The Record<string, IVisitorExternalIdentifier> structure for externalIds is consistent with the core-typings definition and enables keying by source (e.g., appId).

Also applies to: 22-22

packages/core-typings/src/ILivechatVisitor.ts (1)

17-20: LGTM!

The IVisitorExternalIdentifier and externalIds property are correctly defined, matching the apps-engine definition. The Record<string, IVisitorExternalIdentifier> structure enforces one external identifier per source, which aligns with the design decision from PR discussions.

Also applies to: 34-34

packages/apps-engine/tests/test-data/bridges/livechatBridge.ts (1)

72-74: LGTM!

The resolveVisitor stub follows the established pattern of other methods in the test bridge, correctly implementing the abstract method signature.

packages/model-typings/src/models/ILivechatVisitorsModel.ts (1)

53-59: LGTM!

The new method signatures are well-defined and align with the model implementation. Return types correctly use Promise<ILivechatVisitor | null>.

apps/meteor/app/apps/server/bridges/livechat.ts (2)

350-364: LGTM!

The resolveVisitor implementation correctly:

  1. Logs the operation with appId
  2. Delegates to the utility function with appId as the source
  3. Converts the result using the established visitor converter pattern

The pattern of passing potentially-null results to convertVisitor is consistent with other finder methods in this file (e.g., findVisitorByEmail, findVisitorByToken).


208-211: LGTM!

The deprecation notice on createVisitor() correctly documents that it does not support externalIds, guiding callers to use createAndReturnVisitor() which properly propagates externalIds at line 249.

Also applies to: 249-249

packages/omni-core/src/visitor/create.ts (1)

14-14: LGTM!

The externalIds parameter flows correctly through the registration process. The spread pattern safely includes externalIds only when provided, and the upsert behavior is appropriate for the visitor creation flow.

Also applies to: 19-19, 33-33

packages/apps-engine/src/server/bridges/LivechatBridge.ts (1)

98-102: LGTM!

The doResolveVisitor method correctly gates behind livechat-visitor.write permission, which is appropriate since the underlying resolution can enrich visitor records via addExternalId. This was addressed in prior review discussions.

packages/models/src/models/LivechatVisitors.ts (3)

34-34: LGTM!

The sparse wildcard index on externalIds.$** correctly covers queries on dynamic nested paths like externalIds.${source}.userId. The sparse option ensures visitors without externalIds don't bloat the index.


53-69: LGTM!

Both methods are correctly implemented:

  • findOneVisitorByPhoneAndAddExternalId: Atomically finds by phone and sets the external identifier at the source-keyed path, returning the updated document.
  • findOneByExternalId: Queries using the dynamic nested path which is covered by the wildcard index.

The Record<string, IVisitorExternalIdentifier> structure ensures one external ID per source, avoiding the deduplication issues from the previous array-based design.


53-63: External ID overwriting on phone-based lookup is intentional design.

The test case "should update existing externalIds when visitor already has some" explicitly validates that unconditional overwriting of externalIds.${source} during phone-based fallback is expected behavior. This allows the system to handle phone number reassignment across different external user accounts. No guard is needed here—the design trusts that a new external ID provided for an existing phone indicates a legitimate reassignment.

@ricardogarim ricardogarim added this to the 8.3.0 milestone Mar 12, 2026
@ricardogarim ricardogarim marked this pull request as ready for review March 12, 2026 11:49
@ricardogarim ricardogarim requested review from a team as code owners March 12, 2026 11:49
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/forty-dolphins-check.md:
- Around line 2-7: Update the changeset so the public API packages are bumped as
minor: change the entries for '@rocket.chat/core-typings' and
'@rocket.chat/apps-engine' from 'patch' to 'minor' (these expose the new
IVisitorExternalIdentifier and the externalIds on visitor types), while leaving
the internal-only packages ('@rocket.chat/model-typings',
'@rocket.chat/omni-core', '@rocket.chat/models', '@rocket.chat/meteor') as
'patch' if appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1a2f5d89-009e-4852-b4c7-92c0c6f2292a

📥 Commits

Reviewing files that changed from the base of the PR and between 52cd3a9 and c95b87a.

📒 Files selected for processing (1)
  • .changeset/forty-dolphins-check.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: 🚢 Build Docker (arm64, account-service, presence-service, omnichannel-transcript-service, cove...
  • GitHub Check: 🔨 Test Storybook / Test Storybook
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/app/apps/server/bridges/livechat.ts:249-249
Timestamp: 2026-03-11T16:46:50.707Z
Learning: In `apps/meteor/app/apps/server/bridges/livechat.ts`, `createVisitor()` intentionally does not propagate `externalIds` to `registerData`. This is by design: the method is deprecated (JSDoc: `deprecated Use createAndReturnVisitor instead. Note: This method does not support externalIds.`) to push callers toward `createAndReturnVisitor()`, which does support `externalIds`. Do not flag the missing `externalIds` field in `createVisitor()` as a bug.
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx:40-46
Timestamp: 2026-03-11T15:56:13.132Z
Learning: In `apps/meteor/client/views/omnichannel/directory/components/ContactField.tsx`, `shortName` is intentionally built from the visitor's internal `username` and `phone` (not from `externalIds`). `ContactField` shows the visitor's internal system identifier. Per-channel external identifiers (e.g. WhatsApp usernames from `externalIds[].username`) are only displayed in `ContactInfoChannelsItem`, which serves a different UI context.
📚 Learning: 2026-03-11T16:46:50.707Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/app/apps/server/bridges/livechat.ts:249-249
Timestamp: 2026-03-11T16:46:50.707Z
Learning: In `apps/meteor/app/apps/server/bridges/livechat.ts`, `createVisitor()` intentionally does not propagate `externalIds` to `registerData`. This is by design: the method is deprecated (JSDoc: `deprecated Use createAndReturnVisitor instead. Note: This method does not support externalIds.`) to push callers toward `createAndReturnVisitor()`, which does support `externalIds`. Do not flag the missing `externalIds` field in `createVisitor()` as a bug.

Applied to files:

  • .changeset/forty-dolphins-check.md
📚 Learning: 2026-02-24T19:09:09.561Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:09.561Z
Learning: In RocketChat/Rocket.Chat OpenAPI migration PRs for apps/meteor/app/api/server/v1 endpoints, maintainers prefer to avoid any logic changes; style-only cleanups (like removing inline comments) may be deferred to follow-ups to keep scope tight.

Applied to files:

  • .changeset/forty-dolphins-check.md
📚 Learning: 2026-02-25T20:10:16.987Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38913
File: packages/ddp-client/src/legacy/types/SDKLegacy.ts:34-34
Timestamp: 2026-02-25T20:10:16.987Z
Learning: In the RocketChat/Rocket.Chat monorepo, packages/ddp-client and apps/meteor do not use TypeScript project references. Module augmentations in apps/meteor (e.g., declare module 'rocket.chat/rest-typings') are not visible when compiling packages/ddp-client in isolation, which is why legacy SDK methods that depend on OperationResult types for OpenAPI-migrated endpoints must remain commented out.

Applied to files:

  • .changeset/forty-dolphins-check.md

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 17 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".changeset/forty-dolphins-check.md">

<violation number="1" location=".changeset/forty-dolphins-check.md:3">
P2: `@rocket.chat/core-typings` and `@rocket.chat/apps-engine` both add new public API surface (`IVisitorExternalIdentifier`, `externalIds` on visitor types, `resolveVisitor` on `ILivechatCreator` / `LivechatBridge`). Per semver, adding new functionality is a `minor` bump, not `patch`. In particular, the new abstract method on `LivechatBridge` and the new method on `ILivechatCreator` will require downstream implementors to add the methods — marking these as `patch` under-reports the impact for consumers.</violation>
</file>

<file name="packages/omni-core/src/visitor/create.ts">

<violation number="1" location="packages/omni-core/src/visitor/create.ts:33">
P2: Setting `externalIds` as a whole object here overwrites any existing external IDs when updating an existing visitor. Merge with the stored `externalIds` (or set per-source) before the update so you don’t erase other channels’ identifiers.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

cardoso
cardoso previously approved these changes Mar 12, 2026
tassoevan
tassoevan previously approved these changes Mar 12, 2026
Copy link
Copy Markdown
Member

@d-gubert d-gubert left a comment

Choose a reason for hiding this comment

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

Please address the bots' comments on the change set.

Additionally, please add tests for the new Apps-Engine API - you can follow the pattern seen in this PR https://github.com/RocketChat/Rocket.Chat/pull/38374/changes#diff-3edecfa2dd0bd2aec99f5a218abb5d57fcd0c85d434fd7a87136e9f1e3461232

@ricardogarim ricardogarim dismissed stale reviews from tassoevan and cardoso via df8ef58 March 13, 2026 12:36
@ricardogarim ricardogarim dismissed stale reviews from gabriellsh, KevLehman, and d-gubert via 28ba4ed April 7, 2026 16:23
@ricardogarim ricardogarim force-pushed the feat/whatsapp-usernames branch from 6dd59f1 to 28ba4ed Compare April 7, 2026 16:23
@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai bot commented Apr 8, 2026

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature Pull requests that introduces new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants