@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,423 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
import type { PluginRuntime } from "../runtime-api.js";
|
|
6
|
-
import { respondToMSTeamsFileConsentInvoke } from "./file-consent-invoke.js";
|
|
7
|
-
import { getPendingUploadFs, storePendingUploadFs } from "./pending-uploads-fs.js";
|
|
8
|
-
import { clearPendingUploads, getPendingUpload, storePendingUpload } from "./pending-uploads.js";
|
|
9
|
-
import { setMSTeamsRuntime } from "./runtime.js";
|
|
10
|
-
import type { MSTeamsTurnContext } from "./sdk-types.js";
|
|
11
|
-
|
|
12
|
-
const fileConsentMockState = vi.hoisted(() => ({
|
|
13
|
-
uploadToConsentUrl: vi.fn(),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
vi.mock("./monitor-handler/message-handler.js", () => ({
|
|
17
|
-
createMSTeamsMessageHandler: () => async () => {},
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
vi.mock("./monitor-handler/reaction-handler.js", () => ({
|
|
21
|
-
createMSTeamsReactionHandler: () => async () => {},
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
vi.mock("./file-consent.js", async () => {
|
|
25
|
-
const actual = await vi.importActual<typeof import("./file-consent.js")>("./file-consent.js");
|
|
26
|
-
return {
|
|
27
|
-
...actual,
|
|
28
|
-
uploadToConsentUrl: fileConsentMockState.uploadToConsentUrl,
|
|
29
|
-
};
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
function createRuntimeStub(stateDir?: string): PluginRuntime {
|
|
33
|
-
return {
|
|
34
|
-
logging: {
|
|
35
|
-
shouldLogVerbose: () => false,
|
|
36
|
-
},
|
|
37
|
-
channel: {
|
|
38
|
-
debounce: {
|
|
39
|
-
resolveInboundDebounceMs: () => 0,
|
|
40
|
-
createInboundDebouncer: () => ({
|
|
41
|
-
enqueue: async () => {},
|
|
42
|
-
}),
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
state: {
|
|
46
|
-
resolveStateDir: (env?: NodeJS.ProcessEnv) => {
|
|
47
|
-
const override = env?.OPENCLAW_STATE_DIR?.trim();
|
|
48
|
-
if (override) {
|
|
49
|
-
return override;
|
|
50
|
-
}
|
|
51
|
-
return stateDir ?? path.join(os.homedir(), ".openclaw");
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
} as unknown as PluginRuntime;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const runtimeStub: PluginRuntime = createRuntimeStub();
|
|
58
|
-
|
|
59
|
-
const log = {
|
|
60
|
-
debug: vi.fn(),
|
|
61
|
-
info: vi.fn(),
|
|
62
|
-
error: vi.fn(),
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
function createInvokeContext(params: {
|
|
66
|
-
conversationId: string;
|
|
67
|
-
uploadId: string;
|
|
68
|
-
action: "accept" | "decline";
|
|
69
|
-
}): {
|
|
70
|
-
context: MSTeamsTurnContext;
|
|
71
|
-
sendActivity: ReturnType<typeof vi.fn>;
|
|
72
|
-
updateActivity: ReturnType<typeof vi.fn>;
|
|
73
|
-
} {
|
|
74
|
-
const sendActivity = vi.fn(async () => ({ id: "activity-id" }));
|
|
75
|
-
const updateActivity = vi.fn(async () => ({ id: "activity-id" }));
|
|
76
|
-
const uploadInfo =
|
|
77
|
-
params.action === "accept"
|
|
78
|
-
? {
|
|
79
|
-
name: "secret.txt",
|
|
80
|
-
uploadUrl: "https://upload.example.com/put",
|
|
81
|
-
contentUrl: "https://content.example.com/file",
|
|
82
|
-
uniqueId: "unique-id",
|
|
83
|
-
fileType: "txt",
|
|
84
|
-
}
|
|
85
|
-
: undefined;
|
|
86
|
-
return {
|
|
87
|
-
context: {
|
|
88
|
-
activity: {
|
|
89
|
-
type: "invoke",
|
|
90
|
-
name: "fileConsent/invoke",
|
|
91
|
-
conversation: { id: params.conversationId },
|
|
92
|
-
value: {
|
|
93
|
-
type: "fileUpload",
|
|
94
|
-
action: params.action,
|
|
95
|
-
uploadInfo,
|
|
96
|
-
context: { uploadId: params.uploadId },
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
sendActivity,
|
|
100
|
-
sendActivities: async () => [],
|
|
101
|
-
updateActivity,
|
|
102
|
-
} as unknown as MSTeamsTurnContext,
|
|
103
|
-
sendActivity,
|
|
104
|
-
updateActivity,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function createConsentInvokeHarness(params: {
|
|
109
|
-
pendingConversationId?: string;
|
|
110
|
-
invokeConversationId: string;
|
|
111
|
-
action: "accept" | "decline";
|
|
112
|
-
consentCardActivityId?: string;
|
|
113
|
-
}) {
|
|
114
|
-
const uploadId = storePendingUpload({
|
|
115
|
-
buffer: Buffer.from("TOP_SECRET_VICTIM_FILE\n"),
|
|
116
|
-
filename: "secret.txt",
|
|
117
|
-
contentType: "text/plain",
|
|
118
|
-
conversationId: params.pendingConversationId ?? "19:victim@thread.v2",
|
|
119
|
-
consentCardActivityId: params.consentCardActivityId,
|
|
120
|
-
});
|
|
121
|
-
const { context, sendActivity, updateActivity } = createInvokeContext({
|
|
122
|
-
conversationId: params.invokeConversationId,
|
|
123
|
-
uploadId,
|
|
124
|
-
action: params.action,
|
|
125
|
-
});
|
|
126
|
-
return { uploadId, context, sendActivity, updateActivity };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function requirePendingUpload(uploadId: string) {
|
|
130
|
-
const upload = getPendingUpload(uploadId);
|
|
131
|
-
if (!upload) {
|
|
132
|
-
throw new Error(`expected pending upload ${uploadId}`);
|
|
133
|
-
}
|
|
134
|
-
return upload;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
describe("msteams file consent invoke authz", () => {
|
|
138
|
-
beforeEach(() => {
|
|
139
|
-
setMSTeamsRuntime(runtimeStub);
|
|
140
|
-
clearPendingUploads();
|
|
141
|
-
vi.clearAllMocks();
|
|
142
|
-
fileConsentMockState.uploadToConsentUrl.mockReset();
|
|
143
|
-
fileConsentMockState.uploadToConsentUrl.mockResolvedValue(undefined);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("uploads when invoke conversation matches pending upload conversation", async () => {
|
|
147
|
-
const { uploadId, context, sendActivity } = createConsentInvokeHarness({
|
|
148
|
-
invokeConversationId: "19:victim@thread.v2;messageid=abc123",
|
|
149
|
-
action: "accept",
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
await respondToMSTeamsFileConsentInvoke(context, log);
|
|
153
|
-
|
|
154
|
-
// invokeResponse should be sent immediately
|
|
155
|
-
expect(sendActivity).toHaveBeenCalledWith(
|
|
156
|
-
expect.objectContaining({
|
|
157
|
-
type: "invokeResponse",
|
|
158
|
-
}),
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledTimes(1);
|
|
162
|
-
|
|
163
|
-
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledWith(
|
|
164
|
-
expect.objectContaining({
|
|
165
|
-
url: "https://upload.example.com/put",
|
|
166
|
-
}),
|
|
167
|
-
);
|
|
168
|
-
expect(getPendingUpload(uploadId)).toBeUndefined();
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("calls updateActivity to replace the consent card when consentCardActivityId is set", async () => {
|
|
172
|
-
const { context, sendActivity, updateActivity } = createConsentInvokeHarness({
|
|
173
|
-
invokeConversationId: "19:victim@thread.v2;messageid=abc123",
|
|
174
|
-
action: "accept",
|
|
175
|
-
consentCardActivityId: "consent-card-activity-id-123",
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
await respondToMSTeamsFileConsentInvoke(context, log);
|
|
179
|
-
|
|
180
|
-
expect(sendActivity).toHaveBeenCalledWith(expect.objectContaining({ type: "invokeResponse" }));
|
|
181
|
-
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledTimes(1);
|
|
182
|
-
|
|
183
|
-
// Should replace the original consent card with the file info card
|
|
184
|
-
expect(updateActivity).toHaveBeenCalledTimes(1);
|
|
185
|
-
expect(updateActivity).toHaveBeenCalledWith(
|
|
186
|
-
expect.objectContaining({
|
|
187
|
-
id: "consent-card-activity-id-123",
|
|
188
|
-
type: "message",
|
|
189
|
-
attachments: expect.arrayContaining([
|
|
190
|
-
expect.objectContaining({
|
|
191
|
-
contentType: "application/vnd.microsoft.teams.card.file.info",
|
|
192
|
-
}),
|
|
193
|
-
]),
|
|
194
|
-
}),
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("does not send file info card via sendActivity when updateActivity succeeds", async () => {
|
|
199
|
-
const { context, sendActivity, updateActivity } = createConsentInvokeHarness({
|
|
200
|
-
invokeConversationId: "19:victim@thread.v2;messageid=abc123",
|
|
201
|
-
action: "accept",
|
|
202
|
-
consentCardActivityId: "consent-card-activity-id-happy",
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
await respondToMSTeamsFileConsentInvoke(context, log);
|
|
206
|
-
|
|
207
|
-
// updateActivity should replace the consent card in-place
|
|
208
|
-
expect(updateActivity).toHaveBeenCalledTimes(1);
|
|
209
|
-
|
|
210
|
-
// sendActivity should only be called once for the invokeResponse, NOT for the file info card
|
|
211
|
-
expect(sendActivity).toHaveBeenCalledTimes(1);
|
|
212
|
-
expect(sendActivity).toHaveBeenCalledWith(expect.objectContaining({ type: "invokeResponse" }));
|
|
213
|
-
|
|
214
|
-
// Explicitly verify no file info card was sent via sendActivity
|
|
215
|
-
for (const call of sendActivity.mock.calls) {
|
|
216
|
-
const arg = call[0] as Record<string, unknown>;
|
|
217
|
-
if (typeof arg === "object" && arg !== null && "attachments" in arg) {
|
|
218
|
-
const attachments = arg.attachments as Array<{ contentType?: string }>;
|
|
219
|
-
for (const att of attachments) {
|
|
220
|
-
expect(att.contentType).not.toBe("application/vnd.microsoft.teams.card.file.info");
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it("does not call updateActivity when no consentCardActivityId is stored", async () => {
|
|
227
|
-
const { context, updateActivity } = createConsentInvokeHarness({
|
|
228
|
-
invokeConversationId: "19:victim@thread.v2;messageid=abc123",
|
|
229
|
-
action: "accept",
|
|
230
|
-
// no consentCardActivityId
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
await respondToMSTeamsFileConsentInvoke(context, log);
|
|
234
|
-
|
|
235
|
-
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledTimes(1);
|
|
236
|
-
expect(updateActivity).not.toHaveBeenCalled();
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it("still completes upload if updateActivity throws", async () => {
|
|
240
|
-
const { uploadId, context, updateActivity } = createConsentInvokeHarness({
|
|
241
|
-
invokeConversationId: "19:victim@thread.v2;messageid=abc123",
|
|
242
|
-
action: "accept",
|
|
243
|
-
consentCardActivityId: "consent-card-activity-id-fail",
|
|
244
|
-
});
|
|
245
|
-
updateActivity.mockRejectedValueOnce(new Error("Teams API error"));
|
|
246
|
-
|
|
247
|
-
await respondToMSTeamsFileConsentInvoke(context, log);
|
|
248
|
-
|
|
249
|
-
// Upload should have completed despite updateActivity failure
|
|
250
|
-
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledTimes(1);
|
|
251
|
-
expect(getPendingUpload(uploadId)).toBeUndefined();
|
|
252
|
-
expect(updateActivity).toHaveBeenCalledTimes(1);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it("rejects cross-conversation accept invoke and keeps pending upload", async () => {
|
|
256
|
-
const { uploadId, context, sendActivity } = createConsentInvokeHarness({
|
|
257
|
-
invokeConversationId: "19:attacker@thread.v2",
|
|
258
|
-
action: "accept",
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
await respondToMSTeamsFileConsentInvoke(context, log);
|
|
262
|
-
|
|
263
|
-
// invokeResponse should be sent immediately
|
|
264
|
-
expect(sendActivity).toHaveBeenCalledWith(
|
|
265
|
-
expect.objectContaining({
|
|
266
|
-
type: "invokeResponse",
|
|
267
|
-
}),
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
expect(sendActivity).toHaveBeenCalledWith(
|
|
271
|
-
"The file upload request has expired. Please try sending the file again.",
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
expect(fileConsentMockState.uploadToConsentUrl).not.toHaveBeenCalled();
|
|
275
|
-
expect(requirePendingUpload(uploadId)).toMatchObject({
|
|
276
|
-
conversationId: "19:victim@thread.v2",
|
|
277
|
-
filename: "secret.txt",
|
|
278
|
-
contentType: "text/plain",
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it("ignores cross-conversation decline invoke and keeps pending upload", async () => {
|
|
283
|
-
const { uploadId, context, sendActivity } = createConsentInvokeHarness({
|
|
284
|
-
invokeConversationId: "19:attacker@thread.v2",
|
|
285
|
-
action: "decline",
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
await respondToMSTeamsFileConsentInvoke(context, log);
|
|
289
|
-
|
|
290
|
-
// invokeResponse should be sent immediately
|
|
291
|
-
expect(sendActivity).toHaveBeenCalledWith(
|
|
292
|
-
expect.objectContaining({
|
|
293
|
-
type: "invokeResponse",
|
|
294
|
-
}),
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
expect(fileConsentMockState.uploadToConsentUrl).not.toHaveBeenCalled();
|
|
298
|
-
expect(requirePendingUpload(uploadId)).toMatchObject({
|
|
299
|
-
conversationId: "19:victim@thread.v2",
|
|
300
|
-
filename: "secret.txt",
|
|
301
|
-
contentType: "text/plain",
|
|
302
|
-
});
|
|
303
|
-
expect(sendActivity).toHaveBeenCalledTimes(1);
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
describe("msteams file consent invoke FS fallback", () => {
|
|
308
|
-
let tmpDir: string;
|
|
309
|
-
let originalStateDir: string | undefined;
|
|
310
|
-
|
|
311
|
-
beforeEach(async () => {
|
|
312
|
-
originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
|
313
|
-
tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "openclaw-msteams-invoke-"));
|
|
314
|
-
process.env.OPENCLAW_STATE_DIR = tmpDir;
|
|
315
|
-
setMSTeamsRuntime(createRuntimeStub(tmpDir));
|
|
316
|
-
clearPendingUploads();
|
|
317
|
-
vi.clearAllMocks();
|
|
318
|
-
fileConsentMockState.uploadToConsentUrl.mockReset();
|
|
319
|
-
fileConsentMockState.uploadToConsentUrl.mockResolvedValue(undefined);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
afterEach(async () => {
|
|
323
|
-
if (originalStateDir === undefined) {
|
|
324
|
-
delete process.env.OPENCLAW_STATE_DIR;
|
|
325
|
-
} else {
|
|
326
|
-
process.env.OPENCLAW_STATE_DIR = originalStateDir;
|
|
327
|
-
}
|
|
328
|
-
try {
|
|
329
|
-
await fs.promises.rm(tmpDir, { recursive: true, force: true });
|
|
330
|
-
} catch {
|
|
331
|
-
// tmp dir may already be gone
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it("reads pending upload from FS store when in-memory store is empty (cross-process CLI path)", async () => {
|
|
336
|
-
// Simulate the CLI process writing to the FS store before exiting; the
|
|
337
|
-
// in-memory store in this (monitor) process is empty.
|
|
338
|
-
const uploadId = "cli-upload-id-123";
|
|
339
|
-
const conversationId = "19:victim@thread.v2";
|
|
340
|
-
await storePendingUploadFs({
|
|
341
|
-
id: uploadId,
|
|
342
|
-
buffer: Buffer.from("CLI PAYLOAD"),
|
|
343
|
-
filename: "cli.bin",
|
|
344
|
-
contentType: "application/octet-stream",
|
|
345
|
-
conversationId,
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
expect(getPendingUpload(uploadId)).toBeUndefined();
|
|
349
|
-
|
|
350
|
-
const sendActivity = vi.fn(async () => ({ id: "activity-id" }));
|
|
351
|
-
const updateActivity = vi.fn(async () => ({ id: "activity-id" }));
|
|
352
|
-
const context = {
|
|
353
|
-
activity: {
|
|
354
|
-
type: "invoke",
|
|
355
|
-
name: "fileConsent/invoke",
|
|
356
|
-
conversation: { id: `${conversationId};messageid=abc123` },
|
|
357
|
-
value: {
|
|
358
|
-
type: "fileUpload",
|
|
359
|
-
action: "accept",
|
|
360
|
-
uploadInfo: {
|
|
361
|
-
name: "cli.bin",
|
|
362
|
-
uploadUrl: "https://upload.example.com/put",
|
|
363
|
-
contentUrl: "https://content.example.com/cli.bin",
|
|
364
|
-
uniqueId: "unique-cli",
|
|
365
|
-
fileType: "bin",
|
|
366
|
-
},
|
|
367
|
-
context: { uploadId },
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
sendActivity,
|
|
371
|
-
sendActivities: async () => [],
|
|
372
|
-
updateActivity,
|
|
373
|
-
} as unknown as MSTeamsTurnContext;
|
|
374
|
-
|
|
375
|
-
await respondToMSTeamsFileConsentInvoke(context, log);
|
|
376
|
-
|
|
377
|
-
// The upload should have run using the FS-loaded buffer
|
|
378
|
-
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledTimes(1);
|
|
379
|
-
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledWith(
|
|
380
|
-
expect.objectContaining({
|
|
381
|
-
url: "https://upload.example.com/put",
|
|
382
|
-
}),
|
|
383
|
-
);
|
|
384
|
-
|
|
385
|
-
// FS entry should have been cleaned up after successful upload
|
|
386
|
-
expect(await getPendingUploadFs(uploadId)).toBeUndefined();
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it("cleans up FS entry on decline even when in-memory store is empty", async () => {
|
|
390
|
-
const uploadId = "cli-decline-id";
|
|
391
|
-
const conversationId = "19:victim@thread.v2";
|
|
392
|
-
await storePendingUploadFs({
|
|
393
|
-
id: uploadId,
|
|
394
|
-
buffer: Buffer.from("DECLINED"),
|
|
395
|
-
filename: "decline.txt",
|
|
396
|
-
contentType: "text/plain",
|
|
397
|
-
conversationId,
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
const sendActivity = vi.fn(async () => ({ id: "activity-id" }));
|
|
401
|
-
const updateActivity = vi.fn(async () => ({ id: "activity-id" }));
|
|
402
|
-
const context = {
|
|
403
|
-
activity: {
|
|
404
|
-
type: "invoke",
|
|
405
|
-
name: "fileConsent/invoke",
|
|
406
|
-
conversation: { id: `${conversationId};messageid=abc123` },
|
|
407
|
-
value: {
|
|
408
|
-
type: "fileUpload",
|
|
409
|
-
action: "decline",
|
|
410
|
-
context: { uploadId },
|
|
411
|
-
},
|
|
412
|
-
},
|
|
413
|
-
sendActivity,
|
|
414
|
-
sendActivities: async () => [],
|
|
415
|
-
updateActivity,
|
|
416
|
-
} as unknown as MSTeamsTurnContext;
|
|
417
|
-
|
|
418
|
-
await respondToMSTeamsFileConsentInvoke(context, log);
|
|
419
|
-
|
|
420
|
-
expect(fileConsentMockState.uploadToConsentUrl).not.toHaveBeenCalled();
|
|
421
|
-
expect(await getPendingUploadFs(uploadId)).toBeUndefined();
|
|
422
|
-
});
|
|
423
|
-
});
|