@openclaw/msteams 2026.3.13 → 2026.5.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/api.ts +3 -0
  2. package/channel-config-api.ts +1 -0
  3. package/channel-plugin-api.ts +2 -0
  4. package/config-api.ts +4 -0
  5. package/contract-api.ts +4 -0
  6. package/index.ts +15 -12
  7. package/openclaw.plugin.json +553 -1
  8. package/package.json +46 -12
  9. package/runtime-api.ts +73 -0
  10. package/secret-contract-api.ts +5 -0
  11. package/setup-entry.ts +13 -0
  12. package/setup-plugin-api.ts +3 -0
  13. package/src/ai-entity.ts +7 -0
  14. package/src/approval-auth.ts +44 -0
  15. package/src/attachments/bot-framework.test.ts +461 -0
  16. package/src/attachments/bot-framework.ts +362 -0
  17. package/src/attachments/download.ts +63 -19
  18. package/src/attachments/graph.test.ts +416 -0
  19. package/src/attachments/graph.ts +163 -72
  20. package/src/attachments/html.ts +33 -1
  21. package/src/attachments/payload.ts +1 -1
  22. package/src/attachments/remote-media.test.ts +137 -0
  23. package/src/attachments/remote-media.ts +75 -8
  24. package/src/attachments/shared.test.ts +138 -1
  25. package/src/attachments/shared.ts +193 -26
  26. package/src/attachments/types.ts +10 -0
  27. package/src/attachments.graph.test.ts +342 -0
  28. package/src/attachments.helpers.test.ts +246 -0
  29. package/src/attachments.test-helpers.ts +17 -0
  30. package/src/attachments.test.ts +163 -418
  31. package/src/attachments.ts +5 -5
  32. package/src/block-streaming-config.test.ts +61 -0
  33. package/src/channel-api.ts +1 -0
  34. package/src/channel.actions.test.ts +742 -0
  35. package/src/channel.directory.test.ts +145 -4
  36. package/src/channel.runtime.ts +56 -0
  37. package/src/channel.setup.ts +77 -0
  38. package/src/channel.test.ts +128 -0
  39. package/src/channel.ts +1077 -395
  40. package/src/config-schema.ts +6 -0
  41. package/src/config-ui-hints.ts +12 -0
  42. package/src/conversation-store-fs.test.ts +4 -5
  43. package/src/conversation-store-fs.ts +35 -51
  44. package/src/conversation-store-helpers.test.ts +202 -0
  45. package/src/conversation-store-helpers.ts +105 -0
  46. package/src/conversation-store-memory.ts +27 -23
  47. package/src/conversation-store.shared.test.ts +225 -0
  48. package/src/conversation-store.ts +30 -0
  49. package/src/directory-live.test.ts +156 -0
  50. package/src/directory-live.ts +7 -4
  51. package/src/doctor.ts +27 -0
  52. package/src/errors.test.ts +64 -1
  53. package/src/errors.ts +50 -9
  54. package/src/feedback-reflection-prompt.ts +117 -0
  55. package/src/feedback-reflection-store.ts +114 -0
  56. package/src/feedback-reflection.test.ts +237 -0
  57. package/src/feedback-reflection.ts +283 -0
  58. package/src/file-consent-helpers.test.ts +83 -0
  59. package/src/file-consent-helpers.ts +64 -11
  60. package/src/file-consent-invoke.ts +150 -0
  61. package/src/file-consent.test.ts +363 -0
  62. package/src/file-consent.ts +165 -4
  63. package/src/graph-chat.ts +5 -3
  64. package/src/graph-group-management.test.ts +318 -0
  65. package/src/graph-group-management.ts +168 -0
  66. package/src/graph-members.test.ts +89 -0
  67. package/src/graph-members.ts +48 -0
  68. package/src/graph-messages.actions.test.ts +243 -0
  69. package/src/graph-messages.read.test.ts +391 -0
  70. package/src/graph-messages.search.test.ts +213 -0
  71. package/src/graph-messages.test-helpers.ts +50 -0
  72. package/src/graph-messages.ts +534 -0
  73. package/src/graph-teams.test.ts +215 -0
  74. package/src/graph-teams.ts +114 -0
  75. package/src/graph-thread.test.ts +246 -0
  76. package/src/graph-thread.ts +146 -0
  77. package/src/graph-upload.test.ts +161 -4
  78. package/src/graph-upload.ts +147 -56
  79. package/src/graph.test.ts +516 -0
  80. package/src/graph.ts +233 -21
  81. package/src/inbound.test.ts +156 -1
  82. package/src/inbound.ts +101 -1
  83. package/src/media-helpers.ts +1 -1
  84. package/src/mentions.test.ts +27 -18
  85. package/src/mentions.ts +2 -2
  86. package/src/messenger.test.ts +504 -23
  87. package/src/messenger.ts +133 -52
  88. package/src/monitor-handler/access.ts +125 -0
  89. package/src/monitor-handler/inbound-media.test.ts +289 -0
  90. package/src/monitor-handler/inbound-media.ts +57 -5
  91. package/src/monitor-handler/message-handler-mock-support.test-support.ts +28 -0
  92. package/src/monitor-handler/message-handler.authz.test.ts +588 -74
  93. package/src/monitor-handler/message-handler.dm-media.test.ts +54 -0
  94. package/src/monitor-handler/message-handler.test-support.ts +100 -0
  95. package/src/monitor-handler/message-handler.thread-parent.test.ts +223 -0
  96. package/src/monitor-handler/message-handler.thread-session.test.ts +77 -0
  97. package/src/monitor-handler/message-handler.ts +470 -164
  98. package/src/monitor-handler/reaction-handler.test.ts +267 -0
  99. package/src/monitor-handler/reaction-handler.ts +210 -0
  100. package/src/monitor-handler/thread-session.ts +17 -0
  101. package/src/monitor-handler.adaptive-card.test.ts +162 -0
  102. package/src/monitor-handler.feedback-authz.test.ts +314 -0
  103. package/src/monitor-handler.file-consent.test.ts +281 -79
  104. package/src/monitor-handler.sso.test.ts +563 -0
  105. package/src/monitor-handler.test-helpers.ts +180 -0
  106. package/src/monitor-handler.ts +459 -115
  107. package/src/monitor-handler.types.ts +27 -0
  108. package/src/monitor-types.ts +1 -0
  109. package/src/monitor.lifecycle.test.ts +74 -10
  110. package/src/monitor.test.ts +35 -1
  111. package/src/monitor.ts +143 -46
  112. package/src/oauth.flow.ts +77 -0
  113. package/src/oauth.shared.ts +37 -0
  114. package/src/oauth.test.ts +305 -0
  115. package/src/oauth.token.ts +158 -0
  116. package/src/oauth.ts +130 -0
  117. package/src/outbound.test.ts +10 -11
  118. package/src/outbound.ts +62 -44
  119. package/src/pending-uploads-fs.test.ts +246 -0
  120. package/src/pending-uploads-fs.ts +235 -0
  121. package/src/pending-uploads.test.ts +173 -0
  122. package/src/pending-uploads.ts +34 -2
  123. package/src/policy.test.ts +11 -5
  124. package/src/policy.ts +5 -5
  125. package/src/polls.test.ts +106 -5
  126. package/src/polls.ts +15 -7
  127. package/src/presentation.ts +68 -0
  128. package/src/probe.test.ts +27 -8
  129. package/src/probe.ts +43 -9
  130. package/src/reply-dispatcher.test.ts +437 -0
  131. package/src/reply-dispatcher.ts +259 -73
  132. package/src/reply-stream-controller.test.ts +235 -0
  133. package/src/reply-stream-controller.ts +147 -0
  134. package/src/resolve-allowlist.test.ts +105 -1
  135. package/src/resolve-allowlist.ts +112 -7
  136. package/src/runtime.ts +6 -3
  137. package/src/sdk-types.ts +43 -3
  138. package/src/sdk.test.ts +666 -0
  139. package/src/sdk.ts +867 -16
  140. package/src/secret-contract.ts +49 -0
  141. package/src/secret-input.ts +1 -1
  142. package/src/send-context.ts +76 -9
  143. package/src/send.test.ts +389 -5
  144. package/src/send.ts +140 -32
  145. package/src/sent-message-cache.ts +30 -18
  146. package/src/session-route.ts +40 -0
  147. package/src/setup-core.ts +160 -0
  148. package/src/setup-surface.test.ts +202 -0
  149. package/src/setup-surface.ts +320 -0
  150. package/src/sso-token-store.test.ts +72 -0
  151. package/src/sso-token-store.ts +166 -0
  152. package/src/sso.ts +300 -0
  153. package/src/storage.ts +1 -1
  154. package/src/store-fs.ts +2 -2
  155. package/src/streaming-message.test.ts +262 -0
  156. package/src/streaming-message.ts +297 -0
  157. package/src/test-runtime.ts +1 -1
  158. package/src/thread-parent-context.test.ts +224 -0
  159. package/src/thread-parent-context.ts +159 -0
  160. package/src/token.test.ts +237 -50
  161. package/src/token.ts +162 -7
  162. package/src/user-agent.test.ts +86 -0
  163. package/src/user-agent.ts +53 -0
  164. package/src/webhook-timeouts.ts +27 -0
  165. package/src/welcome-card.test.ts +81 -0
  166. package/src/welcome-card.ts +57 -0
  167. package/test-api.ts +1 -0
  168. package/tsconfig.json +16 -0
  169. package/CHANGELOG.md +0 -107
  170. package/src/file-lock.ts +0 -1
  171. package/src/graph-users.test.ts +0 -66
  172. package/src/onboarding.ts +0 -381
  173. package/src/polls-store.test.ts +0 -38
  174. package/src/revoked-context.test.ts +0 -39
  175. package/src/token-response.test.ts +0 -23
@@ -0,0 +1,314 @@
1
+ import { access, mkdtemp, readFile, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "../runtime-api.js";
6
+ import {
7
+ type MSTeamsActivityHandler,
8
+ type MSTeamsMessageHandlerDeps,
9
+ registerMSTeamsHandlers,
10
+ } from "./monitor-handler.js";
11
+ import {
12
+ createActivityHandler,
13
+ createMSTeamsMessageHandlerDeps,
14
+ } from "./monitor-handler.test-helpers.js";
15
+ import { setMSTeamsRuntime } from "./runtime.js";
16
+ import type { MSTeamsTurnContext } from "./sdk-types.js";
17
+
18
+ const feedbackReflectionMockState = vi.hoisted(() => ({
19
+ runFeedbackReflection: vi.fn(),
20
+ }));
21
+
22
+ vi.mock("./monitor-handler/message-handler.js", () => ({
23
+ createMSTeamsMessageHandler: () => async () => {},
24
+ }));
25
+
26
+ vi.mock("./monitor-handler/reaction-handler.js", () => ({
27
+ createMSTeamsReactionHandler: () => async () => {},
28
+ }));
29
+
30
+ vi.mock("./feedback-reflection.js", async () => {
31
+ const actual = await vi.importActual<typeof import("./feedback-reflection.js")>(
32
+ "./feedback-reflection.js",
33
+ );
34
+ return {
35
+ ...actual,
36
+ runFeedbackReflection: feedbackReflectionMockState.runFeedbackReflection,
37
+ };
38
+ });
39
+
40
+ function createRuntimeStub(readAllowFromStore: ReturnType<typeof vi.fn>): PluginRuntime {
41
+ return {
42
+ logging: {
43
+ shouldLogVerbose: () => false,
44
+ },
45
+ channel: {
46
+ debounce: {
47
+ resolveInboundDebounceMs: () => 0,
48
+ createInboundDebouncer: () => ({
49
+ enqueue: async () => {},
50
+ }),
51
+ },
52
+ pairing: {
53
+ readAllowFromStore,
54
+ upsertPairingRequest: vi.fn(async () => null),
55
+ },
56
+ routing: {
57
+ resolveAgentRoute: ({ peer }: { peer: { kind: string; id: string } }) => ({
58
+ sessionKey: `msteams:${peer.kind}:${peer.id}`,
59
+ agentId: "default",
60
+ }),
61
+ },
62
+ session: {
63
+ resolveStorePath: (storePath?: string) => storePath ?? tmpdir(),
64
+ },
65
+ },
66
+ } as unknown as PluginRuntime;
67
+ }
68
+
69
+ function createDeps(params: {
70
+ cfg: OpenClawConfig;
71
+ readAllowFromStore?: ReturnType<typeof vi.fn>;
72
+ }): MSTeamsMessageHandlerDeps {
73
+ const readAllowFromStore = params.readAllowFromStore ?? vi.fn(async () => []);
74
+ setMSTeamsRuntime(createRuntimeStub(readAllowFromStore));
75
+ return createMSTeamsMessageHandlerDeps({
76
+ cfg: params.cfg,
77
+ runtime: { error: vi.fn() } as unknown as RuntimeEnv,
78
+ });
79
+ }
80
+
81
+ function createFeedbackInvokeContext(params: {
82
+ reaction: "like" | "dislike";
83
+ conversationId: string;
84
+ conversationType: string;
85
+ senderId: string;
86
+ senderName?: string;
87
+ teamId?: string;
88
+ channelName?: string;
89
+ comment?: string;
90
+ }): MSTeamsTurnContext {
91
+ return {
92
+ activity: {
93
+ id: `invoke-${params.reaction}`,
94
+ type: "invoke",
95
+ name: "message/submitAction",
96
+ channelId: "msteams",
97
+ serviceUrl: "https://service.example.test",
98
+ from: {
99
+ id: `${params.senderId}-botframework`,
100
+ aadObjectId: params.senderId,
101
+ name: params.senderName ?? "Sender",
102
+ },
103
+ recipient: {
104
+ id: "bot-id",
105
+ name: "Bot",
106
+ },
107
+ conversation: {
108
+ id: params.conversationId,
109
+ conversationType: params.conversationType,
110
+ tenantId: params.teamId ? "tenant-1" : undefined,
111
+ },
112
+ channelData: params.teamId
113
+ ? {
114
+ team: { id: params.teamId, name: "Team 1" },
115
+ channel: params.channelName ? { name: params.channelName } : undefined,
116
+ }
117
+ : {},
118
+ value: {
119
+ actionName: "feedback",
120
+ actionValue: {
121
+ reaction: params.reaction,
122
+ feedback: JSON.stringify({ feedbackText: params.comment ?? "feedback text" }),
123
+ },
124
+ replyToId: "bot-msg-1",
125
+ },
126
+ },
127
+ sendActivity: vi.fn(async () => ({ id: "ignored" })),
128
+ sendActivities: async () => [],
129
+ } as unknown as MSTeamsTurnContext;
130
+ }
131
+
132
+ async function expectFileMissing(filePath: string) {
133
+ await expect(access(filePath)).rejects.toThrow();
134
+ }
135
+
136
+ async function withFeedbackHandler(params: {
137
+ cfg: OpenClawConfig;
138
+ context: Parameters<typeof createFeedbackInvokeContext>[0];
139
+ assertResult: (args: { tmpDir: string; originalRun: ReturnType<typeof vi.fn> }) => Promise<void>;
140
+ }) {
141
+ const tmpDir = await mkdtemp(path.join(tmpdir(), "openclaw-msteams-feedback-"));
142
+ try {
143
+ const originalRun = vi.fn(async () => undefined);
144
+ const handler = registerMSTeamsHandlers(
145
+ createActivityHandler(originalRun),
146
+ createDeps({
147
+ cfg: {
148
+ ...params.cfg,
149
+ session: { store: tmpDir },
150
+ },
151
+ }),
152
+ ) as MSTeamsActivityHandler & {
153
+ run: NonNullable<MSTeamsActivityHandler["run"]>;
154
+ };
155
+
156
+ await handler.run(createFeedbackInvokeContext(params.context));
157
+ await params.assertResult({ tmpDir, originalRun });
158
+ } finally {
159
+ await rm(tmpDir, { recursive: true, force: true });
160
+ }
161
+ }
162
+
163
+ describe("msteams feedback invoke authz", () => {
164
+ beforeEach(() => {
165
+ feedbackReflectionMockState.runFeedbackReflection.mockReset();
166
+ feedbackReflectionMockState.runFeedbackReflection.mockResolvedValue(undefined);
167
+ });
168
+
169
+ it("records feedback for an allowlisted DM sender", async () => {
170
+ await withFeedbackHandler({
171
+ cfg: {
172
+ channels: {
173
+ msteams: {
174
+ dmPolicy: "allowlist",
175
+ allowFrom: ["owner-aad"],
176
+ },
177
+ },
178
+ } as OpenClawConfig,
179
+ context: {
180
+ reaction: "like",
181
+ conversationId: "a:personal-chat;messageid=bot-msg-1",
182
+ conversationType: "personal",
183
+ senderId: "owner-aad",
184
+ senderName: "Owner",
185
+ comment: "allowed feedback",
186
+ },
187
+ assertResult: async ({ tmpDir, originalRun }) => {
188
+ const transcript = await readFile(
189
+ path.join(tmpDir, "msteams_direct_owner-aad.jsonl"),
190
+ "utf-8",
191
+ );
192
+ expect(JSON.parse(transcript.trim())).toMatchObject({
193
+ event: "feedback",
194
+ messageId: "bot-msg-1",
195
+ value: "positive",
196
+ comment: "allowed feedback",
197
+ sessionKey: "msteams:direct:owner-aad",
198
+ conversationId: "a:personal-chat",
199
+ });
200
+ expect(originalRun).not.toHaveBeenCalled();
201
+ },
202
+ });
203
+ });
204
+
205
+ it("keeps DM feedback allowed when team route allowlists exist", async () => {
206
+ await withFeedbackHandler({
207
+ cfg: {
208
+ channels: {
209
+ msteams: {
210
+ dmPolicy: "allowlist",
211
+ allowFrom: ["owner-aad"],
212
+ teams: {
213
+ team123: {
214
+ channels: {
215
+ "19:group@thread.tacv2": { requireMention: false },
216
+ },
217
+ },
218
+ },
219
+ },
220
+ },
221
+ } as OpenClawConfig,
222
+ context: {
223
+ reaction: "like",
224
+ conversationId: "a:personal-chat;messageid=bot-msg-1",
225
+ conversationType: "personal",
226
+ senderId: "owner-aad",
227
+ senderName: "Owner",
228
+ comment: "allowed dm feedback",
229
+ },
230
+ assertResult: async ({ tmpDir, originalRun }) => {
231
+ const transcript = await readFile(
232
+ path.join(tmpDir, "msteams_direct_owner-aad.jsonl"),
233
+ "utf-8",
234
+ );
235
+ expect(JSON.parse(transcript.trim())).toMatchObject({
236
+ event: "feedback",
237
+ value: "positive",
238
+ comment: "allowed dm feedback",
239
+ sessionKey: "msteams:direct:owner-aad",
240
+ });
241
+ expect(originalRun).not.toHaveBeenCalled();
242
+ },
243
+ });
244
+ });
245
+
246
+ it("does not record feedback for a DM sender outside allowFrom", async () => {
247
+ await withFeedbackHandler({
248
+ cfg: {
249
+ channels: {
250
+ msteams: {
251
+ dmPolicy: "allowlist",
252
+ allowFrom: ["owner-aad"],
253
+ },
254
+ },
255
+ } as OpenClawConfig,
256
+ context: {
257
+ reaction: "like",
258
+ conversationId: "a:personal-chat;messageid=bot-msg-1",
259
+ conversationType: "personal",
260
+ senderId: "attacker-aad",
261
+ senderName: "Attacker",
262
+ comment: "blocked feedback",
263
+ },
264
+ assertResult: async ({ tmpDir, originalRun }) => {
265
+ await expectFileMissing(path.join(tmpDir, "msteams_direct_attacker-aad.jsonl"));
266
+ expect(feedbackReflectionMockState.runFeedbackReflection).not.toHaveBeenCalled();
267
+ expect(originalRun).not.toHaveBeenCalled();
268
+ },
269
+ });
270
+ });
271
+
272
+ it("does not trigger reflection for a group sender outside groupAllowFrom", async () => {
273
+ const tmpDir = await mkdtemp(path.join(tmpdir(), "openclaw-msteams-feedback-"));
274
+ try {
275
+ const originalRun = vi.fn(async () => undefined);
276
+ const handler = registerMSTeamsHandlers(
277
+ createActivityHandler(originalRun),
278
+ createDeps({
279
+ cfg: {
280
+ session: { store: tmpDir },
281
+ channels: {
282
+ msteams: {
283
+ groupPolicy: "allowlist",
284
+ groupAllowFrom: ["owner-aad"],
285
+ feedbackReflection: true,
286
+ },
287
+ },
288
+ } as OpenClawConfig,
289
+ }),
290
+ ) as MSTeamsActivityHandler & {
291
+ run: NonNullable<MSTeamsActivityHandler["run"]>;
292
+ };
293
+
294
+ await handler.run(
295
+ createFeedbackInvokeContext({
296
+ reaction: "dislike",
297
+ conversationId: "19:group@thread.tacv2;messageid=bot-msg-1",
298
+ conversationType: "groupChat",
299
+ senderId: "attacker-aad",
300
+ senderName: "Attacker",
301
+ teamId: "team-1",
302
+ channelName: "General",
303
+ comment: "blocked reflection",
304
+ }),
305
+ );
306
+
307
+ await expectFileMissing(path.join(tmpDir, "msteams_group_19_group_thread_tacv2.jsonl"));
308
+ expect(feedbackReflectionMockState.runFeedbackReflection).not.toHaveBeenCalled();
309
+ expect(originalRun).not.toHaveBeenCalled();
310
+ } finally {
311
+ await rm(tmpDir, { recursive: true, force: true });
312
+ }
313
+ });
314
+ });