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