@openclaw/msteams 2026.5.2 → 2026.5.3-beta.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 (197) hide show
  1. package/dist/api.js +3 -0
  2. package/dist/channel-D7hdreTh.js +984 -0
  3. package/dist/channel-config-api.js +2 -0
  4. package/dist/channel-plugin-api.js +2 -0
  5. package/dist/channel.runtime-BC1ruIfN.js +573 -0
  6. package/dist/config-schema-B8QezH6t.js +15 -0
  7. package/dist/contract-api.js +2 -0
  8. package/dist/graph-users-9uQJepqr.js +1354 -0
  9. package/dist/index.js +22 -0
  10. package/dist/oauth-BWJyilR1.js +114 -0
  11. package/dist/oauth.token-xxpoLWy5.js +115 -0
  12. package/dist/policy-DTnU2GR7.js +142 -0
  13. package/dist/probe-D_H8yFps.js +2194 -0
  14. package/dist/resolve-allowlist-D41JSziq.js +219 -0
  15. package/dist/runtime-api-DV1iVMn1.js +28 -0
  16. package/dist/runtime-api.js +2 -0
  17. package/dist/secret-contract-BuoEXmPS.js +35 -0
  18. package/dist/secret-contract-api.js +2 -0
  19. package/dist/setup-entry.js +15 -0
  20. package/dist/setup-plugin-api.js +64 -0
  21. package/dist/setup-surface-BLkFQYIQ.js +313 -0
  22. package/dist/src-CFp1QpFd.js +4064 -0
  23. package/dist/test-api.js +2 -0
  24. package/package.json +14 -6
  25. package/api.ts +0 -3
  26. package/channel-config-api.ts +0 -1
  27. package/channel-plugin-api.ts +0 -2
  28. package/config-api.ts +0 -4
  29. package/contract-api.ts +0 -4
  30. package/index.ts +0 -20
  31. package/runtime-api.ts +0 -73
  32. package/secret-contract-api.ts +0 -5
  33. package/setup-entry.ts +0 -13
  34. package/setup-plugin-api.ts +0 -3
  35. package/src/ai-entity.ts +0 -7
  36. package/src/approval-auth.ts +0 -44
  37. package/src/attachments/bot-framework.test.ts +0 -461
  38. package/src/attachments/bot-framework.ts +0 -362
  39. package/src/attachments/download.ts +0 -311
  40. package/src/attachments/graph.test.ts +0 -416
  41. package/src/attachments/graph.ts +0 -484
  42. package/src/attachments/html.ts +0 -122
  43. package/src/attachments/payload.ts +0 -14
  44. package/src/attachments/remote-media.test.ts +0 -137
  45. package/src/attachments/remote-media.ts +0 -112
  46. package/src/attachments/shared.test.ts +0 -530
  47. package/src/attachments/shared.ts +0 -626
  48. package/src/attachments/types.ts +0 -47
  49. package/src/attachments.graph.test.ts +0 -342
  50. package/src/attachments.helpers.test.ts +0 -246
  51. package/src/attachments.test-helpers.ts +0 -17
  52. package/src/attachments.test.ts +0 -687
  53. package/src/attachments.ts +0 -18
  54. package/src/block-streaming-config.test.ts +0 -61
  55. package/src/channel-api.ts +0 -1
  56. package/src/channel.actions.test.ts +0 -742
  57. package/src/channel.directory.test.ts +0 -200
  58. package/src/channel.runtime.ts +0 -56
  59. package/src/channel.setup.ts +0 -77
  60. package/src/channel.test.ts +0 -128
  61. package/src/channel.ts +0 -1136
  62. package/src/config-schema.ts +0 -6
  63. package/src/config-ui-hints.ts +0 -12
  64. package/src/conversation-store-fs.test.ts +0 -74
  65. package/src/conversation-store-fs.ts +0 -149
  66. package/src/conversation-store-helpers.test.ts +0 -202
  67. package/src/conversation-store-helpers.ts +0 -105
  68. package/src/conversation-store-memory.ts +0 -51
  69. package/src/conversation-store.shared.test.ts +0 -225
  70. package/src/conversation-store.ts +0 -71
  71. package/src/directory-live.test.ts +0 -156
  72. package/src/directory-live.ts +0 -111
  73. package/src/doctor.ts +0 -27
  74. package/src/errors.test.ts +0 -133
  75. package/src/errors.ts +0 -246
  76. package/src/feedback-reflection-prompt.ts +0 -117
  77. package/src/feedback-reflection-store.ts +0 -114
  78. package/src/feedback-reflection.test.ts +0 -237
  79. package/src/feedback-reflection.ts +0 -283
  80. package/src/file-consent-helpers.test.ts +0 -326
  81. package/src/file-consent-helpers.ts +0 -126
  82. package/src/file-consent-invoke.ts +0 -150
  83. package/src/file-consent.test.ts +0 -363
  84. package/src/file-consent.ts +0 -287
  85. package/src/graph-chat.ts +0 -55
  86. package/src/graph-group-management.test.ts +0 -318
  87. package/src/graph-group-management.ts +0 -168
  88. package/src/graph-members.test.ts +0 -89
  89. package/src/graph-members.ts +0 -48
  90. package/src/graph-messages.actions.test.ts +0 -243
  91. package/src/graph-messages.read.test.ts +0 -391
  92. package/src/graph-messages.search.test.ts +0 -213
  93. package/src/graph-messages.test-helpers.ts +0 -50
  94. package/src/graph-messages.ts +0 -534
  95. package/src/graph-teams.test.ts +0 -215
  96. package/src/graph-teams.ts +0 -114
  97. package/src/graph-thread.test.ts +0 -246
  98. package/src/graph-thread.ts +0 -146
  99. package/src/graph-upload.test.ts +0 -258
  100. package/src/graph-upload.ts +0 -531
  101. package/src/graph-users.ts +0 -29
  102. package/src/graph.test.ts +0 -516
  103. package/src/graph.ts +0 -293
  104. package/src/inbound.test.ts +0 -221
  105. package/src/inbound.ts +0 -148
  106. package/src/index.ts +0 -4
  107. package/src/media-helpers.test.ts +0 -202
  108. package/src/media-helpers.ts +0 -105
  109. package/src/mentions.test.ts +0 -244
  110. package/src/mentions.ts +0 -114
  111. package/src/messenger.test.ts +0 -865
  112. package/src/messenger.ts +0 -605
  113. package/src/monitor-handler/access.ts +0 -125
  114. package/src/monitor-handler/inbound-media.test.ts +0 -289
  115. package/src/monitor-handler/inbound-media.ts +0 -180
  116. package/src/monitor-handler/message-handler-mock-support.test-support.ts +0 -28
  117. package/src/monitor-handler/message-handler.authz.test.ts +0 -669
  118. package/src/monitor-handler/message-handler.dm-media.test.ts +0 -54
  119. package/src/monitor-handler/message-handler.test-support.ts +0 -100
  120. package/src/monitor-handler/message-handler.thread-parent.test.ts +0 -223
  121. package/src/monitor-handler/message-handler.thread-session.test.ts +0 -77
  122. package/src/monitor-handler/message-handler.ts +0 -1000
  123. package/src/monitor-handler/reaction-handler.test.ts +0 -267
  124. package/src/monitor-handler/reaction-handler.ts +0 -210
  125. package/src/monitor-handler/thread-session.ts +0 -17
  126. package/src/monitor-handler.adaptive-card.test.ts +0 -162
  127. package/src/monitor-handler.feedback-authz.test.ts +0 -314
  128. package/src/monitor-handler.file-consent.test.ts +0 -423
  129. package/src/monitor-handler.sso.test.ts +0 -563
  130. package/src/monitor-handler.test-helpers.ts +0 -180
  131. package/src/monitor-handler.ts +0 -534
  132. package/src/monitor-handler.types.ts +0 -27
  133. package/src/monitor-types.ts +0 -6
  134. package/src/monitor.lifecycle.test.ts +0 -278
  135. package/src/monitor.test.ts +0 -119
  136. package/src/monitor.ts +0 -442
  137. package/src/oauth.flow.ts +0 -77
  138. package/src/oauth.shared.ts +0 -37
  139. package/src/oauth.test.ts +0 -305
  140. package/src/oauth.token.ts +0 -158
  141. package/src/oauth.ts +0 -130
  142. package/src/outbound.test.ts +0 -130
  143. package/src/outbound.ts +0 -71
  144. package/src/pending-uploads-fs.test.ts +0 -246
  145. package/src/pending-uploads-fs.ts +0 -235
  146. package/src/pending-uploads.test.ts +0 -173
  147. package/src/pending-uploads.ts +0 -121
  148. package/src/policy.test.ts +0 -240
  149. package/src/policy.ts +0 -262
  150. package/src/polls-store-memory.ts +0 -32
  151. package/src/polls.test.ts +0 -160
  152. package/src/polls.ts +0 -323
  153. package/src/presentation.ts +0 -68
  154. package/src/probe.test.ts +0 -77
  155. package/src/probe.ts +0 -132
  156. package/src/reply-dispatcher.test.ts +0 -437
  157. package/src/reply-dispatcher.ts +0 -346
  158. package/src/reply-stream-controller.test.ts +0 -235
  159. package/src/reply-stream-controller.ts +0 -147
  160. package/src/resolve-allowlist.test.ts +0 -250
  161. package/src/resolve-allowlist.ts +0 -309
  162. package/src/revoked-context.ts +0 -17
  163. package/src/runtime.ts +0 -9
  164. package/src/sdk-types.ts +0 -59
  165. package/src/sdk.test.ts +0 -666
  166. package/src/sdk.ts +0 -884
  167. package/src/secret-contract.ts +0 -49
  168. package/src/secret-input.ts +0 -7
  169. package/src/send-context.ts +0 -231
  170. package/src/send.test.ts +0 -493
  171. package/src/send.ts +0 -637
  172. package/src/sent-message-cache.test.ts +0 -15
  173. package/src/sent-message-cache.ts +0 -56
  174. package/src/session-route.ts +0 -40
  175. package/src/setup-core.ts +0 -160
  176. package/src/setup-surface.test.ts +0 -202
  177. package/src/setup-surface.ts +0 -320
  178. package/src/sso-token-store.test.ts +0 -72
  179. package/src/sso-token-store.ts +0 -166
  180. package/src/sso.ts +0 -300
  181. package/src/storage.ts +0 -25
  182. package/src/store-fs.ts +0 -44
  183. package/src/streaming-message.test.ts +0 -262
  184. package/src/streaming-message.ts +0 -297
  185. package/src/test-runtime.ts +0 -16
  186. package/src/thread-parent-context.test.ts +0 -224
  187. package/src/thread-parent-context.ts +0 -159
  188. package/src/token-response.ts +0 -11
  189. package/src/token.test.ts +0 -259
  190. package/src/token.ts +0 -195
  191. package/src/user-agent.test.ts +0 -86
  192. package/src/user-agent.ts +0 -53
  193. package/src/webhook-timeouts.ts +0 -27
  194. package/src/welcome-card.test.ts +0 -81
  195. package/src/welcome-card.ts +0 -57
  196. package/test-api.ts +0 -1
  197. package/tsconfig.json +0 -16
@@ -1,326 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
2
- import { prepareFileConsentActivity, requiresFileConsent } from "./file-consent-helpers.js";
3
- import {
4
- clearPendingUploads,
5
- getPendingUpload,
6
- getPendingUploadCount,
7
- removePendingUpload,
8
- storePendingUpload,
9
- } from "./pending-uploads.js";
10
- import * as pendingUploads from "./pending-uploads.js";
11
-
12
- describe("requiresFileConsent", () => {
13
- const thresholdBytes = 4 * 1024 * 1024; // 4MB
14
-
15
- it("returns true for personal chat with non-image", () => {
16
- expect(
17
- requiresFileConsent({
18
- conversationType: "personal",
19
- contentType: "application/pdf",
20
- bufferSize: 1000,
21
- thresholdBytes,
22
- }),
23
- ).toBe(true);
24
- });
25
-
26
- it("returns true for personal chat with large image", () => {
27
- expect(
28
- requiresFileConsent({
29
- conversationType: "personal",
30
- contentType: "image/png",
31
- bufferSize: 5 * 1024 * 1024, // 5MB
32
- thresholdBytes,
33
- }),
34
- ).toBe(true);
35
- });
36
-
37
- it("returns false for personal chat with small image", () => {
38
- expect(
39
- requiresFileConsent({
40
- conversationType: "personal",
41
- contentType: "image/png",
42
- bufferSize: 1000,
43
- thresholdBytes,
44
- }),
45
- ).toBe(false);
46
- });
47
-
48
- it("returns false for group chat with large non-image", () => {
49
- expect(
50
- requiresFileConsent({
51
- conversationType: "groupChat",
52
- contentType: "application/pdf",
53
- bufferSize: 5 * 1024 * 1024,
54
- thresholdBytes,
55
- }),
56
- ).toBe(false);
57
- });
58
-
59
- it("returns false for channel with large non-image", () => {
60
- expect(
61
- requiresFileConsent({
62
- conversationType: "channel",
63
- contentType: "application/pdf",
64
- bufferSize: 5 * 1024 * 1024,
65
- thresholdBytes,
66
- }),
67
- ).toBe(false);
68
- });
69
-
70
- it("handles case-insensitive conversation type", () => {
71
- expect(
72
- requiresFileConsent({
73
- conversationType: "Personal",
74
- contentType: "application/pdf",
75
- bufferSize: 1000,
76
- thresholdBytes,
77
- }),
78
- ).toBe(true);
79
-
80
- expect(
81
- requiresFileConsent({
82
- conversationType: "PERSONAL",
83
- contentType: "application/pdf",
84
- bufferSize: 1000,
85
- thresholdBytes,
86
- }),
87
- ).toBe(true);
88
- });
89
-
90
- it("returns false when conversationType is undefined", () => {
91
- expect(
92
- requiresFileConsent({
93
- conversationType: undefined,
94
- contentType: "application/pdf",
95
- bufferSize: 1000,
96
- thresholdBytes,
97
- }),
98
- ).toBe(false);
99
- });
100
-
101
- it("returns true for personal chat when contentType is undefined (non-image)", () => {
102
- expect(
103
- requiresFileConsent({
104
- conversationType: "personal",
105
- contentType: undefined,
106
- bufferSize: 1000,
107
- thresholdBytes,
108
- }),
109
- ).toBe(true);
110
- });
111
-
112
- it("returns true for personal chat with file exactly at threshold", () => {
113
- expect(
114
- requiresFileConsent({
115
- conversationType: "personal",
116
- contentType: "image/jpeg",
117
- bufferSize: thresholdBytes, // exactly 4MB
118
- thresholdBytes,
119
- }),
120
- ).toBe(true);
121
- });
122
-
123
- it("returns false for personal chat with file just below threshold", () => {
124
- expect(
125
- requiresFileConsent({
126
- conversationType: "personal",
127
- contentType: "image/jpeg",
128
- bufferSize: thresholdBytes - 1, // 4MB - 1 byte
129
- thresholdBytes,
130
- }),
131
- ).toBe(false);
132
- });
133
- });
134
-
135
- describe("prepareFileConsentActivity", () => {
136
- const mockUploadId = "test-upload-id-123";
137
-
138
- beforeEach(() => {
139
- vi.spyOn(pendingUploads, "storePendingUpload").mockReturnValue(mockUploadId);
140
- });
141
-
142
- afterEach(() => {
143
- vi.restoreAllMocks();
144
- });
145
-
146
- it("creates activity with consent card attachment", () => {
147
- const result = prepareFileConsentActivity({
148
- media: {
149
- buffer: Buffer.from("test content"),
150
- filename: "test.pdf",
151
- contentType: "application/pdf",
152
- },
153
- conversationId: "conv123",
154
- description: "My file",
155
- });
156
-
157
- expect(result.uploadId).toBe(mockUploadId);
158
- expect(result.activity.type).toBe("message");
159
- expect(result.activity.attachments).toHaveLength(1);
160
-
161
- const attachment = (result.activity.attachments as unknown[])[0] as Record<string, unknown>;
162
- expect(attachment.contentType).toBe("application/vnd.microsoft.teams.card.file.consent");
163
- expect(attachment.name).toBe("test.pdf");
164
- });
165
-
166
- it("stores pending upload with correct data", () => {
167
- const buffer = Buffer.from("test content");
168
- prepareFileConsentActivity({
169
- media: {
170
- buffer,
171
- filename: "test.pdf",
172
- contentType: "application/pdf",
173
- },
174
- conversationId: "conv123",
175
- description: "My file",
176
- });
177
-
178
- expect(pendingUploads.storePendingUpload).toHaveBeenCalledWith({
179
- buffer,
180
- filename: "test.pdf",
181
- contentType: "application/pdf",
182
- conversationId: "conv123",
183
- });
184
- });
185
-
186
- it("uses default description when not provided", () => {
187
- const result = prepareFileConsentActivity({
188
- media: {
189
- buffer: Buffer.from("test"),
190
- filename: "document.docx",
191
- contentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
192
- },
193
- conversationId: "conv456",
194
- });
195
-
196
- const attachment = (result.activity.attachments as unknown[])[0] as Record<
197
- string,
198
- { description: string }
199
- >;
200
- expect(attachment.content.description).toBe("File: document.docx");
201
- });
202
-
203
- it("uses provided description", () => {
204
- const result = prepareFileConsentActivity({
205
- media: {
206
- buffer: Buffer.from("test"),
207
- filename: "report.pdf",
208
- contentType: "application/pdf",
209
- },
210
- conversationId: "conv789",
211
- description: "Q4 Financial Report",
212
- });
213
-
214
- const attachment = (result.activity.attachments as unknown[])[0] as Record<
215
- string,
216
- { description: string }
217
- >;
218
- expect(attachment.content.description).toBe("Q4 Financial Report");
219
- });
220
-
221
- it("includes uploadId in consent card context", () => {
222
- const result = prepareFileConsentActivity({
223
- media: {
224
- buffer: Buffer.from("test"),
225
- filename: "file.txt",
226
- contentType: "text/plain",
227
- },
228
- conversationId: "conv000",
229
- });
230
-
231
- const attachment = (result.activity.attachments as unknown[])[0] as Record<
232
- string,
233
- { acceptContext: { uploadId: string } }
234
- >;
235
- expect(attachment.content.acceptContext.uploadId).toBe(mockUploadId);
236
- });
237
-
238
- it("handles media without contentType", () => {
239
- const result = prepareFileConsentActivity({
240
- media: {
241
- buffer: Buffer.from("binary data"),
242
- filename: "unknown.bin",
243
- },
244
- conversationId: "conv111",
245
- });
246
-
247
- expect(result.uploadId).toBe(mockUploadId);
248
- expect(result.activity.type).toBe("message");
249
- });
250
- });
251
-
252
- describe("msteams pending uploads", () => {
253
- beforeEach(() => {
254
- vi.useFakeTimers();
255
- clearPendingUploads();
256
- });
257
-
258
- afterEach(() => {
259
- clearPendingUploads();
260
- vi.useRealTimers();
261
- });
262
-
263
- it("stores uploads, exposes them by id, and tracks count", () => {
264
- const id = storePendingUpload({
265
- buffer: Buffer.from("hello"),
266
- filename: "hello.txt",
267
- contentType: "text/plain",
268
- conversationId: "conv-1",
269
- });
270
-
271
- expect(getPendingUploadCount()).toBe(1);
272
- expect(getPendingUpload(id)).toEqual(
273
- expect.objectContaining({
274
- id,
275
- filename: "hello.txt",
276
- contentType: "text/plain",
277
- conversationId: "conv-1",
278
- }),
279
- );
280
- });
281
-
282
- it("removes uploads explicitly and ignores empty ids", () => {
283
- const id = storePendingUpload({
284
- buffer: Buffer.from("hello"),
285
- filename: "hello.txt",
286
- conversationId: "conv-1",
287
- });
288
-
289
- removePendingUpload(undefined);
290
- expect(getPendingUploadCount()).toBe(1);
291
-
292
- removePendingUpload(id);
293
- expect(getPendingUpload(id)).toBeUndefined();
294
- expect(getPendingUploadCount()).toBe(0);
295
- });
296
-
297
- it("expires uploads by ttl even if the timeout callback has not been observed yet", () => {
298
- const id = storePendingUpload({
299
- buffer: Buffer.from("hello"),
300
- filename: "hello.txt",
301
- conversationId: "conv-1",
302
- });
303
-
304
- vi.advanceTimersByTime(5 * 60 * 1000 + 1);
305
-
306
- expect(getPendingUpload(id)).toBeUndefined();
307
- expect(getPendingUploadCount()).toBe(0);
308
- });
309
-
310
- it("clears all uploads for test cleanup", () => {
311
- storePendingUpload({
312
- buffer: Buffer.from("a"),
313
- filename: "a.txt",
314
- conversationId: "conv-1",
315
- });
316
- storePendingUpload({
317
- buffer: Buffer.from("b"),
318
- filename: "b.txt",
319
- conversationId: "conv-2",
320
- });
321
-
322
- clearPendingUploads();
323
-
324
- expect(getPendingUploadCount()).toBe(0);
325
- });
326
- });
@@ -1,126 +0,0 @@
1
- /**
2
- * Shared helpers for FileConsentCard flow in MSTeams.
3
- *
4
- * FileConsentCard is required for:
5
- * - Personal (1:1) chats with large files (>=4MB)
6
- * - Personal chats with non-image files (PDFs, documents, etc.)
7
- *
8
- * This module consolidates the logic used by both send.ts (proactive sends)
9
- * and messenger.ts (reply path) to avoid duplication.
10
- */
11
-
12
- import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
13
- import { buildFileConsentCard } from "./file-consent.js";
14
- import { storePendingUploadFs } from "./pending-uploads-fs.js";
15
- import { storePendingUpload } from "./pending-uploads.js";
16
-
17
- type FileConsentMedia = {
18
- buffer: Buffer;
19
- filename: string;
20
- contentType?: string;
21
- };
22
-
23
- type FileConsentActivityResult = {
24
- activity: Record<string, unknown>;
25
- uploadId: string;
26
- };
27
-
28
- function buildConsentActivity(params: {
29
- media: FileConsentMedia;
30
- description?: string;
31
- uploadId: string;
32
- }): Record<string, unknown> {
33
- const { media, description, uploadId } = params;
34
- const consentCard = buildFileConsentCard({
35
- filename: media.filename,
36
- description: description || `File: ${media.filename}`,
37
- sizeInBytes: media.buffer.length,
38
- context: { uploadId },
39
- });
40
- return {
41
- type: "message",
42
- attachments: [consentCard],
43
- };
44
- }
45
-
46
- /**
47
- * Prepare a FileConsentCard activity for large files or non-images in personal chats.
48
- * Returns the activity object and uploadId - caller is responsible for sending.
49
- *
50
- * This variant only writes to the in-memory store. Use it when the caller and
51
- * the `fileConsent/invoke` handler share the same process (for example the
52
- * messenger reply path). For proactive CLI sends where the invoke arrives in
53
- * a different process, use {@link prepareFileConsentActivityFs} instead.
54
- */
55
- export function prepareFileConsentActivity(params: {
56
- media: FileConsentMedia;
57
- conversationId: string;
58
- description?: string;
59
- }): FileConsentActivityResult {
60
- const { media, conversationId, description } = params;
61
-
62
- const uploadId = storePendingUpload({
63
- buffer: media.buffer,
64
- filename: media.filename,
65
- contentType: media.contentType,
66
- conversationId,
67
- });
68
-
69
- const activity = buildConsentActivity({ media, description, uploadId });
70
- return { activity, uploadId };
71
- }
72
-
73
- /**
74
- * Prepare a FileConsentCard activity and persist the pending upload to the
75
- * filesystem so a different process can read it when the user accepts.
76
- *
77
- * This is used by the proactive CLI `message send --media` path: the CLI
78
- * process sends the card and exits, but the `fileConsent/invoke` callback is
79
- * delivered to the long-lived gateway monitor process. The FS-backed store
80
- * bridges those two processes. The in-memory store is also populated so
81
- * same-process flows keep the fast path.
82
- */
83
- export async function prepareFileConsentActivityFs(params: {
84
- media: FileConsentMedia;
85
- conversationId: string;
86
- description?: string;
87
- }): Promise<FileConsentActivityResult> {
88
- const { media, conversationId, description } = params;
89
-
90
- // Populate the in-memory store first so the uploadId is consistent, then
91
- // mirror the same entry to the FS store under the same id so an invoke
92
- // handler in another process can find it.
93
- const uploadId = storePendingUpload({
94
- buffer: media.buffer,
95
- filename: media.filename,
96
- contentType: media.contentType,
97
- conversationId,
98
- });
99
-
100
- await storePendingUploadFs({
101
- id: uploadId,
102
- buffer: media.buffer,
103
- filename: media.filename,
104
- contentType: media.contentType,
105
- conversationId,
106
- });
107
-
108
- const activity = buildConsentActivity({ media, description, uploadId });
109
- return { activity, uploadId };
110
- }
111
-
112
- /**
113
- * Check if a file requires FileConsentCard flow.
114
- * True for: personal chat AND (large file OR non-image)
115
- */
116
- export function requiresFileConsent(params: {
117
- conversationType: string | undefined;
118
- contentType: string | undefined;
119
- bufferSize: number;
120
- thresholdBytes: number;
121
- }): boolean {
122
- const isPersonal = normalizeOptionalLowercaseString(params.conversationType) === "personal";
123
- const isImage = params.contentType?.startsWith("image/") ?? false;
124
- const isLargeFile = params.bufferSize >= params.thresholdBytes;
125
- return isPersonal && (isLargeFile || !isImage);
126
- }
@@ -1,150 +0,0 @@
1
- import { formatUnknownError } from "./errors.js";
2
- import { buildFileInfoCard, parseFileConsentInvoke, uploadToConsentUrl } from "./file-consent.js";
3
- import { normalizeMSTeamsConversationId } from "./inbound.js";
4
- import type { MSTeamsMonitorLogger } from "./monitor-types.js";
5
- import { getPendingUploadFs, removePendingUploadFs } from "./pending-uploads-fs.js";
6
- import { getPendingUpload, removePendingUpload } from "./pending-uploads.js";
7
- import { withRevokedProxyFallback } from "./revoked-context.js";
8
- import type { MSTeamsTurnContext } from "./sdk-types.js";
9
-
10
- /**
11
- * Handle fileConsent/invoke activities for large file uploads.
12
- */
13
- async function handleMSTeamsFileConsentInvoke(
14
- context: MSTeamsTurnContext,
15
- log: MSTeamsMonitorLogger,
16
- ): Promise<boolean> {
17
- const expiredUploadMessage =
18
- "The file upload request has expired. Please try sending the file again.";
19
- const activity = context.activity;
20
- if (activity.type !== "invoke" || activity.name !== "fileConsent/invoke") {
21
- return false;
22
- }
23
-
24
- const consentResponse = parseFileConsentInvoke(activity);
25
- if (!consentResponse) {
26
- log.debug?.("invalid file consent invoke", { value: activity.value });
27
- return false;
28
- }
29
-
30
- const uploadId =
31
- typeof consentResponse.context?.uploadId === "string"
32
- ? consentResponse.context.uploadId
33
- : undefined;
34
- // Prefer the in-memory store (same-process reply path); fall back to the
35
- // FS-backed store so CLI `message send --media` flows work even when the
36
- // invoke callback is delivered to a different process.
37
- const inMemoryFile = getPendingUpload(uploadId);
38
- const fsFile = inMemoryFile ? undefined : await getPendingUploadFs(uploadId);
39
- const pendingFile:
40
- | {
41
- buffer: Buffer;
42
- filename: string;
43
- contentType?: string;
44
- conversationId: string;
45
- consentCardActivityId?: string;
46
- }
47
- | undefined = inMemoryFile ?? fsFile;
48
- if (pendingFile) {
49
- const pendingConversationId = normalizeMSTeamsConversationId(pendingFile.conversationId);
50
- const invokeConversationId = normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
51
- if (!invokeConversationId || pendingConversationId !== invokeConversationId) {
52
- log.info("file consent conversation mismatch", {
53
- uploadId,
54
- expectedConversationId: pendingConversationId,
55
- receivedConversationId: invokeConversationId || undefined,
56
- });
57
- if (consentResponse.action === "accept") {
58
- await context.sendActivity(expiredUploadMessage);
59
- }
60
- return true;
61
- }
62
- }
63
-
64
- if (consentResponse.action === "accept" && consentResponse.uploadInfo) {
65
- if (pendingFile) {
66
- log.debug?.("user accepted file consent, uploading", {
67
- uploadId,
68
- filename: pendingFile.filename,
69
- size: pendingFile.buffer.length,
70
- });
71
-
72
- try {
73
- await uploadToConsentUrl({
74
- url: consentResponse.uploadInfo.uploadUrl,
75
- buffer: pendingFile.buffer,
76
- contentType: pendingFile.contentType,
77
- });
78
-
79
- const fileInfoCard = buildFileInfoCard({
80
- filename: consentResponse.uploadInfo.name,
81
- contentUrl: consentResponse.uploadInfo.contentUrl,
82
- uniqueId: consentResponse.uploadInfo.uniqueId,
83
- fileType: consentResponse.uploadInfo.fileType,
84
- });
85
-
86
- if (!pendingFile.consentCardActivityId) {
87
- await context.sendActivity({
88
- type: "message",
89
- attachments: [fileInfoCard],
90
- });
91
- }
92
-
93
- if (pendingFile.consentCardActivityId) {
94
- try {
95
- await context.updateActivity({
96
- id: pendingFile.consentCardActivityId,
97
- type: "message",
98
- attachments: [fileInfoCard],
99
- });
100
- } catch {
101
- await context.sendActivity({
102
- type: "message",
103
- attachments: [fileInfoCard],
104
- });
105
- }
106
- }
107
-
108
- log.info("file upload complete", {
109
- uploadId,
110
- filename: consentResponse.uploadInfo.name,
111
- uniqueId: consentResponse.uploadInfo.uniqueId,
112
- });
113
- } catch (err) {
114
- log.error("file upload failed", { uploadId, error: formatUnknownError(err) });
115
- await context.sendActivity("File upload failed. Please try again.");
116
- } finally {
117
- removePendingUpload(uploadId);
118
- await removePendingUploadFs(uploadId);
119
- }
120
- } else {
121
- log.debug?.("pending file not found for consent", { uploadId });
122
- await context.sendActivity(expiredUploadMessage);
123
- }
124
- } else {
125
- log.debug?.("user declined file consent", { uploadId });
126
- removePendingUpload(uploadId);
127
- await removePendingUploadFs(uploadId);
128
- }
129
-
130
- return true;
131
- }
132
-
133
- export async function respondToMSTeamsFileConsentInvoke(
134
- context: MSTeamsTurnContext,
135
- log: MSTeamsMonitorLogger,
136
- ): Promise<void> {
137
- await context.sendActivity({ type: "invokeResponse", value: { status: 200 } });
138
-
139
- try {
140
- await withRevokedProxyFallback({
141
- run: async () => await handleMSTeamsFileConsentInvoke(context, log),
142
- onRevoked: async () => true,
143
- onRevokedLog: () => {
144
- log.debug?.("turn context revoked during file consent invoke; skipping delayed response");
145
- },
146
- });
147
- } catch (err) {
148
- log.debug?.("file consent handler error", { error: formatUnknownError(err) });
149
- }
150
- }