@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.
- package/dist/api.js +3 -0
- package/dist/channel-D7hdreTh.js +984 -0
- package/dist/channel-config-api.js +2 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-BC1ruIfN.js +573 -0
- package/dist/config-schema-B8QezH6t.js +15 -0
- package/dist/contract-api.js +2 -0
- package/dist/graph-users-9uQJepqr.js +1354 -0
- package/dist/index.js +22 -0
- package/dist/oauth-BWJyilR1.js +114 -0
- package/dist/oauth.token-xxpoLWy5.js +115 -0
- package/dist/policy-DTnU2GR7.js +142 -0
- package/dist/probe-D_H8yFps.js +2194 -0
- package/dist/resolve-allowlist-D41JSziq.js +219 -0
- package/dist/runtime-api-DV1iVMn1.js +28 -0
- package/dist/runtime-api.js +2 -0
- package/dist/secret-contract-BuoEXmPS.js +35 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/setup-entry.js +15 -0
- package/dist/setup-plugin-api.js +64 -0
- package/dist/setup-surface-BLkFQYIQ.js +313 -0
- package/dist/src-CFp1QpFd.js +4064 -0
- package/dist/test-api.js +2 -0
- package/package.json +14 -6
- package/api.ts +0 -3
- package/channel-config-api.ts +0 -1
- package/channel-plugin-api.ts +0 -2
- package/config-api.ts +0 -4
- package/contract-api.ts +0 -4
- package/index.ts +0 -20
- package/runtime-api.ts +0 -73
- package/secret-contract-api.ts +0 -5
- package/setup-entry.ts +0 -13
- package/setup-plugin-api.ts +0 -3
- package/src/ai-entity.ts +0 -7
- package/src/approval-auth.ts +0 -44
- package/src/attachments/bot-framework.test.ts +0 -461
- package/src/attachments/bot-framework.ts +0 -362
- package/src/attachments/download.ts +0 -311
- package/src/attachments/graph.test.ts +0 -416
- package/src/attachments/graph.ts +0 -484
- package/src/attachments/html.ts +0 -122
- package/src/attachments/payload.ts +0 -14
- package/src/attachments/remote-media.test.ts +0 -137
- package/src/attachments/remote-media.ts +0 -112
- package/src/attachments/shared.test.ts +0 -530
- package/src/attachments/shared.ts +0 -626
- package/src/attachments/types.ts +0 -47
- package/src/attachments.graph.test.ts +0 -342
- package/src/attachments.helpers.test.ts +0 -246
- package/src/attachments.test-helpers.ts +0 -17
- package/src/attachments.test.ts +0 -687
- package/src/attachments.ts +0 -18
- package/src/block-streaming-config.test.ts +0 -61
- package/src/channel-api.ts +0 -1
- package/src/channel.actions.test.ts +0 -742
- package/src/channel.directory.test.ts +0 -200
- package/src/channel.runtime.ts +0 -56
- package/src/channel.setup.ts +0 -77
- package/src/channel.test.ts +0 -128
- package/src/channel.ts +0 -1136
- package/src/config-schema.ts +0 -6
- package/src/config-ui-hints.ts +0 -12
- package/src/conversation-store-fs.test.ts +0 -74
- package/src/conversation-store-fs.ts +0 -149
- package/src/conversation-store-helpers.test.ts +0 -202
- package/src/conversation-store-helpers.ts +0 -105
- package/src/conversation-store-memory.ts +0 -51
- package/src/conversation-store.shared.test.ts +0 -225
- package/src/conversation-store.ts +0 -71
- package/src/directory-live.test.ts +0 -156
- package/src/directory-live.ts +0 -111
- package/src/doctor.ts +0 -27
- package/src/errors.test.ts +0 -133
- package/src/errors.ts +0 -246
- package/src/feedback-reflection-prompt.ts +0 -117
- package/src/feedback-reflection-store.ts +0 -114
- package/src/feedback-reflection.test.ts +0 -237
- package/src/feedback-reflection.ts +0 -283
- package/src/file-consent-helpers.test.ts +0 -326
- package/src/file-consent-helpers.ts +0 -126
- package/src/file-consent-invoke.ts +0 -150
- package/src/file-consent.test.ts +0 -363
- package/src/file-consent.ts +0 -287
- package/src/graph-chat.ts +0 -55
- package/src/graph-group-management.test.ts +0 -318
- package/src/graph-group-management.ts +0 -168
- package/src/graph-members.test.ts +0 -89
- package/src/graph-members.ts +0 -48
- package/src/graph-messages.actions.test.ts +0 -243
- package/src/graph-messages.read.test.ts +0 -391
- package/src/graph-messages.search.test.ts +0 -213
- package/src/graph-messages.test-helpers.ts +0 -50
- package/src/graph-messages.ts +0 -534
- package/src/graph-teams.test.ts +0 -215
- package/src/graph-teams.ts +0 -114
- package/src/graph-thread.test.ts +0 -246
- package/src/graph-thread.ts +0 -146
- package/src/graph-upload.test.ts +0 -258
- package/src/graph-upload.ts +0 -531
- package/src/graph-users.ts +0 -29
- package/src/graph.test.ts +0 -516
- package/src/graph.ts +0 -293
- package/src/inbound.test.ts +0 -221
- package/src/inbound.ts +0 -148
- package/src/index.ts +0 -4
- package/src/media-helpers.test.ts +0 -202
- package/src/media-helpers.ts +0 -105
- package/src/mentions.test.ts +0 -244
- package/src/mentions.ts +0 -114
- package/src/messenger.test.ts +0 -865
- package/src/messenger.ts +0 -605
- package/src/monitor-handler/access.ts +0 -125
- package/src/monitor-handler/inbound-media.test.ts +0 -289
- package/src/monitor-handler/inbound-media.ts +0 -180
- package/src/monitor-handler/message-handler-mock-support.test-support.ts +0 -28
- package/src/monitor-handler/message-handler.authz.test.ts +0 -669
- package/src/monitor-handler/message-handler.dm-media.test.ts +0 -54
- package/src/monitor-handler/message-handler.test-support.ts +0 -100
- package/src/monitor-handler/message-handler.thread-parent.test.ts +0 -223
- package/src/monitor-handler/message-handler.thread-session.test.ts +0 -77
- package/src/monitor-handler/message-handler.ts +0 -1000
- package/src/monitor-handler/reaction-handler.test.ts +0 -267
- package/src/monitor-handler/reaction-handler.ts +0 -210
- package/src/monitor-handler/thread-session.ts +0 -17
- package/src/monitor-handler.adaptive-card.test.ts +0 -162
- package/src/monitor-handler.feedback-authz.test.ts +0 -314
- package/src/monitor-handler.file-consent.test.ts +0 -423
- package/src/monitor-handler.sso.test.ts +0 -563
- package/src/monitor-handler.test-helpers.ts +0 -180
- package/src/monitor-handler.ts +0 -534
- package/src/monitor-handler.types.ts +0 -27
- package/src/monitor-types.ts +0 -6
- package/src/monitor.lifecycle.test.ts +0 -278
- package/src/monitor.test.ts +0 -119
- package/src/monitor.ts +0 -442
- package/src/oauth.flow.ts +0 -77
- package/src/oauth.shared.ts +0 -37
- package/src/oauth.test.ts +0 -305
- package/src/oauth.token.ts +0 -158
- package/src/oauth.ts +0 -130
- package/src/outbound.test.ts +0 -130
- package/src/outbound.ts +0 -71
- package/src/pending-uploads-fs.test.ts +0 -246
- package/src/pending-uploads-fs.ts +0 -235
- package/src/pending-uploads.test.ts +0 -173
- package/src/pending-uploads.ts +0 -121
- package/src/policy.test.ts +0 -240
- package/src/policy.ts +0 -262
- package/src/polls-store-memory.ts +0 -32
- package/src/polls.test.ts +0 -160
- package/src/polls.ts +0 -323
- package/src/presentation.ts +0 -68
- package/src/probe.test.ts +0 -77
- package/src/probe.ts +0 -132
- package/src/reply-dispatcher.test.ts +0 -437
- package/src/reply-dispatcher.ts +0 -346
- package/src/reply-stream-controller.test.ts +0 -235
- package/src/reply-stream-controller.ts +0 -147
- package/src/resolve-allowlist.test.ts +0 -250
- package/src/resolve-allowlist.ts +0 -309
- package/src/revoked-context.ts +0 -17
- package/src/runtime.ts +0 -9
- package/src/sdk-types.ts +0 -59
- package/src/sdk.test.ts +0 -666
- package/src/sdk.ts +0 -884
- package/src/secret-contract.ts +0 -49
- package/src/secret-input.ts +0 -7
- package/src/send-context.ts +0 -231
- package/src/send.test.ts +0 -493
- package/src/send.ts +0 -637
- package/src/sent-message-cache.test.ts +0 -15
- package/src/sent-message-cache.ts +0 -56
- package/src/session-route.ts +0 -40
- package/src/setup-core.ts +0 -160
- package/src/setup-surface.test.ts +0 -202
- package/src/setup-surface.ts +0 -320
- package/src/sso-token-store.test.ts +0 -72
- package/src/sso-token-store.ts +0 -166
- package/src/sso.ts +0 -300
- package/src/storage.ts +0 -25
- package/src/store-fs.ts +0 -44
- package/src/streaming-message.test.ts +0 -262
- package/src/streaming-message.ts +0 -297
- package/src/test-runtime.ts +0 -16
- package/src/thread-parent-context.test.ts +0 -224
- package/src/thread-parent-context.ts +0 -159
- package/src/token-response.ts +0 -11
- package/src/token.test.ts +0 -259
- package/src/token.ts +0 -195
- package/src/user-agent.test.ts +0 -86
- package/src/user-agent.ts +0 -53
- package/src/webhook-timeouts.ts +0 -27
- package/src/welcome-card.test.ts +0 -81
- package/src/welcome-card.ts +0 -57
- package/test-api.ts +0 -1
- 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
|
-
}
|