Skip to content

Dispatch Teams install/join activities #847

Description

@magnusburton

Problem Statement

When the bot is installed on Teams, the adapter receives the lifecycle activities but dispatches
no Chat SDK event, so chat.onMemberJoinedChannel never fires and there is no install signal at
all. registerEventHandlers handles both events with a cache-only listener:

this.app.on("conversationUpdate", async (ctx) => { this.cacheUserContext(ctx.activity); });
this.app.on("installationUpdate", async (ctx) => { this.cacheUserContext(ctx.activity); });

The Slack adapter, by contrast, calls chat.processMemberJoinedChannel(...) when the bot joins a
channel. So the same consumer welcome handler works on Slack and is dead on Teams.

Proposed Solution

Map these to Chat SDK events, matching the Slack adapter:

  1. conversationUpdate where membersAdded contains the bot (recipient.id) →
    processMemberJoinedChannel({ adapter, channelId, userId: <bot id>, inviterId }).
    Also set botUserId on the adapter so the standard "did the bot join?" guard
    (event.userId === event.adapter.botUserId) works — it is currently never assigned, so the
    guard can't be satisfied.
  2. installationUpdate add/remove → a first-class event (e.g. onInstalled /
    onUninstalled), since a personal install is not a channel join and has no onMemberJoinedChannel
    analog. Include the conversation reference so consumers can send a proactive welcome and persist
    it for later proactive messages.
  3. Fix the conversation-update route key union to include teamMemberAdded (and any other
    eventTypes Microsoft emits) so the typed alias is usable.

Alternatives Considered

We subclass TeamsAdapter and override the (protected) registerEventHandlers to register our own
installationUpdate / conversationUpdate listeners before super.registerEventHandlers()
the router runs matching routes as a middleware chain and the base cache listeners are terminal
(never call ctx.next()), so a later route is skipped. We post the welcome via ctx.send(...),
subscribe the channel intro thread via getState().subscribe(...), and persist ctx.ref. This
depends on protected internals and the middleware-ordering detail, which a native dispatch would
let us delete.

Use Case

Priority

Important

Contribution

  • I am willing to help implement this feature

Additional Context

The activities Teams actually sends

  • Personal installinstallationUpdate, action: "add", conversation.conversationType: "personal", carrying conversation.id, serviceUrl, from.aadObjectId, and locale.
  • Personal uninstallinstallationUpdate, action: "remove", personal.
  • Added to a team/channelconversationUpdate with membersAdded: [{ id: "28:<botId>" }]
    and channelData.eventType: "teamMemberAdded". Note: the SDK's typed eventType union
    (routes/conversation-update.d.ts) lists channelMemberAdded but not teamMemberAdded,
    which is the value Microsoft actually sends for a team install — so the typed event alias can't
    match it, and a consumer must fall back to the generic conversationUpdate and inspect
    membersAdded manually.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions