@cuylabs/agent-channel-teams 3.2.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -339,6 +339,68 @@ onTurn: async (context, next) => {
339
339
  },
340
340
  ```
341
341
 
342
+ ## Targeted Activities (Group Chat / Channel)
343
+
344
+ Teams supports an `activityTreatment: targeted` entity that makes a reply
345
+ visible only to one recipient inside a group conversation. Surfaced here in two
346
+ places, both built on the `ActivityTreatments.Targeted` entity added in
347
+ `@microsoft/agents-activity` 1.5:
348
+
349
+ ```ts
350
+ import {
351
+ isTargetedTeamsActivity,
352
+ makeTargetedTeamsActivity,
353
+ parseTeamsActivity,
354
+ } from "@cuylabs/agent-channel-teams";
355
+
356
+ // Read on inbound: as a derived field on TeamsActivityInfo
357
+ const teams = parseTeamsActivity(turnContext);
358
+ if (teams.targeted) {
359
+ // The user explicitly asked the bot privately inside a group context
360
+ }
361
+
362
+ // Or directly from any TurnContext
363
+ isTargetedTeamsActivity(turnContext);
364
+
365
+ // Mark on outbound — only valid for group conversations
366
+ const reply = MessageFactory.text("This response is just for you.");
367
+ makeTargetedTeamsActivity(reply);
368
+ await turnContext.sendActivity(reply);
369
+ ```
370
+
371
+ The helper is idempotent and can be called before `TurnContext.sendActivity()`
372
+ applies the conversation reference. Teams only honors this treatment in group
373
+ chat and channel contexts.
374
+
375
+ ## Proactive Messaging
376
+
377
+ `@cuylabs/agent-channel-teams/proactive` re-exports the M365 proactive bridge
378
+ and adds a Teams-aware capture helper that bundles the `Conversation` together
379
+ with parsed Teams metadata (tenant, surface, team, channel, meeting) so apps
380
+ can route stored references later:
381
+
382
+ ```ts
383
+ import {
384
+ captureTeamsConversationReference,
385
+ continueM365Conversation,
386
+ } from "@cuylabs/agent-channel-teams/proactive";
387
+
388
+ const capture = captureTeamsConversationReference(turnContext);
389
+ await store.put(capture.teams.conversationId, capture);
390
+
391
+ // Later:
392
+ await continueM365Conversation(adapter, capture.conversation, async (ctx) => {
393
+ await ctx.sendActivity("Background work finished.");
394
+ });
395
+ ```
396
+
397
+ The upstream `Proactive` class (which is bound to `AgentApplication`) is
398
+ intentionally not used; this package exposes the underlying
399
+ `Conversation` / builder primitives plus a thin wrapper around
400
+ `CloudAdapter.continueConversation`. See
401
+ [`@cuylabs/agent-channel-m365` README](../agent-channel-m365/README.md#proactive-messaging)
402
+ for the M365-level details and telemetry coverage.
403
+
342
404
  ## Docs
343
405
 
344
406
  The package README is the quick-start view. For the focused concept guides, use
@@ -0,0 +1,357 @@
1
+ import {
2
+ parseTeamsChannelData
3
+ } from "./chunk-B5KRH22J.js";
4
+
5
+ // src/proactive.ts
6
+ import {
7
+ captureM365ConversationReference
8
+ } from "@cuylabs/agent-channel-m365/proactive";
9
+
10
+ // src/teams-activity.ts
11
+ import {
12
+ ActivityTreatments,
13
+ ActivityTypes
14
+ } from "@microsoft/agents-activity";
15
+ var ACTIVITY_TREATMENT_ENTITY_TYPE = "activityTreatment";
16
+ function isTargetedTreatmentEntity(entity) {
17
+ const value = asObject(entity);
18
+ return value?.type === ACTIVITY_TREATMENT_ENTITY_TYPE && value.treatment === ActivityTreatments.Targeted;
19
+ }
20
+ function isTargetedTeamsActivity(context) {
21
+ const activity = context.activity;
22
+ const entities = activity?.entities;
23
+ if (!Array.isArray(entities)) return false;
24
+ return entities.some(isTargetedTreatmentEntity);
25
+ }
26
+ function makeTargetedTeamsActivity(activity) {
27
+ const entities = Array.isArray(activity.entities) ? activity.entities : [];
28
+ if (!entities.some(isTargetedTreatmentEntity)) {
29
+ activity.entities = [
30
+ ...entities,
31
+ {
32
+ type: ACTIVITY_TREATMENT_ENTITY_TYPE,
33
+ treatment: ActivityTreatments.Targeted
34
+ }
35
+ ];
36
+ }
37
+ return activity;
38
+ }
39
+ function asObject(value) {
40
+ return value && typeof value === "object" ? value : void 0;
41
+ }
42
+ function readString(value, key) {
43
+ const candidate = value?.[key];
44
+ return typeof candidate === "string" && candidate.trim() ? candidate : void 0;
45
+ }
46
+ function detectSurface(conversationType, meetingId, teamId) {
47
+ if (meetingId) return "meeting";
48
+ if (teamId) return "channel";
49
+ if (conversationType === "personal") return "personal";
50
+ if (conversationType === "groupChat") return "group";
51
+ return "unknown";
52
+ }
53
+ function includesName(names, candidate) {
54
+ return !!candidate && names.includes(candidate);
55
+ }
56
+ function parseTypedChannelData(channelData, options = {}) {
57
+ if (!channelData) return void 0;
58
+ try {
59
+ return parseTeamsChannelData(channelData);
60
+ } catch (error) {
61
+ if (options.strictChannelData ?? true) {
62
+ throw error;
63
+ }
64
+ return void 0;
65
+ }
66
+ }
67
+ var TEAMS_CONFIG_FETCH_NAMES = ["config/fetch"];
68
+ var TEAMS_CONFIG_SUBMIT_NAMES = ["config/submit"];
69
+ var TEAMS_FILE_CONSENT_NAMES = ["fileConsent/invoke"];
70
+ var TEAMS_ACTIONABLE_MESSAGE_NAMES = [
71
+ "actionableMessage/executeAction"
72
+ ];
73
+ var TEAMS_ADAPTIVE_CARD_ACTION_NAMES = [
74
+ "adaptiveCard/action"
75
+ ];
76
+ var TEAMS_ADAPTIVE_CARD_SEARCH_NAMES = ["application/search"];
77
+ var TEAMS_TASK_MODULE_FETCH_NAMES = [
78
+ "task/fetch",
79
+ "composeExtension/fetchTask"
80
+ ];
81
+ var TEAMS_TASK_MODULE_SUBMIT_NAMES = ["task/submit"];
82
+ var TEAMS_TAB_FETCH_NAMES = ["tab/fetch"];
83
+ var TEAMS_TAB_SUBMIT_NAMES = ["tab/submit"];
84
+ var TEAMS_MESSAGE_EXTENSION_QUERY_NAMES = [
85
+ "composeExtension/query",
86
+ "composeExtension/queryLink",
87
+ "composeExtension/anonymousQueryLink"
88
+ ];
89
+ var TEAMS_MESSAGE_EXTENSION_SUBMIT_ACTION_NAMES = [
90
+ "composeExtension/submitAction"
91
+ ];
92
+ var TEAMS_MESSAGE_EXTENSION_FETCH_TASK_NAMES = [
93
+ "composeExtension/fetchTask"
94
+ ];
95
+ var TEAMS_MESSAGE_EXTENSION_QUERY_SETTING_URL_NAMES = [
96
+ "composeExtension/querySettingUrl"
97
+ ];
98
+ var TEAMS_MESSAGE_EXTENSION_SETTING_NAMES = [
99
+ "composeExtension/setting"
100
+ ];
101
+ var TEAMS_MESSAGE_EXTENSION_CARD_BUTTON_CLICKED_NAMES = [
102
+ "composeExtension/onCardButtonClicked"
103
+ ];
104
+ var TEAMS_MESSAGE_EXTENSION_SELECT_NAMES = [
105
+ "composeExtension/selectItem"
106
+ ];
107
+ var TEAMS_CARD_ACTION_SUBMIT_NAMES = ["message/submitAction"];
108
+ var TEAMS_CONVERSATION_UPDATE_EVENT_NAMES = {
109
+ channelCreated: "channel-created",
110
+ channelDeleted: "channel-deleted",
111
+ channelRenamed: "channel-renamed",
112
+ channelRestored: "channel-restored",
113
+ channelShared: "channel-shared",
114
+ channelUnshared: "channel-unshared",
115
+ teamRenamed: "team-renamed",
116
+ teamArchived: "team-archived",
117
+ teamUnarchived: "team-unarchived",
118
+ teamDeleted: "team-deleted",
119
+ teamHardDeleted: "team-hard-deleted",
120
+ teamRestored: "team-restored"
121
+ };
122
+ var TEAMS_MEETING_EVENT_NAMES = {
123
+ "application/vnd.microsoft.readReceipt": "read-receipt",
124
+ "application/vnd.microsoft.meetingStart": "meeting-start",
125
+ "application/vnd.microsoft.meetingEnd": "meeting-end",
126
+ "application/vnd.microsoft.meetingParticipantJoin": "participants-join",
127
+ "application/vnd.microsoft.meetingParticipantLeave": "participants-leave",
128
+ "application/vnd.microsoft.meetingRoomJoin": "meeting-room-join",
129
+ "application/vnd.microsoft.meetingRoomLeave": "meeting-room-leave",
130
+ "application/vnd.microsoft.meetingReaction": "meeting-reaction",
131
+ "application/vnd.microsoft.meetingPollResponse": "meeting-poll-response",
132
+ "application/vnd.microsoft.meetingAppsInstalled": "meeting-apps-installed",
133
+ "application/vnd.microsoft.meetingAppsUninstalled": "meeting-apps-uninstalled",
134
+ "application/vnd.microsoft.meetingRecordingStarted": "meeting-recording-started",
135
+ "application/vnd.microsoft.meetingRecordingStopped": "meeting-recording-stopped",
136
+ "application/vnd.microsoft.meetingFocusChange": "meeting-focus-change",
137
+ "application/vnd.microsoft.meetingScreenShareStart": "meeting-screen-share-start",
138
+ "application/vnd.microsoft.meetingScreenShareStop": "meeting-screen-share-stop"
139
+ };
140
+ var TEAMS_MEETING_INVOKE_NAMES = {
141
+ "application/vnd.microsoft.meetingStageView": "meeting-stage-view",
142
+ "application/vnd.microsoft.meetingSmartReply": "meeting-smart-reply"
143
+ };
144
+ function detectKind(context, eventType) {
145
+ const { activity } = context;
146
+ const payload = asObject(activity.value);
147
+ const invokeName = activity.name;
148
+ switch (activity.type) {
149
+ case ActivityTypes.Message:
150
+ return "message";
151
+ case ActivityTypes.MessageReaction:
152
+ return "reaction";
153
+ case ActivityTypes.ConversationUpdate:
154
+ if (Array.isArray(activity.membersAdded) && activity.membersAdded.length > 0) {
155
+ return "members-added";
156
+ }
157
+ if (Array.isArray(activity.membersRemoved) && activity.membersRemoved.length > 0) {
158
+ return "members-removed";
159
+ }
160
+ if (eventType && eventType in TEAMS_CONVERSATION_UPDATE_EVENT_NAMES) {
161
+ return TEAMS_CONVERSATION_UPDATE_EVENT_NAMES[eventType];
162
+ }
163
+ return "conversation-update";
164
+ case ActivityTypes.MessageUpdate:
165
+ if (eventType === "editMessage") return "message-edit";
166
+ if (eventType === "undeleteMessage") return "message-restore";
167
+ return "other";
168
+ case ActivityTypes.MessageDelete:
169
+ if (eventType === "softDeleteMessage") return "message-delete";
170
+ return "other";
171
+ case ActivityTypes.Event:
172
+ if (invokeName && invokeName in TEAMS_MEETING_EVENT_NAMES) {
173
+ return TEAMS_MEETING_EVENT_NAMES[invokeName];
174
+ }
175
+ return "other";
176
+ case ActivityTypes.Invoke:
177
+ if (invokeName && invokeName in TEAMS_MEETING_INVOKE_NAMES) {
178
+ return TEAMS_MEETING_INVOKE_NAMES[invokeName];
179
+ }
180
+ if (includesName(TEAMS_CONFIG_FETCH_NAMES, invokeName)) {
181
+ return "config-fetch";
182
+ }
183
+ if (includesName(TEAMS_CONFIG_SUBMIT_NAMES, invokeName)) {
184
+ return "config-submit";
185
+ }
186
+ if (includesName(TEAMS_FILE_CONSENT_NAMES, invokeName)) {
187
+ return "file-consent";
188
+ }
189
+ if (includesName(TEAMS_ACTIONABLE_MESSAGE_NAMES, invokeName)) {
190
+ return "actionable-message-execute-action";
191
+ }
192
+ if (includesName(TEAMS_ADAPTIVE_CARD_ACTION_NAMES, invokeName)) {
193
+ return "adaptive-card-action";
194
+ }
195
+ if (includesName(TEAMS_ADAPTIVE_CARD_SEARCH_NAMES, invokeName)) {
196
+ return "adaptive-card-search";
197
+ }
198
+ if (includesName(TEAMS_TASK_MODULE_FETCH_NAMES, invokeName)) {
199
+ return invokeName === "composeExtension/fetchTask" ? "message-extension-fetch-task" : "task-fetch";
200
+ }
201
+ if (includesName(TEAMS_TASK_MODULE_SUBMIT_NAMES, invokeName)) {
202
+ return "task-submit";
203
+ }
204
+ if (includesName(TEAMS_TAB_FETCH_NAMES, invokeName)) {
205
+ return "tab-fetch";
206
+ }
207
+ if (includesName(TEAMS_TAB_SUBMIT_NAMES, invokeName)) {
208
+ return "tab-submit";
209
+ }
210
+ if (includesName(TEAMS_MESSAGE_EXTENSION_QUERY_NAMES, invokeName)) {
211
+ switch (invokeName) {
212
+ case "composeExtension/queryLink":
213
+ return "message-extension-query-link";
214
+ case "composeExtension/anonymousQueryLink":
215
+ return "message-extension-anonymous-query-link";
216
+ default:
217
+ return "message-extension-query";
218
+ }
219
+ }
220
+ if (includesName(TEAMS_MESSAGE_EXTENSION_SELECT_NAMES, invokeName)) {
221
+ return "message-extension-select-item";
222
+ }
223
+ if (includesName(TEAMS_MESSAGE_EXTENSION_SUBMIT_ACTION_NAMES, invokeName)) {
224
+ return "message-extension-submit-action";
225
+ }
226
+ if (includesName(
227
+ TEAMS_MESSAGE_EXTENSION_QUERY_SETTING_URL_NAMES,
228
+ invokeName
229
+ )) {
230
+ return "message-extension-query-setting-url";
231
+ }
232
+ if (includesName(TEAMS_MESSAGE_EXTENSION_SETTING_NAMES, invokeName)) {
233
+ return "message-extension-setting";
234
+ }
235
+ if (includesName(
236
+ TEAMS_MESSAGE_EXTENSION_CARD_BUTTON_CLICKED_NAMES,
237
+ invokeName
238
+ )) {
239
+ return "message-extension-card-button-clicked";
240
+ }
241
+ if (includesName(TEAMS_CARD_ACTION_SUBMIT_NAMES, invokeName)) {
242
+ return payload?.actionName === "feedback" ? "feedback" : "card-submit";
243
+ }
244
+ return "invoke";
245
+ default:
246
+ return "other";
247
+ }
248
+ }
249
+ function parseActors(channelData) {
250
+ const raw = channelData?.onBehalfOf;
251
+ if (!Array.isArray(raw)) return [];
252
+ const actors = [];
253
+ for (const item of raw) {
254
+ const actor = asObject(item);
255
+ const itemRef = asObject(actor?.item);
256
+ actors.push({
257
+ id: readString(itemRef, "id"),
258
+ displayName: readString(itemRef, "displayName"),
259
+ tenantId: readString(actor, "tenantId"),
260
+ userPrincipalName: readString(itemRef, "userPrincipalName")
261
+ });
262
+ }
263
+ return actors.filter(
264
+ (actor) => actor.id !== void 0 || actor.displayName !== void 0 || actor.userPrincipalName !== void 0
265
+ );
266
+ }
267
+ function parseTeamsActivity(context, options = {}) {
268
+ const { activity } = context;
269
+ const channelData = asObject(activity.channelData);
270
+ const parsedChannelData = parseTypedChannelData(channelData, options);
271
+ const team = asObject(channelData?.team);
272
+ const channel = asObject(channelData?.channel);
273
+ const tenant = asObject(channelData?.tenant);
274
+ const meeting = asObject(channelData?.meeting);
275
+ const eventType = parsedChannelData?.eventType ?? readString(channelData, "eventType");
276
+ const conversationType = typeof activity.conversation?.conversationType === "string" ? activity.conversation.conversationType : void 0;
277
+ const teamId = parsedChannelData?.team?.aadGroupId ?? parsedChannelData?.team?.id ?? readString(team, "aadGroupId") ?? readString(team, "id");
278
+ const meetingId = parsedChannelData?.meeting?.id ?? readString(meeting, "id");
279
+ const surface = detectSurface(
280
+ conversationType,
281
+ meetingId,
282
+ teamId
283
+ );
284
+ return {
285
+ channelId: activity.channelId ?? "unknown",
286
+ conversationId: activity.conversation?.id ?? "unknown",
287
+ activityId: activity.id ?? void 0,
288
+ messageId: activity.id ?? void 0,
289
+ replyToId: activity.replyToId ?? void 0,
290
+ invokeName: activity.name ?? void 0,
291
+ eventType,
292
+ tenantId: parsedChannelData?.tenant?.id ?? readString(tenant, "id") ?? activity.conversation?.tenantId,
293
+ teamId,
294
+ channelData: parsedChannelData,
295
+ channelThreadId: parsedChannelData?.channel?.id ?? readString(channel, "id"),
296
+ meetingId,
297
+ surface,
298
+ kind: detectKind(context, eventType),
299
+ actors: parseActors(channelData),
300
+ targeted: isTargetedTeamsActivity(context)
301
+ };
302
+ }
303
+ function parseTeamsActivitySafe(context) {
304
+ return parseTeamsActivity(context, { strictChannelData: false });
305
+ }
306
+
307
+ // src/proactive.ts
308
+ import {
309
+ Conversation,
310
+ ConversationBuilder,
311
+ ConversationReferenceBuilder,
312
+ CreateConversationOptionsBuilder,
313
+ captureM365ConversationReference as captureM365ConversationReference2,
314
+ continueM365Conversation,
315
+ restoreM365ConversationReference
316
+ } from "@cuylabs/agent-channel-m365/proactive";
317
+ function captureTeamsConversationReference(context) {
318
+ const conversation = captureM365ConversationReference(context);
319
+ const teams = parseTeamsActivity(context, { strictChannelData: false });
320
+ return { conversation, teams };
321
+ }
322
+
323
+ export {
324
+ isTargetedTeamsActivity,
325
+ makeTargetedTeamsActivity,
326
+ TEAMS_CONFIG_FETCH_NAMES,
327
+ TEAMS_CONFIG_SUBMIT_NAMES,
328
+ TEAMS_FILE_CONSENT_NAMES,
329
+ TEAMS_ACTIONABLE_MESSAGE_NAMES,
330
+ TEAMS_ADAPTIVE_CARD_ACTION_NAMES,
331
+ TEAMS_ADAPTIVE_CARD_SEARCH_NAMES,
332
+ TEAMS_TASK_MODULE_FETCH_NAMES,
333
+ TEAMS_TASK_MODULE_SUBMIT_NAMES,
334
+ TEAMS_TAB_FETCH_NAMES,
335
+ TEAMS_TAB_SUBMIT_NAMES,
336
+ TEAMS_MESSAGE_EXTENSION_QUERY_NAMES,
337
+ TEAMS_MESSAGE_EXTENSION_SUBMIT_ACTION_NAMES,
338
+ TEAMS_MESSAGE_EXTENSION_FETCH_TASK_NAMES,
339
+ TEAMS_MESSAGE_EXTENSION_QUERY_SETTING_URL_NAMES,
340
+ TEAMS_MESSAGE_EXTENSION_SETTING_NAMES,
341
+ TEAMS_MESSAGE_EXTENSION_CARD_BUTTON_CLICKED_NAMES,
342
+ TEAMS_MESSAGE_EXTENSION_SELECT_NAMES,
343
+ TEAMS_CARD_ACTION_SUBMIT_NAMES,
344
+ TEAMS_CONVERSATION_UPDATE_EVENT_NAMES,
345
+ TEAMS_MEETING_EVENT_NAMES,
346
+ TEAMS_MEETING_INVOKE_NAMES,
347
+ parseTeamsActivity,
348
+ parseTeamsActivitySafe,
349
+ captureTeamsConversationReference,
350
+ Conversation,
351
+ ConversationBuilder,
352
+ ConversationReferenceBuilder,
353
+ CreateConversationOptionsBuilder,
354
+ captureM365ConversationReference2 as captureM365ConversationReference,
355
+ continueM365Conversation,
356
+ restoreM365ConversationReference
357
+ };