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:
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.
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.
- 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
Additional Context
The activities Teams actually sends
- Personal install —
installationUpdate, action: "add", conversation.conversationType: "personal", carrying conversation.id, serviceUrl, from.aadObjectId, and locale.
- Personal uninstall —
installationUpdate, action: "remove", personal.
- Added to a team/channel —
conversationUpdate 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.
Problem Statement
When the bot is installed on Teams, the adapter receives the lifecycle activities but dispatches
no Chat SDK event, so
chat.onMemberJoinedChannelnever fires and there is no install signal atall.
registerEventHandlershandles both events with a cache-only listener:The Slack adapter, by contrast, calls
chat.processMemberJoinedChannel(...)when the bot joins achannel. 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:
conversationUpdatewheremembersAddedcontains the bot (recipient.id) →processMemberJoinedChannel({ adapter, channelId, userId: <bot id>, inviterId }).Also set
botUserIdon the adapter so the standard "did the bot join?" guard(
event.userId === event.adapter.botUserId) works — it is currently never assigned, so theguard can't be satisfied.
installationUpdateadd/remove→ a first-class event (e.g.onInstalled/onUninstalled), since a personal install is not a channel join and has noonMemberJoinedChannelanalog. Include the conversation reference so consumers can send a proactive welcome and persist
it for later proactive messages.
conversation-updateroute key union to includeteamMemberAdded(and any othereventTypes Microsoft emits) so the typed alias is usable.
Alternatives Considered
We subclass
TeamsAdapterand override the (protected)registerEventHandlersto register our owninstallationUpdate/conversationUpdatelisteners beforesuper.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 viactx.send(...),subscribe the channel intro thread via
getState().subscribe(...), and persistctx.ref. Thisdepends on
protectedinternals and the middleware-ordering detail, which a native dispatch wouldlet us delete.
Use Case
Priority
Important
Contribution
Additional Context
The activities Teams actually sends
installationUpdate,action: "add",conversation.conversationType: "personal", carryingconversation.id,serviceUrl,from.aadObjectId, andlocale.installationUpdate,action: "remove", personal.conversationUpdatewithmembersAdded: [{ id: "28:<botId>" }]and
channelData.eventType: "teamMemberAdded". Note: the SDK's typedeventTypeunion(
routes/conversation-update.d.ts) listschannelMemberAddedbut notteamMemberAdded,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
conversationUpdateand inspectmembersAddedmanually.