@openclaw/msteams 2026.1.29 → 2026.2.2

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 (50) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/index.ts +0 -1
  3. package/openclaw.plugin.json +1 -3
  4. package/package.json +13 -10
  5. package/src/attachments/download.ts +98 -21
  6. package/src/attachments/graph.ts +50 -16
  7. package/src/attachments/html.ts +23 -9
  8. package/src/attachments/shared.ts +74 -18
  9. package/src/attachments.test.ts +37 -2
  10. package/src/channel.directory.test.ts +7 -5
  11. package/src/channel.ts +46 -23
  12. package/src/conversation-store-fs.test.ts +7 -8
  13. package/src/conversation-store-fs.ts +15 -5
  14. package/src/conversation-store-memory.ts +3 -1
  15. package/src/directory-live.ts +41 -15
  16. package/src/errors.test.ts +0 -1
  17. package/src/errors.ts +48 -16
  18. package/src/file-consent-helpers.test.ts +12 -3
  19. package/src/file-consent.ts +6 -2
  20. package/src/graph-chat.ts +5 -4
  21. package/src/graph-upload.ts +23 -15
  22. package/src/inbound.test.ts +0 -1
  23. package/src/inbound.ts +15 -5
  24. package/src/media-helpers.test.ts +9 -6
  25. package/src/media-helpers.ts +15 -6
  26. package/src/messenger.test.ts +7 -4
  27. package/src/messenger.ts +55 -20
  28. package/src/monitor-handler/inbound-media.ts +7 -2
  29. package/src/monitor-handler/message-handler.ts +66 -55
  30. package/src/monitor-handler.ts +3 -7
  31. package/src/monitor.ts +19 -14
  32. package/src/onboarding.ts +10 -11
  33. package/src/outbound.ts +0 -1
  34. package/src/pending-uploads.ts +7 -5
  35. package/src/policy.test.ts +1 -2
  36. package/src/policy.ts +39 -13
  37. package/src/polls-store-memory.ts +3 -1
  38. package/src/polls-store.test.ts +1 -3
  39. package/src/polls.test.ts +5 -6
  40. package/src/polls.ts +24 -9
  41. package/src/probe.test.ts +4 -3
  42. package/src/probe.ts +18 -10
  43. package/src/reply-dispatcher.ts +5 -3
  44. package/src/resolve-allowlist.ts +39 -19
  45. package/src/send-context.ts +12 -4
  46. package/src/send.ts +49 -19
  47. package/src/sent-message-cache.test.ts +0 -1
  48. package/src/sent-message-cache.ts +9 -3
  49. package/src/storage.ts +6 -3
  50. package/src/store-fs.ts +6 -3
package/src/errors.ts CHANGED
@@ -1,12 +1,22 @@
1
1
  export function formatUnknownError(err: unknown): string {
2
- if (err instanceof Error) return err.message;
3
- if (typeof err === "string") return err;
4
- if (err === null) return "null";
5
- if (err === undefined) return "undefined";
2
+ if (err instanceof Error) {
3
+ return err.message;
4
+ }
5
+ if (typeof err === "string") {
6
+ return err;
7
+ }
8
+ if (err === null) {
9
+ return "null";
10
+ }
11
+ if (err === undefined) {
12
+ return "undefined";
13
+ }
6
14
  if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") {
7
15
  return String(err);
8
16
  }
9
- if (typeof err === "symbol") return err.description ?? err.toString();
17
+ if (typeof err === "symbol") {
18
+ return err.description ?? err.toString();
19
+ }
10
20
  if (typeof err === "function") {
11
21
  return err.name ? `[function ${err.name}]` : "[function]";
12
22
  }
@@ -22,21 +32,31 @@ function isRecord(value: unknown): value is Record<string, unknown> {
22
32
  }
23
33
 
24
34
  function extractStatusCode(err: unknown): number | null {
25
- if (!isRecord(err)) return null;
35
+ if (!isRecord(err)) {
36
+ return null;
37
+ }
26
38
  const direct = err.statusCode ?? err.status;
27
- if (typeof direct === "number" && Number.isFinite(direct)) return direct;
39
+ if (typeof direct === "number" && Number.isFinite(direct)) {
40
+ return direct;
41
+ }
28
42
  if (typeof direct === "string") {
29
43
  const parsed = Number.parseInt(direct, 10);
30
- if (Number.isFinite(parsed)) return parsed;
44
+ if (Number.isFinite(parsed)) {
45
+ return parsed;
46
+ }
31
47
  }
32
48
 
33
49
  const response = err.response;
34
50
  if (isRecord(response)) {
35
51
  const status = response.status;
36
- if (typeof status === "number" && Number.isFinite(status)) return status;
52
+ if (typeof status === "number" && Number.isFinite(status)) {
53
+ return status;
54
+ }
37
55
  if (typeof status === "string") {
38
56
  const parsed = Number.parseInt(status, 10);
39
- if (Number.isFinite(parsed)) return parsed;
57
+ if (Number.isFinite(parsed)) {
58
+ return parsed;
59
+ }
40
60
  }
41
61
  }
42
62
 
@@ -44,7 +64,9 @@ function extractStatusCode(err: unknown): number | null {
44
64
  }
45
65
 
46
66
  function extractRetryAfterMs(err: unknown): number | null {
47
- if (!isRecord(err)) return null;
67
+ if (!isRecord(err)) {
68
+ return null;
69
+ }
48
70
 
49
71
  const direct = err.retryAfterMs ?? err.retry_after_ms;
50
72
  if (typeof direct === "number" && Number.isFinite(direct) && direct >= 0) {
@@ -57,20 +79,28 @@ function extractRetryAfterMs(err: unknown): number | null {
57
79
  }
58
80
  if (typeof retryAfter === "string") {
59
81
  const parsed = Number.parseFloat(retryAfter);
60
- if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1000;
82
+ if (Number.isFinite(parsed) && parsed >= 0) {
83
+ return parsed * 1000;
84
+ }
61
85
  }
62
86
 
63
87
  const response = err.response;
64
- if (!isRecord(response)) return null;
88
+ if (!isRecord(response)) {
89
+ return null;
90
+ }
65
91
 
66
92
  const headers = response.headers;
67
- if (!headers) return null;
93
+ if (!headers) {
94
+ return null;
95
+ }
68
96
 
69
97
  if (isRecord(headers)) {
70
98
  const raw = headers["retry-after"] ?? headers["Retry-After"];
71
99
  if (typeof raw === "string") {
72
100
  const parsed = Number.parseFloat(raw);
73
- if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1000;
101
+ if (Number.isFinite(parsed) && parsed >= 0) {
102
+ return parsed * 1000;
103
+ }
74
104
  }
75
105
  }
76
106
 
@@ -84,7 +114,9 @@ function extractRetryAfterMs(err: unknown): number | null {
84
114
  const raw = (headers as { get: (name: string) => string | null }).get("retry-after");
85
115
  if (raw) {
86
116
  const parsed = Number.parseFloat(raw);
87
- if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1000;
117
+ if (Number.isFinite(parsed) && parsed >= 0) {
118
+ return parsed * 1000;
119
+ }
88
120
  }
89
121
  }
90
122
 
@@ -186,7 +186,10 @@ describe("prepareFileConsentActivity", () => {
186
186
  conversationId: "conv456",
187
187
  });
188
188
 
189
- const attachment = (result.activity.attachments as unknown[])[0] as Record<string, { description: string }>;
189
+ const attachment = (result.activity.attachments as unknown[])[0] as Record<
190
+ string,
191
+ { description: string }
192
+ >;
190
193
  expect(attachment.content.description).toBe("File: document.docx");
191
194
  });
192
195
 
@@ -201,7 +204,10 @@ describe("prepareFileConsentActivity", () => {
201
204
  description: "Q4 Financial Report",
202
205
  });
203
206
 
204
- const attachment = (result.activity.attachments as unknown[])[0] as Record<string, { description: string }>;
207
+ const attachment = (result.activity.attachments as unknown[])[0] as Record<
208
+ string,
209
+ { description: string }
210
+ >;
205
211
  expect(attachment.content.description).toBe("Q4 Financial Report");
206
212
  });
207
213
 
@@ -215,7 +221,10 @@ describe("prepareFileConsentActivity", () => {
215
221
  conversationId: "conv000",
216
222
  });
217
223
 
218
- const attachment = (result.activity.attachments as unknown[])[0] as Record<string, { acceptContext: { uploadId: string } }>;
224
+ const attachment = (result.activity.attachments as unknown[])[0] as Record<
225
+ string,
226
+ { acceptContext: { uploadId: string } }
227
+ >;
219
228
  expect(attachment.content.acceptContext.uploadId).toBe(mockUploadId);
220
229
  });
221
230
 
@@ -78,7 +78,9 @@ export function parseFileConsentInvoke(activity: {
78
78
  name?: string;
79
79
  value?: unknown;
80
80
  }): FileConsentResponse | null {
81
- if (activity.name !== "fileConsent/invoke") return null;
81
+ if (activity.name !== "fileConsent/invoke") {
82
+ return null;
83
+ }
82
84
 
83
85
  const value = activity.value as {
84
86
  type?: string;
@@ -87,7 +89,9 @@ export function parseFileConsentInvoke(activity: {
87
89
  context?: Record<string, unknown>;
88
90
  };
89
91
 
90
- if (value?.type !== "fileUpload") return null;
92
+ if (value?.type !== "fileUpload") {
93
+ return null;
94
+ }
91
95
 
92
96
  return {
93
97
  action: value.action === "accept" ? "accept" : "decline",
package/src/graph-chat.ts CHANGED
@@ -31,10 +31,11 @@ export function buildTeamsFileInfoCard(file: DriveItemProperties): {
31
31
  // Extract unique ID from eTag (remove quotes, braces, and version suffix)
32
32
  // Example eTag formats: "{GUID},version" or "\"{GUID},version\""
33
33
  const rawETag = file.eTag;
34
- const uniqueId = rawETag
35
- .replace(/^["']|["']$/g, "") // Remove outer quotes
36
- .replace(/[{}]/g, "") // Remove curly braces
37
- .split(",")[0] ?? rawETag; // Take the GUID part before comma
34
+ const uniqueId =
35
+ rawETag
36
+ .replace(/^["']|["']$/g, "") // Remove outer quotes
37
+ .replace(/[{}]/g, "") // Remove curly braces
38
+ .split(",")[0] ?? rawETag; // Take the GUID part before comma
38
39
 
39
40
  // Extract file extension from filename
40
41
  const lastDot = file.name.lastIndexOf(".");
@@ -182,14 +182,17 @@ export async function uploadToSharePoint(params: {
182
182
  // Use "OpenClawShared" folder to organize bot-uploaded files
183
183
  const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`;
184
184
 
185
- const res = await fetchFn(`${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content`, {
186
- method: "PUT",
187
- headers: {
188
- Authorization: `Bearer ${token}`,
189
- "Content-Type": params.contentType ?? "application/octet-stream",
185
+ const res = await fetchFn(
186
+ `${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content`,
187
+ {
188
+ method: "PUT",
189
+ headers: {
190
+ Authorization: `Bearer ${token}`,
191
+ "Content-Type": params.contentType ?? "application/octet-stream",
192
+ },
193
+ body: new Uint8Array(params.buffer),
190
194
  },
191
- body: new Uint8Array(params.buffer),
192
- });
195
+ );
193
196
 
194
197
  if (!res.ok) {
195
198
  const body = await res.text().catch(() => "");
@@ -342,18 +345,23 @@ export async function createSharePointSharingLink(params: {
342
345
  body.recipients = params.recipientObjectIds.map((id) => ({ objectId: id }));
343
346
  }
344
347
 
345
- const res = await fetchFn(`${apiRoot}/sites/${params.siteId}/drive/items/${params.itemId}/createLink`, {
346
- method: "POST",
347
- headers: {
348
- Authorization: `Bearer ${token}`,
349
- "Content-Type": "application/json",
348
+ const res = await fetchFn(
349
+ `${apiRoot}/sites/${params.siteId}/drive/items/${params.itemId}/createLink`,
350
+ {
351
+ method: "POST",
352
+ headers: {
353
+ Authorization: `Bearer ${token}`,
354
+ "Content-Type": "application/json",
355
+ },
356
+ body: JSON.stringify(body),
350
357
  },
351
- body: JSON.stringify(body),
352
- });
358
+ );
353
359
 
354
360
  if (!res.ok) {
355
361
  const respBody = await res.text().catch(() => "");
356
- throw new Error(`Create SharePoint sharing link failed: ${res.status} ${res.statusText} - ${respBody}`);
362
+ throw new Error(
363
+ `Create SharePoint sharing link failed: ${res.status} ${res.statusText} - ${respBody}`,
364
+ );
357
365
  }
358
366
 
359
367
  const data = (await res.json()) as {
@@ -1,5 +1,4 @@
1
1
  import { describe, expect, it } from "vitest";
2
-
3
2
  import {
4
3
  normalizeMSTeamsConversationId,
5
4
  parseMSTeamsActivityTimestamp,
package/src/inbound.ts CHANGED
@@ -11,16 +11,24 @@ export function normalizeMSTeamsConversationId(raw: string): string {
11
11
  }
12
12
 
13
13
  export function extractMSTeamsConversationMessageId(raw: string): string | undefined {
14
- if (!raw) return undefined;
14
+ if (!raw) {
15
+ return undefined;
16
+ }
15
17
  const match = /(?:^|;)messageid=([^;]+)/i.exec(raw);
16
18
  const value = match?.[1]?.trim() ?? "";
17
19
  return value || undefined;
18
20
  }
19
21
 
20
22
  export function parseMSTeamsActivityTimestamp(value: unknown): Date | undefined {
21
- if (!value) return undefined;
22
- if (value instanceof Date) return value;
23
- if (typeof value !== "string") return undefined;
23
+ if (!value) {
24
+ return undefined;
25
+ }
26
+ if (value instanceof Date) {
27
+ return value;
28
+ }
29
+ if (typeof value !== "string") {
30
+ return undefined;
31
+ }
24
32
  const date = new Date(value);
25
33
  return Number.isNaN(date.getTime()) ? undefined : date;
26
34
  }
@@ -32,7 +40,9 @@ export function stripMSTeamsMentionTags(text: string): string {
32
40
 
33
41
  export function wasMSTeamsBotMentioned(activity: MentionableActivity): boolean {
34
42
  const botId = activity.recipient?.id;
35
- if (!botId) return false;
43
+ if (!botId) {
44
+ return false;
45
+ }
36
46
  const entities = activity.entities ?? [];
37
47
  return entities.some((e) => e.type === "mention" && e.mentioned?.id === botId);
38
48
  }
@@ -1,5 +1,4 @@
1
1
  import { describe, expect, it } from "vitest";
2
-
3
2
  import { extractFilename, extractMessageId, getMimeType, isLocalPath } from "./media-helpers.js";
4
3
 
5
4
  describe("msteams media-helpers", () => {
@@ -46,7 +45,9 @@ describe("msteams media-helpers", () => {
46
45
 
47
46
  it("defaults to application/octet-stream for unknown extensions", async () => {
48
47
  expect(await getMimeType("https://example.com/image")).toBe("application/octet-stream");
49
- expect(await getMimeType("https://example.com/image.unknown")).toBe("application/octet-stream");
48
+ expect(await getMimeType("https://example.com/image.unknown")).toBe(
49
+ "application/octet-stream",
50
+ );
50
51
  });
51
52
 
52
53
  it("is case-insensitive", async () => {
@@ -110,15 +111,17 @@ describe("msteams media-helpers", () => {
110
111
 
111
112
  it("extracts original filename with uppercase UUID", async () => {
112
113
  expect(
113
- await extractFilename("/media/inbound/Document---A1B2C3D4-E5F6-7890-ABCD-EF1234567890.docx"),
114
+ await extractFilename(
115
+ "/media/inbound/Document---A1B2C3D4-E5F6-7890-ABCD-EF1234567890.docx",
116
+ ),
114
117
  ).toBe("Document.docx");
115
118
  });
116
119
 
117
120
  it("falls back to UUID filename for legacy paths", async () => {
118
121
  // UUID-only filename (legacy format, no embedded name)
119
- expect(
120
- await extractFilename("/media/inbound/a1b2c3d4-e5f6-7890-abcd-ef1234567890.pdf"),
121
- ).toBe("a1b2c3d4-e5f6-7890-abcd-ef1234567890.pdf");
122
+ expect(await extractFilename("/media/inbound/a1b2c3d4-e5f6-7890-abcd-ef1234567890.pdf")).toBe(
123
+ "a1b2c3d4-e5f6-7890-abcd-ef1234567890.pdf",
124
+ );
122
125
  });
123
126
 
124
127
  it("handles --- in filename without valid UUID pattern", async () => {
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import path from "node:path";
6
-
7
6
  import {
8
7
  detectMime,
9
8
  extensionForMime,
@@ -19,7 +18,9 @@ export async function getMimeType(url: string): Promise<string> {
19
18
  // Handle data URLs: data:image/png;base64,...
20
19
  if (url.startsWith("data:")) {
21
20
  const match = url.match(/^data:([^;,]+)/);
22
- if (match?.[1]) return match[1];
21
+ if (match?.[1]) {
22
+ return match[1];
23
+ }
23
24
  }
24
25
 
25
26
  // Use shared MIME detection (extension-based for URLs)
@@ -46,7 +47,9 @@ export async function extractFilename(url: string): Promise<string> {
46
47
  const pathname = new URL(url).pathname;
47
48
  const basename = path.basename(pathname);
48
49
  const existingExt = getFileExtension(pathname);
49
- if (basename && existingExt) return basename;
50
+ if (basename && existingExt) {
51
+ return basename;
52
+ }
50
53
  // No extension in URL, derive from MIME
51
54
  const mime = await getMimeType(url);
52
55
  const ext = extensionForMime(mime) ?? ".bin";
@@ -69,9 +72,15 @@ export function isLocalPath(url: string): boolean {
69
72
  * Extract the message ID from a Bot Framework response.
70
73
  */
71
74
  export function extractMessageId(response: unknown): string | null {
72
- if (!response || typeof response !== "object") return null;
73
- if (!("id" in response)) return null;
75
+ if (!response || typeof response !== "object") {
76
+ return null;
77
+ }
78
+ if (!("id" in response)) {
79
+ return null;
80
+ }
74
81
  const { id } = response as { id?: unknown };
75
- if (typeof id !== "string" || !id) return null;
82
+ if (typeof id !== "string" || !id) {
83
+ return null;
84
+ }
76
85
  return id;
77
86
  }
@@ -1,6 +1,5 @@
1
- import { beforeEach, describe, expect, it } from "vitest";
2
-
3
1
  import { SILENT_REPLY_TOKEN, type PluginRuntime } from "openclaw/plugin-sdk";
2
+ import { beforeEach, describe, expect, it } from "vitest";
4
3
  import type { StoredConversationReference } from "./conversation-store.js";
5
4
  import {
6
5
  type MSTeamsAdapter,
@@ -10,8 +9,12 @@ import {
10
9
  import { setMSTeamsRuntime } from "./runtime.js";
11
10
 
12
11
  const chunkMarkdownText = (text: string, limit: number) => {
13
- if (!text) return [];
14
- if (limit <= 0 || text.length <= limit) return [text];
12
+ if (!text) {
13
+ return [];
14
+ }
15
+ if (limit <= 0 || text.length <= limit) {
16
+ return [text];
17
+ }
15
18
  const chunks: string[] = [];
16
19
  for (let index = 0; index < text.length; index += limit) {
17
20
  chunks.push(text.slice(index, index + limit));
package/src/messenger.ts CHANGED
@@ -134,7 +134,9 @@ function pushTextMessages(
134
134
  chunkMode: ChunkMode;
135
135
  },
136
136
  ) {
137
- if (!text) return;
137
+ if (!text) {
138
+ return;
139
+ }
138
140
  if (opts.chunkText) {
139
141
  for (const chunk of getMSTeamsRuntime().channel.text.chunkMarkdownTextWithMode(
140
142
  text,
@@ -142,26 +144,33 @@ function pushTextMessages(
142
144
  opts.chunkMode,
143
145
  )) {
144
146
  const trimmed = chunk.trim();
145
- if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) continue;
147
+ if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) {
148
+ continue;
149
+ }
146
150
  out.push({ text: trimmed });
147
151
  }
148
152
  return;
149
153
  }
150
154
 
151
155
  const trimmed = text.trim();
152
- if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) return;
156
+ if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) {
157
+ return;
158
+ }
153
159
  out.push({ text: trimmed });
154
160
  }
155
161
 
156
-
157
162
  function clampMs(value: number, maxMs: number): number {
158
- if (!Number.isFinite(value) || value < 0) return 0;
163
+ if (!Number.isFinite(value) || value < 0) {
164
+ return 0;
165
+ }
159
166
  return Math.min(value, maxMs);
160
167
  }
161
168
 
162
169
  async function sleep(ms: number): Promise<void> {
163
170
  const delay = Math.max(0, ms);
164
- if (delay === 0) return;
171
+ if (delay === 0) {
172
+ return;
173
+ }
165
174
  await new Promise<void>((resolve) => {
166
175
  setTimeout(resolve, delay);
167
176
  });
@@ -220,7 +229,9 @@ export function renderReplyPayloadsToMessages(
220
229
  tableMode,
221
230
  );
222
231
 
223
- if (!text && mediaList.length === 0) continue;
232
+ if (!text && mediaList.length === 0) {
233
+ continue;
234
+ }
224
235
 
225
236
  if (mediaList.length === 0) {
226
237
  pushTextMessages(out, text, { chunkText, chunkLimit, chunkMode });
@@ -234,7 +245,9 @@ export function renderReplyPayloadsToMessages(
234
245
  out.push({ text: text || undefined, mediaUrl: firstMedia });
235
246
  // Additional media URLs as separate messages
236
247
  for (let i = 1; i < mediaList.length; i++) {
237
- if (mediaList[i]) out.push({ mediaUrl: mediaList[i] });
248
+ if (mediaList[i]) {
249
+ out.push({ mediaUrl: mediaList[i] });
250
+ }
238
251
  }
239
252
  } else {
240
253
  pushTextMessages(out, text, { chunkText, chunkLimit, chunkMode });
@@ -245,7 +258,9 @@ export function renderReplyPayloadsToMessages(
245
258
  // mediaMode === "split"
246
259
  pushTextMessages(out, text, { chunkText, chunkLimit, chunkMode });
247
260
  for (const mediaUrl of mediaList) {
248
- if (!mediaUrl) continue;
261
+ if (!mediaUrl) {
262
+ continue;
263
+ }
249
264
  out.push({ mediaUrl });
250
265
  }
251
266
  }
@@ -283,12 +298,14 @@ async function buildActivity(
283
298
  const isPersonal = conversationType === "personal";
284
299
  const isImage = contentType?.startsWith("image/") ?? false;
285
300
 
286
- if (requiresFileConsent({
287
- conversationType,
288
- contentType,
289
- bufferSize: media.buffer.length,
290
- thresholdBytes: FILE_CONSENT_THRESHOLD_BYTES,
291
- })) {
301
+ if (
302
+ requiresFileConsent({
303
+ conversationType,
304
+ contentType,
305
+ bufferSize: media.buffer.length,
306
+ thresholdBytes: FILE_CONSENT_THRESHOLD_BYTES,
307
+ })
308
+ ) {
292
309
  // Large file or non-image in personal chat: use FileConsentCard flow
293
310
  const conversationId = conversationRef.conversation?.id ?? "unknown";
294
311
  const { activity: consentActivity } = prepareFileConsentActivity({
@@ -382,7 +399,9 @@ export async function sendMSTeamsMessages(params: {
382
399
  const messages = params.messages.filter(
383
400
  (m) => (m.text && m.text.trim().length > 0) || m.mediaUrl,
384
401
  );
385
- if (messages.length === 0) return [];
402
+ if (messages.length === 0) {
403
+ return [];
404
+ }
386
405
 
387
406
  const retryOptions = resolveRetryOptions(params.retry);
388
407
 
@@ -390,7 +409,9 @@ export async function sendMSTeamsMessages(params: {
390
409
  sendOnce: () => Promise<unknown>,
391
410
  meta: { messageIndex: number; messageCount: number },
392
411
  ): Promise<unknown> => {
393
- if (!retryOptions.enabled) return await sendOnce();
412
+ if (!retryOptions.enabled) {
413
+ return await sendOnce();
414
+ }
394
415
 
395
416
  let attempt = 1;
396
417
  while (true) {
@@ -399,7 +420,9 @@ export async function sendMSTeamsMessages(params: {
399
420
  } catch (err) {
400
421
  const classification = classifyMSTeamsSendError(err);
401
422
  const canRetry = attempt < retryOptions.maxAttempts && shouldRetry(classification);
402
- if (!canRetry) throw err;
423
+ if (!canRetry) {
424
+ throw err;
425
+ }
403
426
 
404
427
  const delayMs = computeRetryDelayMs(attempt, classification, retryOptions);
405
428
  const nextAttempt = attempt + 1;
@@ -428,7 +451,13 @@ export async function sendMSTeamsMessages(params: {
428
451
  const response = await sendWithRetry(
429
452
  async () =>
430
453
  await ctx.sendActivity(
431
- await buildActivity(message, params.conversationRef, params.tokenProvider, params.sharePointSiteId, params.mediaMaxBytes),
454
+ await buildActivity(
455
+ message,
456
+ params.conversationRef,
457
+ params.tokenProvider,
458
+ params.sharePointSiteId,
459
+ params.mediaMaxBytes,
460
+ ),
432
461
  ),
433
462
  { messageIndex: idx, messageCount: messages.length },
434
463
  );
@@ -449,7 +478,13 @@ export async function sendMSTeamsMessages(params: {
449
478
  const response = await sendWithRetry(
450
479
  async () =>
451
480
  await ctx.sendActivity(
452
- await buildActivity(message, params.conversationRef, params.tokenProvider, params.sharePointSiteId, params.mediaMaxBytes),
481
+ await buildActivity(
482
+ message,
483
+ params.conversationRef,
484
+ params.tokenProvider,
485
+ params.sharePointSiteId,
486
+ params.mediaMaxBytes,
487
+ ),
453
488
  ),
454
489
  { messageIndex: idx, messageCount: messages.length },
455
490
  );
@@ -1,3 +1,4 @@
1
+ import type { MSTeamsTurnContext } from "../sdk-types.js";
1
2
  import {
2
3
  buildMSTeamsGraphMessageUrls,
3
4
  downloadMSTeamsAttachments,
@@ -7,7 +8,6 @@ import {
7
8
  type MSTeamsHtmlAttachmentSummary,
8
9
  type MSTeamsInboundMedia,
9
10
  } from "../attachments.js";
10
- import type { MSTeamsTurnContext } from "../sdk-types.js";
11
11
 
12
12
  type MSTeamsLogger = {
13
13
  debug: (message: string, meta?: Record<string, unknown>) => void;
@@ -18,6 +18,7 @@ export async function resolveMSTeamsInboundMedia(params: {
18
18
  htmlSummary?: MSTeamsHtmlAttachmentSummary;
19
19
  maxBytes: number;
20
20
  allowHosts?: string[];
21
+ authAllowHosts?: string[];
21
22
  tokenProvider: MSTeamsAccessTokenProvider;
22
23
  conversationType: string;
23
24
  conversationId: string;
@@ -46,6 +47,7 @@ export async function resolveMSTeamsInboundMedia(params: {
46
47
  maxBytes,
47
48
  tokenProvider,
48
49
  allowHosts,
50
+ authAllowHosts: params.authAllowHosts,
49
51
  preserveFilenames,
50
52
  });
51
53
 
@@ -85,6 +87,7 @@ export async function resolveMSTeamsInboundMedia(params: {
85
87
  tokenProvider,
86
88
  maxBytes,
87
89
  allowHosts,
90
+ authAllowHosts: params.authAllowHosts,
88
91
  preserveFilenames,
89
92
  });
90
93
  attempts.push({
@@ -99,7 +102,9 @@ export async function resolveMSTeamsInboundMedia(params: {
99
102
  mediaList = graphMedia.media;
100
103
  break;
101
104
  }
102
- if (graphMedia.tokenError) break;
105
+ if (graphMedia.tokenError) {
106
+ break;
107
+ }
103
108
  }
104
109
  if (mediaList.length === 0) {
105
110
  log.debug("graph media fetch empty", { attempts });