@kodelyth/msteams 2026.5.39 → 2026.6.1

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.
@@ -0,0 +1,1161 @@
1
+ import { O as resolveNestedAllowlistDecision, S as normalizeChannelSlug, T as resolveChannelEntryMatchWithFallback, _ as isDangerousNameMatchingEnabled, i as buildChannelKeyCandidates, k as resolveToolsBySender, o as buildProbeChannelStatusSummary, r as PAIRING_APPROVED_MESSAGE, s as chunkTextForOutbound, t as DEFAULT_ACCOUNT_ID, u as createDefaultChannelRuntimeState, w as resolveAllowlistMatchSimple } from "./runtime-api-BL4DOWXD.js";
2
+ import { h as resolveMSTeamsCredentials } from "./graph-users-D-gKCguI.js";
3
+ import { a as looksLikeMSTeamsTargetId, c as parseMSTeamsConversationId, d as resolveMSTeamsUserAllowlist, i as msteamsSetupAdapter, l as parseMSTeamsTeamChannelInput, o as normalizeMSTeamsMessagingTarget, s as normalizeMSTeamsUserInput, t as msteamsSetupWizard, u as resolveMSTeamsChannelAllowlist } from "./setup-surface-COTQDcTQ.js";
4
+ import { t as MSTeamsChannelConfigSchema } from "./config-schema-Btk-XCOd.js";
5
+ import { describeAccountSnapshot } from "klaw/plugin-sdk/account-helpers";
6
+ import { formatAllowFromLowercase } from "klaw/plugin-sdk/allow-from";
7
+ import { createTopLevelChannelConfigAdapter } from "klaw/plugin-sdk/channel-config-helpers";
8
+ import { buildChannelOutboundSessionRoute, createChatChannelPlugin, stripChannelTargetPrefix, stripTargetKindPrefix } from "klaw/plugin-sdk/channel-core";
9
+ import { createChannelMessageAdapterFromOutbound } from "klaw/plugin-sdk/channel-message";
10
+ import { createPairingPrefixStripper } from "klaw/plugin-sdk/channel-pairing";
11
+ import { createAllowlistProviderGroupPolicyWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, projectConfigWarningCollector } from "klaw/plugin-sdk/channel-policy";
12
+ import { createChannelDirectoryAdapter, createRuntimeDirectoryLiveAdapter, listDirectoryEntriesFromSources } from "klaw/plugin-sdk/directory-runtime";
13
+ import { adaptMessagePresentationForChannel, normalizeMessagePresentation } from "klaw/plugin-sdk/interactive-runtime";
14
+ import { createLazyRuntimeNamedExport } from "klaw/plugin-sdk/lazy-runtime";
15
+ import { createRuntimeOutboundDelegates } from "klaw/plugin-sdk/outbound-runtime";
16
+ import { createComputedAccountStatusAdapter } from "klaw/plugin-sdk/status-helpers";
17
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "klaw/plugin-sdk/string-coerce-runtime";
18
+ import { Type } from "typebox";
19
+ import { createResolvedApproverActionAuthAdapter, resolveApprovalApprovers } from "klaw/plugin-sdk/approval-auth-runtime";
20
+ //#region extensions/msteams/src/approval-auth.ts
21
+ const MSTEAMS_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
22
+ function normalizeMSTeamsApproverId(value) {
23
+ const normalized = normalizeMSTeamsMessagingTarget(String(value));
24
+ if (!normalized?.startsWith("user:")) return;
25
+ const id = normalizeOptionalLowercaseString(normalized.slice(5));
26
+ if (!id) return;
27
+ return MSTEAMS_ID_RE.test(id) ? id : void 0;
28
+ }
29
+ function resolveMSTeamsChannelConfig$1(cfg) {
30
+ return cfg.channels?.msteams;
31
+ }
32
+ const msTeamsApprovalAuth = createResolvedApproverActionAuthAdapter({
33
+ channelLabel: "Microsoft Teams",
34
+ resolveApprovers: ({ cfg }) => {
35
+ const channel = resolveMSTeamsChannelConfig$1(cfg);
36
+ return resolveApprovalApprovers({
37
+ allowFrom: channel?.allowFrom,
38
+ defaultTo: channel?.defaultTo,
39
+ normalizeApprover: normalizeMSTeamsApproverId
40
+ });
41
+ },
42
+ normalizeSenderId: (value) => {
43
+ const trimmed = normalizeOptionalLowercaseString(value);
44
+ if (!trimmed) return;
45
+ return MSTEAMS_ID_RE.test(trimmed) ? trimmed : void 0;
46
+ }
47
+ });
48
+ //#endregion
49
+ //#region extensions/msteams/src/doctor.ts
50
+ function isMSTeamsMutableAllowEntry(raw) {
51
+ const text = raw.trim();
52
+ if (!text || text === "*") return false;
53
+ const withoutPrefix = text.replace(/^(msteams|user):/i, "").trim();
54
+ return /\s/.test(withoutPrefix) || withoutPrefix.includes("@");
55
+ }
56
+ const collectMSTeamsMutableAllowlistWarnings = createDangerousNameMatchingMutableAllowlistWarningCollector({
57
+ channel: "msteams",
58
+ detector: isMSTeamsMutableAllowEntry,
59
+ collectLists: (scope) => [{
60
+ pathLabel: `${scope.prefix}.allowFrom`,
61
+ list: scope.account.allowFrom
62
+ }, {
63
+ pathLabel: `${scope.prefix}.groupAllowFrom`,
64
+ list: scope.account.groupAllowFrom
65
+ }]
66
+ });
67
+ //#endregion
68
+ //#region extensions/msteams/src/policy.ts
69
+ function resolveMSTeamsRouteConfig(params) {
70
+ const teamId = params.teamId?.trim();
71
+ const teamName = params.teamName?.trim();
72
+ const conversationId = params.conversationId?.trim();
73
+ const channelName = params.channelName?.trim();
74
+ const teams = params.cfg?.teams ?? {};
75
+ const allowlistConfigured = Object.keys(teams).length > 0;
76
+ const teamMatch = resolveChannelEntryMatchWithFallback({
77
+ entries: teams,
78
+ keys: buildChannelKeyCandidates(teamId, params.allowNameMatching ? teamName : void 0, params.allowNameMatching && teamName ? normalizeChannelSlug(teamName) : void 0),
79
+ wildcardKey: "*",
80
+ normalizeKey: normalizeChannelSlug
81
+ });
82
+ const teamConfig = teamMatch.entry;
83
+ const channels = teamConfig?.channels ?? {};
84
+ const channelAllowlistConfigured = Object.keys(channels).length > 0;
85
+ const channelMatch = resolveChannelEntryMatchWithFallback({
86
+ entries: channels,
87
+ keys: buildChannelKeyCandidates(conversationId, params.allowNameMatching ? channelName : void 0, params.allowNameMatching && channelName ? normalizeChannelSlug(channelName) : void 0),
88
+ wildcardKey: "*",
89
+ normalizeKey: normalizeChannelSlug
90
+ });
91
+ const channelConfig = channelMatch.entry;
92
+ return {
93
+ teamConfig,
94
+ channelConfig,
95
+ allowlistConfigured,
96
+ allowed: resolveNestedAllowlistDecision({
97
+ outerConfigured: allowlistConfigured,
98
+ outerMatched: Boolean(teamConfig),
99
+ innerConfigured: channelAllowlistConfigured,
100
+ innerMatched: Boolean(channelConfig)
101
+ }),
102
+ teamKey: teamMatch.matchKey ?? teamMatch.key,
103
+ channelKey: channelMatch.matchKey ?? channelMatch.key,
104
+ channelMatchKey: channelMatch.matchKey,
105
+ channelMatchSource: channelMatch.matchSource === "direct" || channelMatch.matchSource === "wildcard" ? channelMatch.matchSource : void 0
106
+ };
107
+ }
108
+ function resolveMSTeamsGroupToolPolicy(params) {
109
+ const cfg = params.cfg.channels?.msteams;
110
+ if (!cfg) return;
111
+ const groupId = params.groupId?.trim();
112
+ const groupChannel = params.groupChannel?.trim();
113
+ const groupSpace = params.groupSpace?.trim();
114
+ const allowNameMatching = isDangerousNameMatchingEnabled(cfg);
115
+ const resolved = resolveMSTeamsRouteConfig({
116
+ cfg,
117
+ teamId: groupSpace,
118
+ teamName: groupSpace,
119
+ conversationId: groupId,
120
+ channelName: groupChannel,
121
+ allowNameMatching
122
+ });
123
+ if (resolved.channelConfig) {
124
+ const senderPolicy = resolveToolsBySender({
125
+ toolsBySender: resolved.channelConfig.toolsBySender,
126
+ senderId: params.senderId,
127
+ senderName: params.senderName,
128
+ senderUsername: params.senderUsername,
129
+ senderE164: params.senderE164
130
+ });
131
+ if (senderPolicy) return senderPolicy;
132
+ if (resolved.channelConfig.tools) return resolved.channelConfig.tools;
133
+ const teamSenderPolicy = resolveToolsBySender({
134
+ toolsBySender: resolved.teamConfig?.toolsBySender,
135
+ senderId: params.senderId,
136
+ senderName: params.senderName,
137
+ senderUsername: params.senderUsername,
138
+ senderE164: params.senderE164
139
+ });
140
+ if (teamSenderPolicy) return teamSenderPolicy;
141
+ return resolved.teamConfig?.tools;
142
+ }
143
+ if (resolved.teamConfig) {
144
+ const teamSenderPolicy = resolveToolsBySender({
145
+ toolsBySender: resolved.teamConfig.toolsBySender,
146
+ senderId: params.senderId,
147
+ senderName: params.senderName,
148
+ senderUsername: params.senderUsername,
149
+ senderE164: params.senderE164
150
+ });
151
+ if (teamSenderPolicy) return teamSenderPolicy;
152
+ if (resolved.teamConfig.tools) return resolved.teamConfig.tools;
153
+ }
154
+ if (!groupId) return;
155
+ const channelCandidates = buildChannelKeyCandidates(groupId, allowNameMatching ? groupChannel : void 0, allowNameMatching && groupChannel ? normalizeChannelSlug(groupChannel) : void 0);
156
+ for (const teamConfig of Object.values(cfg.teams ?? {})) {
157
+ const match = resolveChannelEntryMatchWithFallback({
158
+ entries: teamConfig?.channels ?? {},
159
+ keys: channelCandidates,
160
+ wildcardKey: "*",
161
+ normalizeKey: normalizeChannelSlug
162
+ });
163
+ if (match.entry) {
164
+ const senderPolicy = resolveToolsBySender({
165
+ toolsBySender: match.entry.toolsBySender,
166
+ senderId: params.senderId,
167
+ senderName: params.senderName,
168
+ senderUsername: params.senderUsername,
169
+ senderE164: params.senderE164
170
+ });
171
+ if (senderPolicy) return senderPolicy;
172
+ if (match.entry.tools) return match.entry.tools;
173
+ const teamSenderPolicy = resolveToolsBySender({
174
+ toolsBySender: teamConfig?.toolsBySender,
175
+ senderId: params.senderId,
176
+ senderName: params.senderName,
177
+ senderUsername: params.senderUsername,
178
+ senderE164: params.senderE164
179
+ });
180
+ if (teamSenderPolicy) return teamSenderPolicy;
181
+ return teamConfig?.tools;
182
+ }
183
+ }
184
+ }
185
+ function resolveMSTeamsAllowlistMatch(params) {
186
+ return resolveAllowlistMatchSimple(params);
187
+ }
188
+ function resolveMSTeamsReplyPolicy(params) {
189
+ if (params.isDirectMessage) return {
190
+ requireMention: false,
191
+ replyStyle: "thread"
192
+ };
193
+ const requireMention = params.channelConfig?.requireMention ?? params.teamConfig?.requireMention ?? params.globalConfig?.requireMention ?? true;
194
+ return {
195
+ requireMention,
196
+ replyStyle: params.channelConfig?.replyStyle ?? params.teamConfig?.replyStyle ?? params.globalConfig?.replyStyle ?? (requireMention ? "thread" : "top-level")
197
+ };
198
+ }
199
+ //#endregion
200
+ //#region extensions/msteams/src/presentation.ts
201
+ const MSTEAMS_PRESENTATION_CAPABILITIES = {
202
+ supported: true,
203
+ buttons: true,
204
+ selects: false,
205
+ context: true,
206
+ divider: true,
207
+ limits: {
208
+ actions: {
209
+ supportsStyles: false,
210
+ supportsDisabled: false
211
+ },
212
+ text: { markdownDialect: "markdown" }
213
+ }
214
+ };
215
+ function buildMSTeamsPresentationCard(params) {
216
+ const body = [];
217
+ const text = normalizeOptionalString(params.text);
218
+ if (text) body.push({
219
+ type: "TextBlock",
220
+ text,
221
+ wrap: true
222
+ });
223
+ const presentation = adaptMessagePresentationForChannel({
224
+ presentation: params.presentation,
225
+ capabilities: MSTEAMS_PRESENTATION_CAPABILITIES
226
+ });
227
+ if (presentation.title) body.push({
228
+ type: "TextBlock",
229
+ text: presentation.title,
230
+ weight: "Bolder",
231
+ size: "Medium",
232
+ wrap: true
233
+ });
234
+ const actions = [];
235
+ for (const block of presentation.blocks) {
236
+ if (block.type === "text" || block.type === "context") {
237
+ body.push({
238
+ type: "TextBlock",
239
+ text: block.text,
240
+ wrap: true,
241
+ ...block.type === "context" ? {
242
+ isSubtle: true,
243
+ size: "Small"
244
+ } : {}
245
+ });
246
+ continue;
247
+ }
248
+ if (block.type === "divider") {
249
+ body.push({
250
+ type: "TextBlock",
251
+ text: "---",
252
+ wrap: true,
253
+ isSubtle: true
254
+ });
255
+ continue;
256
+ }
257
+ if (block.type === "buttons") for (const button of block.buttons) {
258
+ const targetUrl = button.url ?? button.webApp?.url ?? button.web_app?.url;
259
+ if (targetUrl) {
260
+ actions.push({
261
+ type: "Action.OpenUrl",
262
+ title: button.label,
263
+ url: targetUrl
264
+ });
265
+ continue;
266
+ }
267
+ if (button.value) actions.push({
268
+ type: "Action.Submit",
269
+ title: button.label,
270
+ data: {
271
+ value: button.value,
272
+ label: button.label
273
+ }
274
+ });
275
+ }
276
+ }
277
+ return {
278
+ type: "AdaptiveCard",
279
+ version: "1.4",
280
+ body,
281
+ ...actions.length ? { actions } : {}
282
+ };
283
+ }
284
+ //#endregion
285
+ //#region extensions/msteams/src/session-route.ts
286
+ function resolveMSTeamsOutboundSessionRoute(params) {
287
+ let trimmed = stripChannelTargetPrefix(params.target, "msteams", "teams");
288
+ if (!trimmed) return null;
289
+ const isUser = normalizeLowercaseStringOrEmpty(trimmed).startsWith("user:");
290
+ const rawId = stripTargetKindPrefix(trimmed);
291
+ if (!rawId) return null;
292
+ const conversationId = rawId.split(";")[0] ?? rawId;
293
+ const isChannel = !isUser && /@thread\.tacv2/i.test(conversationId);
294
+ return buildChannelOutboundSessionRoute({
295
+ cfg: params.cfg,
296
+ agentId: params.agentId,
297
+ channel: "msteams",
298
+ accountId: params.accountId,
299
+ peer: {
300
+ kind: isUser ? "direct" : isChannel ? "channel" : "group",
301
+ id: conversationId
302
+ },
303
+ chatType: isUser ? "direct" : isChannel ? "channel" : "group",
304
+ from: isUser ? `msteams:${conversationId}` : isChannel ? `msteams:channel:${conversationId}` : `msteams:group:${conversationId}`,
305
+ to: isUser ? `user:${conversationId}` : `conversation:${conversationId}`
306
+ });
307
+ }
308
+ //#endregion
309
+ //#region extensions/msteams/src/channel.ts
310
+ const meta = {
311
+ id: "msteams",
312
+ label: "Microsoft Teams",
313
+ selectionLabel: "Microsoft Teams (Bot Framework)",
314
+ docsPath: "/channels/msteams",
315
+ docsLabel: "msteams",
316
+ blurb: "Teams SDK; enterprise support.",
317
+ aliases: ["teams"],
318
+ order: 60
319
+ };
320
+ const TEAMS_GRAPH_PERMISSION_HINTS = {
321
+ "ChannelMessage.Read.All": "channel history",
322
+ "Chat.Read.All": "chat history",
323
+ "Channel.ReadBasic.All": "channel list",
324
+ "Team.ReadBasic.All": "team list",
325
+ "TeamsActivity.Read.All": "teams activity",
326
+ "Sites.Read.All": "files (SharePoint)",
327
+ "Files.Read.All": "files (OneDrive)"
328
+ };
329
+ const collectMSTeamsSecurityWarnings = createAllowlistProviderGroupPolicyWarningCollector({
330
+ providerConfigPresent: (cfg) => cfg.channels?.msteams !== void 0,
331
+ resolveGroupPolicy: ({ cfg }) => cfg.channels?.msteams?.groupPolicy,
332
+ collect: ({ groupPolicy }) => groupPolicy === "open" ? ["- MS Teams groups: groupPolicy=\"open\" allows any member to trigger (mention-gated). Set channels.msteams.groupPolicy=\"allowlist\" + channels.msteams.groupAllowFrom to restrict senders."] : []
333
+ });
334
+ const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-NssGKZm5.js"), "msTeamsChannelRuntime");
335
+ const resolveMSTeamsChannelConfig = (cfg) => ({
336
+ allowFrom: cfg.channels?.msteams?.allowFrom,
337
+ defaultTo: cfg.channels?.msteams?.defaultTo
338
+ });
339
+ const msteamsConfigAdapter = createTopLevelChannelConfigAdapter({
340
+ sectionKey: "msteams",
341
+ resolveAccount: (cfg) => ({
342
+ accountId: DEFAULT_ACCOUNT_ID,
343
+ enabled: cfg.channels?.msteams?.enabled !== false,
344
+ configured: Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams))
345
+ }),
346
+ resolveAccessorAccount: ({ cfg }) => resolveMSTeamsChannelConfig(cfg),
347
+ resolveAllowFrom: (account) => account.allowFrom,
348
+ formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
349
+ resolveDefaultTo: (account) => account.defaultTo
350
+ });
351
+ function jsonActionResult(data) {
352
+ return {
353
+ content: [{
354
+ type: "text",
355
+ text: JSON.stringify(data)
356
+ }],
357
+ details: data
358
+ };
359
+ }
360
+ function jsonMSTeamsActionResult(action, data = {}) {
361
+ return jsonActionResult({
362
+ channel: "msteams",
363
+ action,
364
+ ...data
365
+ });
366
+ }
367
+ function jsonMSTeamsOkActionResult(action, data = {}) {
368
+ return jsonActionResult({
369
+ ok: true,
370
+ channel: "msteams",
371
+ action,
372
+ ...data
373
+ });
374
+ }
375
+ function jsonMSTeamsConversationResult(conversationId) {
376
+ return jsonActionResultWithDetails({
377
+ ok: true,
378
+ channel: "msteams",
379
+ conversationId
380
+ }, {
381
+ ok: true,
382
+ channel: "msteams"
383
+ });
384
+ }
385
+ function jsonActionResultWithDetails(contentData, details) {
386
+ return {
387
+ content: [{
388
+ type: "text",
389
+ text: JSON.stringify(contentData)
390
+ }],
391
+ details
392
+ };
393
+ }
394
+ const MSTEAMS_REACTION_TYPES = [
395
+ "like",
396
+ "heart",
397
+ "laugh",
398
+ "surprised",
399
+ "sad",
400
+ "angry"
401
+ ];
402
+ function actionError(message) {
403
+ return {
404
+ isError: true,
405
+ content: [{
406
+ type: "text",
407
+ text: message
408
+ }],
409
+ details: { error: message }
410
+ };
411
+ }
412
+ function resolveActionTarget(params, currentChannelId) {
413
+ return typeof params.to === "string" ? params.to.trim() : typeof params.target === "string" ? params.target.trim() : currentChannelId?.trim() ?? "";
414
+ }
415
+ function resolveGraphActionTarget(params, currentChannelId, currentGraphChannelId) {
416
+ return resolveActionTarget(params, currentGraphChannelId ?? currentChannelId);
417
+ }
418
+ function resolveActionMessageId(params) {
419
+ return normalizeOptionalString(params.messageId) ?? "";
420
+ }
421
+ function resolveActionPinnedMessageId(params) {
422
+ return typeof params.pinnedMessageId === "string" ? params.pinnedMessageId.trim() : typeof params.messageId === "string" ? params.messageId.trim() : "";
423
+ }
424
+ function resolveActionQuery(params) {
425
+ return normalizeOptionalString(params.query) ?? "";
426
+ }
427
+ function resolveActionContent(params) {
428
+ return typeof params.text === "string" ? params.text : typeof params.content === "string" ? params.content : typeof params.message === "string" ? params.message : "";
429
+ }
430
+ function readOptionalTrimmedString(params, key) {
431
+ return typeof params[key] === "string" ? params[key].trim() || void 0 : void 0;
432
+ }
433
+ function resolveActionUploadFilePath(params) {
434
+ for (const key of [
435
+ "filePath",
436
+ "path",
437
+ "media"
438
+ ]) if (typeof params[key] === "string") {
439
+ const value = params[key];
440
+ if (value.trim()) return value;
441
+ }
442
+ }
443
+ function resolveRequiredActionTarget(params) {
444
+ const to = params.graphOnly ? resolveGraphActionTarget(params.toolParams, params.currentChannelId, params.currentGraphChannelId) : resolveActionTarget(params.toolParams, params.currentChannelId);
445
+ if (!to) return actionError(`${params.actionLabel} requires a target (to).`);
446
+ return to;
447
+ }
448
+ function resolveRequiredActionMessageTarget(params) {
449
+ const to = params.graphOnly ? resolveGraphActionTarget(params.toolParams, params.currentChannelId, params.currentGraphChannelId) : resolveActionTarget(params.toolParams, params.currentChannelId);
450
+ const messageId = resolveActionMessageId(params.toolParams);
451
+ if (!to || !messageId) return actionError(`${params.actionLabel} requires a target (to) and messageId.`);
452
+ return {
453
+ to,
454
+ messageId
455
+ };
456
+ }
457
+ function resolveRequiredActionPinnedMessageTarget(params) {
458
+ const to = params.graphOnly ? resolveGraphActionTarget(params.toolParams, params.currentChannelId, params.currentGraphChannelId) : resolveActionTarget(params.toolParams, params.currentChannelId);
459
+ const pinnedMessageId = resolveActionPinnedMessageId(params.toolParams);
460
+ if (!to || !pinnedMessageId) return actionError(`${params.actionLabel} requires a target (to) and pinnedMessageId.`);
461
+ return {
462
+ to,
463
+ pinnedMessageId
464
+ };
465
+ }
466
+ async function runWithRequiredActionTarget(params) {
467
+ const to = resolveRequiredActionTarget({
468
+ actionLabel: params.actionLabel,
469
+ toolParams: params.toolParams,
470
+ currentChannelId: params.currentChannelId,
471
+ currentGraphChannelId: params.currentGraphChannelId,
472
+ graphOnly: params.graphOnly
473
+ });
474
+ if (typeof to !== "string") return to;
475
+ return await params.run(to);
476
+ }
477
+ async function runWithRequiredActionMessageTarget(params) {
478
+ const target = resolveRequiredActionMessageTarget({
479
+ actionLabel: params.actionLabel,
480
+ toolParams: params.toolParams,
481
+ currentChannelId: params.currentChannelId,
482
+ currentGraphChannelId: params.currentGraphChannelId,
483
+ graphOnly: params.graphOnly
484
+ });
485
+ if ("isError" in target) return target;
486
+ return await params.run(target);
487
+ }
488
+ async function runWithRequiredActionPinnedMessageTarget(params) {
489
+ const target = resolveRequiredActionPinnedMessageTarget({
490
+ actionLabel: params.actionLabel,
491
+ toolParams: params.toolParams,
492
+ currentChannelId: params.currentChannelId,
493
+ currentGraphChannelId: params.currentGraphChannelId,
494
+ graphOnly: params.graphOnly
495
+ });
496
+ if ("isError" in target) return target;
497
+ return await params.run(target);
498
+ }
499
+ function describeMSTeamsMessageTool({ cfg }) {
500
+ const enabled = cfg.channels?.msteams?.enabled !== false && Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams));
501
+ return {
502
+ actions: enabled ? [
503
+ "upload-file",
504
+ "poll",
505
+ "edit",
506
+ "delete",
507
+ "pin",
508
+ "unpin",
509
+ "list-pins",
510
+ "read",
511
+ "react",
512
+ "reactions",
513
+ "search",
514
+ "member-info",
515
+ "channel-list",
516
+ "channel-info",
517
+ "addParticipant",
518
+ "removeParticipant",
519
+ "renameGroup"
520
+ ] : [],
521
+ capabilities: enabled ? ["presentation"] : [],
522
+ schema: enabled ? {
523
+ actions: ["unpin"],
524
+ properties: { pinnedMessageId: Type.Optional(Type.String({ description: "Pinned message resource ID for unpin (from pin or list-pins, not the chat message ID)." })) }
525
+ } : null
526
+ };
527
+ }
528
+ const msteamsChannelOutbound = {
529
+ deliveryMode: "direct",
530
+ chunker: chunkTextForOutbound,
531
+ chunkerMode: "markdown",
532
+ textChunkLimit: 4e3,
533
+ pollMaxOptions: 12,
534
+ deliveryCapabilities: { durableFinal: {
535
+ text: true,
536
+ media: true,
537
+ payload: true,
538
+ messageSendingHooks: true
539
+ } },
540
+ presentationCapabilities: MSTEAMS_PRESENTATION_CAPABILITIES,
541
+ ...createRuntimeOutboundDelegates({
542
+ getRuntime: loadMSTeamsChannelRuntime,
543
+ renderPresentation: { resolve: (runtime) => runtime.msteamsOutbound.renderPresentation },
544
+ sendPayload: { resolve: (runtime) => runtime.msteamsOutbound.sendPayload },
545
+ sendText: { resolve: (runtime) => runtime.msteamsOutbound.sendText },
546
+ sendMedia: { resolve: (runtime) => runtime.msteamsOutbound.sendMedia },
547
+ sendPoll: { resolve: (runtime) => runtime.msteamsOutbound.sendPoll }
548
+ })
549
+ };
550
+ const msteamsMessageAdapter = createChannelMessageAdapterFromOutbound({
551
+ id: "msteams",
552
+ outbound: msteamsChannelOutbound,
553
+ live: {
554
+ capabilities: {
555
+ draftPreview: true,
556
+ previewFinalization: true,
557
+ progressUpdates: true,
558
+ nativeStreaming: true
559
+ },
560
+ finalizer: { capabilities: {
561
+ finalEdit: true,
562
+ normalFallback: true,
563
+ previewReceipt: true
564
+ } }
565
+ }
566
+ });
567
+ const msteamsPlugin = createChatChannelPlugin({
568
+ base: {
569
+ id: "msteams",
570
+ meta: {
571
+ ...meta,
572
+ aliases: [...meta.aliases]
573
+ },
574
+ setupWizard: msteamsSetupWizard,
575
+ capabilities: {
576
+ chatTypes: [
577
+ "direct",
578
+ "channel",
579
+ "thread"
580
+ ],
581
+ polls: true,
582
+ threads: true,
583
+ media: true
584
+ },
585
+ streaming: { blockStreamingCoalesceDefaults: {
586
+ minChars: 1500,
587
+ idleMs: 1e3
588
+ } },
589
+ agentPrompt: { messageToolHints: () => ["- Adaptive Cards supported. Use `action=send` with `card={type,version,body}` to send rich cards.", "- MSTeams targeting: omit `target` to reply to the current conversation (auto-inferred). Explicit targets: `user:ID` or `user:Display Name` (requires Graph API) for DMs, `conversation:19:...@thread.tacv2` for groups/channels. Prefer IDs over display names for speed."] },
590
+ groups: { resolveToolPolicy: resolveMSTeamsGroupToolPolicy },
591
+ reload: { configPrefixes: ["channels.msteams"] },
592
+ configSchema: MSTeamsChannelConfigSchema,
593
+ config: {
594
+ ...msteamsConfigAdapter,
595
+ isConfigured: (_account, cfg) => Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)),
596
+ describeAccount: (account) => describeAccountSnapshot({
597
+ account,
598
+ configured: account.configured
599
+ })
600
+ },
601
+ approvalCapability: msTeamsApprovalAuth,
602
+ doctor: {
603
+ dmAllowFromMode: "topOnly",
604
+ groupModel: "hybrid",
605
+ groupAllowFromFallbackToAllowFrom: true,
606
+ warnOnEmptyGroupSenderAllowlist: true,
607
+ collectMutableAllowlistWarnings: collectMSTeamsMutableAllowlistWarnings
608
+ },
609
+ setup: msteamsSetupAdapter,
610
+ messaging: {
611
+ targetPrefixes: ["msteams", "teams"],
612
+ normalizeTarget: normalizeMSTeamsMessagingTarget,
613
+ resolveOutboundSessionRoute: (params) => resolveMSTeamsOutboundSessionRoute(params),
614
+ targetResolver: {
615
+ looksLikeId: (raw) => looksLikeMSTeamsTargetId(raw),
616
+ hint: "<conversationId|user:ID|conversation:ID>"
617
+ }
618
+ },
619
+ message: msteamsMessageAdapter,
620
+ directory: createChannelDirectoryAdapter({
621
+ self: async ({ cfg }) => {
622
+ const creds = resolveMSTeamsCredentials(cfg.channels?.msteams);
623
+ if (!creds) return null;
624
+ return {
625
+ kind: "user",
626
+ id: creds.appId,
627
+ name: creds.appId
628
+ };
629
+ },
630
+ listPeers: async ({ cfg, query, limit }) => listDirectoryEntriesFromSources({
631
+ kind: "user",
632
+ sources: [cfg.channels?.msteams?.allowFrom ?? [], Object.keys(cfg.channels?.msteams?.dms ?? {})],
633
+ query,
634
+ limit,
635
+ normalizeId: (raw) => {
636
+ const normalized = normalizeMSTeamsMessagingTarget(raw) ?? raw;
637
+ const lowered = normalized.toLowerCase();
638
+ if (lowered.startsWith("user:") || lowered.startsWith("conversation:")) return normalized;
639
+ return `user:${normalized}`;
640
+ }
641
+ }),
642
+ listGroups: async ({ cfg, query, limit }) => listDirectoryEntriesFromSources({
643
+ kind: "group",
644
+ sources: [Object.values(cfg.channels?.msteams?.teams ?? {}).flatMap((team) => Object.keys(team.channels ?? {}))],
645
+ query,
646
+ limit,
647
+ normalizeId: (raw) => `conversation:${raw.replace(/^conversation:/i, "").trim()}`
648
+ }),
649
+ ...createRuntimeDirectoryLiveAdapter({
650
+ getRuntime: loadMSTeamsChannelRuntime,
651
+ listPeersLive: (runtime) => runtime.listMSTeamsDirectoryPeersLive,
652
+ listGroupsLive: (runtime) => runtime.listMSTeamsDirectoryGroupsLive
653
+ })
654
+ }),
655
+ resolver: { resolveTargets: async ({ cfg, inputs, kind, runtime }) => {
656
+ const results = inputs.map((input) => ({
657
+ input,
658
+ resolved: false,
659
+ id: void 0,
660
+ name: void 0,
661
+ note: void 0
662
+ }));
663
+ const stripPrefix = (value) => normalizeMSTeamsUserInput(value);
664
+ const markPendingLookupFailed = (pending) => {
665
+ pending.forEach(({ index }) => {
666
+ const entry = results[index];
667
+ if (entry) entry.note = "lookup failed";
668
+ });
669
+ };
670
+ const resolvePending = async (pending, resolveEntries, applyResolvedEntry) => {
671
+ if (pending.length === 0) return;
672
+ try {
673
+ (await resolveEntries(pending.map((entry) => entry.query))).forEach((entry, idx) => {
674
+ const target = results[pending[idx]?.index ?? -1];
675
+ if (!target) return;
676
+ applyResolvedEntry(target, entry);
677
+ });
678
+ } catch (err) {
679
+ runtime.error?.(`msteams resolve failed: ${String(err)}`);
680
+ markPendingLookupFailed(pending);
681
+ }
682
+ };
683
+ if (kind === "user") {
684
+ const pending = [];
685
+ results.forEach((entry, index) => {
686
+ const trimmed = entry.input.trim();
687
+ if (!trimmed) {
688
+ entry.note = "empty input";
689
+ return;
690
+ }
691
+ const cleaned = stripPrefix(trimmed);
692
+ if (/^[0-9a-fA-F-]{16,}$/.test(cleaned) || cleaned.includes("@")) {
693
+ entry.resolved = true;
694
+ entry.id = cleaned;
695
+ return;
696
+ }
697
+ pending.push({
698
+ input: entry.input,
699
+ query: cleaned,
700
+ index
701
+ });
702
+ });
703
+ await resolvePending(pending, (entries) => resolveMSTeamsUserAllowlist({
704
+ cfg,
705
+ entries
706
+ }), (target, entry) => {
707
+ target.resolved = entry.resolved;
708
+ target.id = entry.id;
709
+ target.name = entry.name;
710
+ target.note = entry.note;
711
+ });
712
+ return results;
713
+ }
714
+ const pending = [];
715
+ results.forEach((entry, index) => {
716
+ const trimmed = entry.input.trim();
717
+ if (!trimmed) {
718
+ entry.note = "empty input";
719
+ return;
720
+ }
721
+ const conversationId = parseMSTeamsConversationId(trimmed);
722
+ if (conversationId !== null) {
723
+ entry.resolved = Boolean(conversationId);
724
+ entry.id = conversationId || void 0;
725
+ entry.note = conversationId ? "conversation id" : "empty conversation id";
726
+ return;
727
+ }
728
+ const parsed = parseMSTeamsTeamChannelInput(trimmed);
729
+ if (!parsed.team) {
730
+ entry.note = "missing team";
731
+ return;
732
+ }
733
+ const query = parsed.channel ? `${parsed.team}/${parsed.channel}` : parsed.team;
734
+ pending.push({
735
+ input: entry.input,
736
+ query,
737
+ index
738
+ });
739
+ });
740
+ await resolvePending(pending, (entries) => resolveMSTeamsChannelAllowlist({
741
+ cfg,
742
+ entries
743
+ }), (target, entry) => {
744
+ if (!entry.resolved || !entry.teamId) {
745
+ target.resolved = false;
746
+ target.note = entry.note;
747
+ return;
748
+ }
749
+ target.resolved = true;
750
+ if (entry.channelId) {
751
+ target.id = `${entry.teamId}/${entry.channelId}`;
752
+ target.name = entry.channelName && entry.teamName ? `${entry.teamName}/${entry.channelName}` : entry.channelName ?? entry.teamName;
753
+ } else {
754
+ target.id = entry.teamId;
755
+ target.name = entry.teamName;
756
+ target.note = "team id";
757
+ }
758
+ if (entry.note) target.note = entry.note;
759
+ });
760
+ return results;
761
+ } },
762
+ actions: {
763
+ describeMessageTool: describeMSTeamsMessageTool,
764
+ handleAction: async (ctx) => {
765
+ const presentation = ctx.action === "send" ? normalizeMessagePresentation(ctx.params.presentation) : void 0;
766
+ if (ctx.action === "send" && presentation) {
767
+ const card = buildMSTeamsPresentationCard({
768
+ presentation,
769
+ text: resolveActionContent(ctx.params)
770
+ });
771
+ return await runWithRequiredActionTarget({
772
+ actionLabel: "Card send",
773
+ toolParams: ctx.params,
774
+ run: async (to) => {
775
+ const { sendAdaptiveCardMSTeams } = await loadMSTeamsChannelRuntime();
776
+ const result = await sendAdaptiveCardMSTeams({
777
+ cfg: ctx.cfg,
778
+ to,
779
+ card
780
+ });
781
+ return jsonActionResultWithDetails({
782
+ ok: true,
783
+ channel: "msteams",
784
+ messageId: result.messageId,
785
+ conversationId: result.conversationId
786
+ }, {
787
+ ok: true,
788
+ channel: "msteams",
789
+ messageId: result.messageId
790
+ });
791
+ }
792
+ });
793
+ }
794
+ if (ctx.action === "upload-file") {
795
+ const mediaUrl = resolveActionUploadFilePath(ctx.params);
796
+ if (!mediaUrl) return actionError("Upload-file requires media, filePath, or path.");
797
+ return await runWithRequiredActionTarget({
798
+ actionLabel: "Upload-file",
799
+ toolParams: ctx.params,
800
+ currentChannelId: ctx.toolContext?.currentChannelId,
801
+ run: async (to) => {
802
+ const { sendMessageMSTeams } = await loadMSTeamsChannelRuntime();
803
+ const result = await sendMessageMSTeams({
804
+ cfg: ctx.cfg,
805
+ to,
806
+ text: resolveActionContent(ctx.params),
807
+ mediaUrl,
808
+ filename: readOptionalTrimmedString(ctx.params, "filename") ?? readOptionalTrimmedString(ctx.params, "title"),
809
+ mediaLocalRoots: ctx.mediaLocalRoots,
810
+ mediaReadFile: ctx.mediaReadFile
811
+ });
812
+ return jsonActionResultWithDetails({
813
+ ok: true,
814
+ channel: "msteams",
815
+ action: "upload-file",
816
+ messageId: result.messageId,
817
+ conversationId: result.conversationId,
818
+ ...result.pendingUploadId ? { pendingUploadId: result.pendingUploadId } : {}
819
+ }, {
820
+ ok: true,
821
+ channel: "msteams",
822
+ messageId: result.messageId,
823
+ ...result.pendingUploadId ? { pendingUploadId: result.pendingUploadId } : {}
824
+ });
825
+ }
826
+ });
827
+ }
828
+ if (ctx.action === "edit") {
829
+ const content = resolveActionContent(ctx.params);
830
+ if (!content) return actionError("Edit requires content.");
831
+ return await runWithRequiredActionMessageTarget({
832
+ actionLabel: "Edit",
833
+ toolParams: ctx.params,
834
+ currentChannelId: ctx.toolContext?.currentChannelId,
835
+ run: async (target) => {
836
+ const { editMessageMSTeams } = await loadMSTeamsChannelRuntime();
837
+ return jsonMSTeamsConversationResult((await editMessageMSTeams({
838
+ cfg: ctx.cfg,
839
+ to: target.to,
840
+ activityId: target.messageId,
841
+ text: content
842
+ })).conversationId);
843
+ }
844
+ });
845
+ }
846
+ if (ctx.action === "delete") return await runWithRequiredActionMessageTarget({
847
+ actionLabel: "Delete",
848
+ toolParams: ctx.params,
849
+ currentChannelId: ctx.toolContext?.currentChannelId,
850
+ run: async (target) => {
851
+ const { deleteMessageMSTeams } = await loadMSTeamsChannelRuntime();
852
+ return jsonMSTeamsConversationResult((await deleteMessageMSTeams({
853
+ cfg: ctx.cfg,
854
+ to: target.to,
855
+ activityId: target.messageId
856
+ })).conversationId);
857
+ }
858
+ });
859
+ if (ctx.action === "read") return await runWithRequiredActionMessageTarget({
860
+ actionLabel: "Read",
861
+ toolParams: ctx.params,
862
+ currentChannelId: ctx.toolContext?.currentChannelId,
863
+ currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
864
+ graphOnly: true,
865
+ run: async (target) => {
866
+ const { getMessageMSTeams } = await loadMSTeamsChannelRuntime();
867
+ return jsonMSTeamsOkActionResult("read", { message: await getMessageMSTeams({
868
+ cfg: ctx.cfg,
869
+ to: target.to,
870
+ messageId: target.messageId
871
+ }) });
872
+ }
873
+ });
874
+ if (ctx.action === "pin") return await runWithRequiredActionMessageTarget({
875
+ actionLabel: "Pin",
876
+ toolParams: ctx.params,
877
+ currentChannelId: ctx.toolContext?.currentChannelId,
878
+ currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
879
+ graphOnly: true,
880
+ run: async (target) => {
881
+ const { pinMessageMSTeams } = await loadMSTeamsChannelRuntime();
882
+ return jsonMSTeamsActionResult("pin", await pinMessageMSTeams({
883
+ cfg: ctx.cfg,
884
+ to: target.to,
885
+ messageId: target.messageId
886
+ }));
887
+ }
888
+ });
889
+ if (ctx.action === "unpin") return await runWithRequiredActionPinnedMessageTarget({
890
+ actionLabel: "Unpin",
891
+ toolParams: ctx.params,
892
+ currentChannelId: ctx.toolContext?.currentChannelId,
893
+ currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
894
+ graphOnly: true,
895
+ run: async (target) => {
896
+ const { unpinMessageMSTeams } = await loadMSTeamsChannelRuntime();
897
+ return jsonMSTeamsActionResult("unpin", await unpinMessageMSTeams({
898
+ cfg: ctx.cfg,
899
+ to: target.to,
900
+ pinnedMessageId: target.pinnedMessageId
901
+ }));
902
+ }
903
+ });
904
+ if (ctx.action === "list-pins") return await runWithRequiredActionTarget({
905
+ actionLabel: "List-pins",
906
+ toolParams: ctx.params,
907
+ currentChannelId: ctx.toolContext?.currentChannelId,
908
+ currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
909
+ graphOnly: true,
910
+ run: async (to) => {
911
+ const { listPinsMSTeams } = await loadMSTeamsChannelRuntime();
912
+ return jsonMSTeamsOkActionResult("list-pins", await listPinsMSTeams({
913
+ cfg: ctx.cfg,
914
+ to
915
+ }));
916
+ }
917
+ });
918
+ if (ctx.action === "react") return await runWithRequiredActionMessageTarget({
919
+ actionLabel: "React",
920
+ toolParams: ctx.params,
921
+ currentChannelId: ctx.toolContext?.currentChannelId,
922
+ currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
923
+ graphOnly: true,
924
+ run: async (target) => {
925
+ const emoji = typeof ctx.params.emoji === "string" ? ctx.params.emoji.trim() : "";
926
+ const remove = typeof ctx.params.remove === "boolean" ? ctx.params.remove : false;
927
+ if (!emoji) return {
928
+ isError: true,
929
+ content: [{
930
+ type: "text",
931
+ text: `React requires an emoji (reaction type). Valid types: ${MSTEAMS_REACTION_TYPES.join(", ")}.`
932
+ }],
933
+ details: {
934
+ error: "React requires an emoji (reaction type).",
935
+ validTypes: [...MSTEAMS_REACTION_TYPES]
936
+ }
937
+ };
938
+ if (remove) {
939
+ const { unreactMessageMSTeams } = await loadMSTeamsChannelRuntime();
940
+ return jsonMSTeamsActionResult("react", {
941
+ removed: true,
942
+ reactionType: emoji,
943
+ ...await unreactMessageMSTeams({
944
+ cfg: ctx.cfg,
945
+ to: target.to,
946
+ messageId: target.messageId,
947
+ reactionType: emoji
948
+ })
949
+ });
950
+ }
951
+ const { reactMessageMSTeams } = await loadMSTeamsChannelRuntime();
952
+ return jsonMSTeamsActionResult("react", {
953
+ reactionType: emoji,
954
+ ...await reactMessageMSTeams({
955
+ cfg: ctx.cfg,
956
+ to: target.to,
957
+ messageId: target.messageId,
958
+ reactionType: emoji
959
+ })
960
+ });
961
+ }
962
+ });
963
+ if (ctx.action === "reactions") return await runWithRequiredActionMessageTarget({
964
+ actionLabel: "Reactions",
965
+ toolParams: ctx.params,
966
+ currentChannelId: ctx.toolContext?.currentChannelId,
967
+ currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
968
+ graphOnly: true,
969
+ run: async (target) => {
970
+ const { listReactionsMSTeams } = await loadMSTeamsChannelRuntime();
971
+ return jsonMSTeamsOkActionResult("reactions", await listReactionsMSTeams({
972
+ cfg: ctx.cfg,
973
+ to: target.to,
974
+ messageId: target.messageId
975
+ }));
976
+ }
977
+ });
978
+ if (ctx.action === "search") return await runWithRequiredActionTarget({
979
+ actionLabel: "Search",
980
+ toolParams: ctx.params,
981
+ currentChannelId: ctx.toolContext?.currentChannelId,
982
+ currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
983
+ graphOnly: true,
984
+ run: async (to) => {
985
+ const query = resolveActionQuery(ctx.params);
986
+ if (!query) return actionError("Search requires a target (to) and query.");
987
+ const limit = typeof ctx.params.limit === "number" ? ctx.params.limit : void 0;
988
+ const from = typeof ctx.params.from === "string" ? ctx.params.from.trim() : void 0;
989
+ const { searchMessagesMSTeams } = await loadMSTeamsChannelRuntime();
990
+ return jsonMSTeamsOkActionResult("search", await searchMessagesMSTeams({
991
+ cfg: ctx.cfg,
992
+ to,
993
+ query,
994
+ from: from || void 0,
995
+ limit
996
+ }));
997
+ }
998
+ });
999
+ if (ctx.action === "member-info") {
1000
+ const userId = normalizeOptionalString(ctx.params.userId) ?? "";
1001
+ if (!userId) return actionError("member-info requires a userId.");
1002
+ const { getMemberInfoMSTeams } = await loadMSTeamsChannelRuntime();
1003
+ return jsonMSTeamsOkActionResult("member-info", await getMemberInfoMSTeams({
1004
+ cfg: ctx.cfg,
1005
+ userId
1006
+ }));
1007
+ }
1008
+ if (ctx.action === "channel-list") {
1009
+ const teamId = normalizeOptionalString(ctx.params.teamId) ?? "";
1010
+ if (!teamId) return actionError("channel-list requires a teamId.");
1011
+ const { listChannelsMSTeams } = await loadMSTeamsChannelRuntime();
1012
+ return jsonMSTeamsOkActionResult("channel-list", await listChannelsMSTeams({
1013
+ cfg: ctx.cfg,
1014
+ teamId
1015
+ }));
1016
+ }
1017
+ if (ctx.action === "channel-info") {
1018
+ const teamId = normalizeOptionalString(ctx.params.teamId) ?? "";
1019
+ const channelId = normalizeOptionalString(ctx.params.channelId) ?? "";
1020
+ if (!teamId || !channelId) return actionError("channel-info requires teamId and channelId.");
1021
+ const { getChannelInfoMSTeams } = await loadMSTeamsChannelRuntime();
1022
+ return jsonMSTeamsOkActionResult("channel-info", { channelInfo: (await getChannelInfoMSTeams({
1023
+ cfg: ctx.cfg,
1024
+ teamId,
1025
+ channelId
1026
+ })).channel });
1027
+ }
1028
+ if (ctx.action === "addParticipant") {
1029
+ const userId = typeof ctx.params.userId === "string" ? ctx.params.userId.trim() : "";
1030
+ if (!userId) return actionError("addParticipant requires a userId.");
1031
+ return await runWithRequiredActionTarget({
1032
+ actionLabel: "addParticipant",
1033
+ toolParams: ctx.params,
1034
+ currentChannelId: ctx.toolContext?.currentChannelId,
1035
+ run: async (to) => {
1036
+ const role = readOptionalTrimmedString(ctx.params, "role");
1037
+ const { addParticipantMSTeams } = await loadMSTeamsChannelRuntime();
1038
+ return jsonMSTeamsOkActionResult("addParticipant", await addParticipantMSTeams({
1039
+ cfg: ctx.cfg,
1040
+ to,
1041
+ userId,
1042
+ role
1043
+ }));
1044
+ }
1045
+ });
1046
+ }
1047
+ if (ctx.action === "removeParticipant") {
1048
+ const userId = typeof ctx.params.userId === "string" ? ctx.params.userId.trim() : "";
1049
+ if (!userId) return actionError("removeParticipant requires a userId.");
1050
+ return await runWithRequiredActionTarget({
1051
+ actionLabel: "removeParticipant",
1052
+ toolParams: ctx.params,
1053
+ currentChannelId: ctx.toolContext?.currentChannelId,
1054
+ run: async (to) => {
1055
+ const { removeParticipantMSTeams } = await loadMSTeamsChannelRuntime();
1056
+ return jsonMSTeamsOkActionResult("removeParticipant", await removeParticipantMSTeams({
1057
+ cfg: ctx.cfg,
1058
+ to,
1059
+ userId
1060
+ }));
1061
+ }
1062
+ });
1063
+ }
1064
+ if (ctx.action === "renameGroup") {
1065
+ const name = typeof ctx.params.name === "string" ? ctx.params.name.trim() : "";
1066
+ if (!name) return actionError("renameGroup requires a name.");
1067
+ return await runWithRequiredActionTarget({
1068
+ actionLabel: "renameGroup",
1069
+ toolParams: ctx.params,
1070
+ currentChannelId: ctx.toolContext?.currentChannelId,
1071
+ run: async (to) => {
1072
+ const { renameGroupMSTeams } = await loadMSTeamsChannelRuntime();
1073
+ return jsonMSTeamsOkActionResult("renameGroup", await renameGroupMSTeams({
1074
+ cfg: ctx.cfg,
1075
+ to,
1076
+ name
1077
+ }));
1078
+ }
1079
+ });
1080
+ }
1081
+ return null;
1082
+ }
1083
+ },
1084
+ status: createComputedAccountStatusAdapter({
1085
+ defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, { port: null }),
1086
+ buildChannelSummary: ({ snapshot }) => buildProbeChannelStatusSummary(snapshot, { port: snapshot.port ?? null }),
1087
+ probeAccount: async ({ cfg }) => await (await loadMSTeamsChannelRuntime()).probeMSTeams(cfg.channels?.msteams),
1088
+ formatCapabilitiesProbe: ({ probe }) => {
1089
+ const teamsProbe = probe;
1090
+ const lines = [];
1091
+ const appId = typeof teamsProbe?.appId === "string" ? teamsProbe.appId.trim() : "";
1092
+ if (appId) lines.push({ text: `App: ${appId}` });
1093
+ const graph = teamsProbe?.graph;
1094
+ if (graph) {
1095
+ const roles = Array.isArray(graph.roles) ? graph.roles.map((role) => role.trim()).filter(Boolean) : [];
1096
+ const scopes = Array.isArray(graph.scopes) ? graph.scopes.map((scope) => scope.trim()).filter(Boolean) : [];
1097
+ const formatPermission = (permission) => {
1098
+ const hint = TEAMS_GRAPH_PERMISSION_HINTS[permission];
1099
+ return hint ? `${permission} (${hint})` : permission;
1100
+ };
1101
+ if (!graph.ok) lines.push({
1102
+ text: `Graph: ${graph.error ?? "failed"}`,
1103
+ tone: "error"
1104
+ });
1105
+ else if (roles.length > 0 || scopes.length > 0) {
1106
+ if (roles.length > 0) lines.push({ text: `Graph roles: ${roles.map(formatPermission).join(", ")}` });
1107
+ if (scopes.length > 0) lines.push({ text: `Graph scopes: ${scopes.map(formatPermission).join(", ")}` });
1108
+ } else if (graph.ok) lines.push({ text: "Graph: ok" });
1109
+ }
1110
+ return lines;
1111
+ },
1112
+ resolveAccountSnapshot: ({ account, runtime }) => ({
1113
+ accountId: account.accountId,
1114
+ enabled: account.enabled,
1115
+ configured: account.configured,
1116
+ extra: { port: runtime?.port ?? null }
1117
+ })
1118
+ }),
1119
+ gateway: { startAccount: async (ctx) => {
1120
+ const { monitorMSTeamsProvider } = await import("./src-tvpsGYPV.js");
1121
+ const port = ctx.cfg.channels?.msteams?.webhook?.port ?? 3978;
1122
+ ctx.setStatus({
1123
+ accountId: ctx.accountId,
1124
+ port
1125
+ });
1126
+ ctx.log?.info(`starting provider (port ${port})`);
1127
+ return monitorMSTeamsProvider({
1128
+ cfg: ctx.cfg,
1129
+ runtime: ctx.runtime,
1130
+ abortSignal: ctx.abortSignal
1131
+ });
1132
+ } }
1133
+ },
1134
+ security: { collectWarnings: projectConfigWarningCollector(collectMSTeamsSecurityWarnings) },
1135
+ pairing: { text: {
1136
+ idLabel: "msteamsUserId",
1137
+ message: PAIRING_APPROVED_MESSAGE,
1138
+ normalizeAllowEntry: createPairingPrefixStripper(/^(msteams|user):/i),
1139
+ notify: async ({ cfg, id, message }) => {
1140
+ const { sendMessageMSTeams } = await loadMSTeamsChannelRuntime();
1141
+ await sendMessageMSTeams({
1142
+ cfg,
1143
+ to: id,
1144
+ text: message
1145
+ });
1146
+ }
1147
+ } },
1148
+ threading: { buildToolContext: ({ context, hasRepliedRef }) => {
1149
+ const nativeChannelId = context.NativeChannelId?.trim();
1150
+ const hasChannelRoute = Boolean(nativeChannelId && nativeChannelId.includes("/"));
1151
+ return {
1152
+ currentChannelId: normalizeOptionalString(context.To),
1153
+ currentGraphChannelId: hasChannelRoute ? nativeChannelId : void 0,
1154
+ currentThreadTs: context.ReplyToId,
1155
+ hasRepliedRef
1156
+ };
1157
+ } },
1158
+ outbound: msteamsChannelOutbound
1159
+ });
1160
+ //#endregion
1161
+ export { resolveMSTeamsReplyPolicy as a, resolveMSTeamsAllowlistMatch as i, MSTEAMS_PRESENTATION_CAPABILITIES as n, resolveMSTeamsRouteConfig as o, buildMSTeamsPresentationCard as r, msteamsPlugin as t };