@openclaw/msteams 2026.2.22 → 2026.2.24

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.
@@ -49,6 +49,28 @@ const runtimeStub = {
49
49
  },
50
50
  } as unknown as PluginRuntime;
51
51
 
52
+ const createNoopAdapter = (): MSTeamsAdapter => ({
53
+ continueConversation: async () => {},
54
+ process: async () => {},
55
+ });
56
+
57
+ const createRecordedSendActivity = (
58
+ sink: string[],
59
+ failFirstWithStatusCode?: number,
60
+ ): ((activity: unknown) => Promise<{ id: string }>) => {
61
+ let attempts = 0;
62
+ return async (activity: unknown) => {
63
+ const { text } = activity as { text?: string };
64
+ const content = text ?? "";
65
+ sink.push(content);
66
+ attempts += 1;
67
+ if (failFirstWithStatusCode !== undefined && attempts === 1) {
68
+ throw Object.assign(new Error("send failed"), { statusCode: failFirstWithStatusCode });
69
+ }
70
+ return { id: `id:${content}` };
71
+ };
72
+ };
73
+
52
74
  describe("msteams messenger", () => {
53
75
  beforeEach(() => {
54
76
  setMSTeamsRuntime(runtimeStub);
@@ -117,17 +139,9 @@ describe("msteams messenger", () => {
117
139
  it("sends thread messages via the provided context", async () => {
118
140
  const sent: string[] = [];
119
141
  const ctx = {
120
- sendActivity: async (activity: unknown) => {
121
- const { text } = activity as { text?: string };
122
- sent.push(text ?? "");
123
- return { id: `id:${text ?? ""}` };
124
- },
125
- };
126
-
127
- const adapter: MSTeamsAdapter = {
128
- continueConversation: async () => {},
129
- process: async () => {},
142
+ sendActivity: createRecordedSendActivity(sent),
130
143
  };
144
+ const adapter = createNoopAdapter();
131
145
 
132
146
  const ids = await sendMSTeamsMessages({
133
147
  replyStyle: "thread",
@@ -149,11 +163,7 @@ describe("msteams messenger", () => {
149
163
  continueConversation: async (_appId, reference, logic) => {
150
164
  seen.reference = reference;
151
165
  await logic({
152
- sendActivity: async (activity: unknown) => {
153
- const { text } = activity as { text?: string };
154
- seen.texts.push(text ?? "");
155
- return { id: `id:${text ?? ""}` };
156
- },
166
+ sendActivity: createRecordedSendActivity(seen.texts),
157
167
  });
158
168
  },
159
169
  process: async () => {},
@@ -192,10 +202,7 @@ describe("msteams messenger", () => {
192
202
  },
193
203
  };
194
204
 
195
- const adapter: MSTeamsAdapter = {
196
- continueConversation: async () => {},
197
- process: async () => {},
198
- };
205
+ const adapter = createNoopAdapter();
199
206
 
200
207
  const ids = await sendMSTeamsMessages({
201
208
  replyStyle: "thread",
@@ -242,20 +249,9 @@ describe("msteams messenger", () => {
242
249
  const retryEvents: Array<{ nextAttempt: number; delayMs: number }> = [];
243
250
 
244
251
  const ctx = {
245
- sendActivity: async (activity: unknown) => {
246
- const { text } = activity as { text?: string };
247
- attempts.push(text ?? "");
248
- if (attempts.length === 1) {
249
- throw Object.assign(new Error("throttled"), { statusCode: 429 });
250
- }
251
- return { id: `id:${text ?? ""}` };
252
- },
253
- };
254
-
255
- const adapter: MSTeamsAdapter = {
256
- continueConversation: async () => {},
257
- process: async () => {},
252
+ sendActivity: createRecordedSendActivity(attempts, 429),
258
253
  };
254
+ const adapter = createNoopAdapter();
259
255
 
260
256
  const ids = await sendMSTeamsMessages({
261
257
  replyStyle: "thread",
@@ -280,10 +276,7 @@ describe("msteams messenger", () => {
280
276
  },
281
277
  };
282
278
 
283
- const adapter: MSTeamsAdapter = {
284
- continueConversation: async () => {},
285
- process: async () => {},
286
- };
279
+ const adapter = createNoopAdapter();
287
280
 
288
281
  await expect(
289
282
  sendMSTeamsMessages({
@@ -303,18 +296,7 @@ describe("msteams messenger", () => {
303
296
 
304
297
  const adapter: MSTeamsAdapter = {
305
298
  continueConversation: async (_appId, _reference, logic) => {
306
- await logic({
307
- sendActivity: async (activity: unknown) => {
308
- const { text } = activity as { text?: string };
309
- attempts.push(text ?? "");
310
- if (attempts.length === 1) {
311
- throw Object.assign(new Error("server error"), {
312
- statusCode: 503,
313
- });
314
- }
315
- return { id: `id:${text ?? ""}` };
316
- },
317
- });
299
+ await logic({ sendActivity: createRecordedSendActivity(attempts, 503) });
318
300
  },
319
301
  process: async () => {},
320
302
  };
@@ -6,6 +6,7 @@ import {
6
6
  recordPendingHistoryEntryIfEnabled,
7
7
  resolveControlCommandGate,
8
8
  resolveDefaultGroupPolicy,
9
+ isDangerousNameMatchingEnabled,
9
10
  resolveMentionGating,
10
11
  formatAllowlistMatchMeta,
11
12
  type HistoryEntry,
@@ -145,10 +146,12 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
145
146
 
146
147
  if (dmPolicy !== "open") {
147
148
  const effectiveAllowFrom = [...allowFrom.map((v) => String(v)), ...storedAllowFrom];
149
+ const allowNameMatching = isDangerousNameMatchingEnabled(msteamsCfg);
148
150
  const allowMatch = resolveMSTeamsAllowlistMatch({
149
151
  allowFrom: effectiveAllowFrom,
150
152
  senderId,
151
153
  senderName,
154
+ allowNameMatching,
152
155
  });
153
156
 
154
157
  if (!allowMatch.allowed) {
@@ -226,10 +229,12 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
226
229
  return;
227
230
  }
228
231
  if (effectiveGroupAllowFrom.length > 0) {
232
+ const allowNameMatching = isDangerousNameMatchingEnabled(msteamsCfg);
229
233
  const allowMatch = resolveMSTeamsAllowlistMatch({
230
234
  allowFrom: effectiveGroupAllowFrom,
231
235
  senderId,
232
236
  senderName,
237
+ allowNameMatching,
233
238
  });
234
239
  if (!allowMatch.allowed) {
235
240
  log.debug?.("dropping group message (not in groupAllowFrom)", {
@@ -248,12 +253,14 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
248
253
  allowFrom: effectiveDmAllowFrom,
249
254
  senderId,
250
255
  senderName,
256
+ allowNameMatching: isDangerousNameMatchingEnabled(msteamsCfg),
251
257
  });
252
258
  const groupAllowedForCommands = isMSTeamsGroupAllowed({
253
259
  groupPolicy: "allowlist",
254
260
  allowFrom: effectiveGroupAllowFrom,
255
261
  senderId,
256
262
  senderName,
263
+ allowNameMatching: isDangerousNameMatchingEnabled(msteamsCfg),
257
264
  });
258
265
  const hasControlCommandInMessage = core.channel.text.hasControlCommand(text, cfg);
259
266
  const commandGate = resolveControlCommandGate({
@@ -184,7 +184,7 @@ describe("msteams policy", () => {
184
184
  ).toBe(true);
185
185
  });
186
186
 
187
- it("allows allowlist when sender name matches", () => {
187
+ it("blocks sender-name allowlist matches by default", () => {
188
188
  expect(
189
189
  isMSTeamsGroupAllowed({
190
190
  groupPolicy: "allowlist",
@@ -192,6 +192,18 @@ describe("msteams policy", () => {
192
192
  senderId: "other",
193
193
  senderName: "User",
194
194
  }),
195
+ ).toBe(false);
196
+ });
197
+
198
+ it("allows sender-name allowlist matches when explicitly enabled", () => {
199
+ expect(
200
+ isMSTeamsGroupAllowed({
201
+ groupPolicy: "allowlist",
202
+ allowFrom: ["user"],
203
+ senderId: "other",
204
+ senderName: "User",
205
+ allowNameMatching: true,
206
+ }),
195
207
  ).toBe(true);
196
208
  });
197
209
 
package/src/policy.ts CHANGED
@@ -209,6 +209,7 @@ export function resolveMSTeamsAllowlistMatch(params: {
209
209
  allowFrom: Array<string | number>;
210
210
  senderId: string;
211
211
  senderName?: string | null;
212
+ allowNameMatching?: boolean;
212
213
  }): MSTeamsAllowlistMatch {
213
214
  return resolveAllowlistMatchSimple(params);
214
215
  }
@@ -245,6 +246,7 @@ export function isMSTeamsGroupAllowed(params: {
245
246
  allowFrom: Array<string | number>;
246
247
  senderId: string;
247
248
  senderName?: string | null;
249
+ allowNameMatching?: boolean;
248
250
  }): boolean {
249
251
  const { groupPolicy } = params;
250
252
  if (groupPolicy === "disabled") {
@@ -68,6 +68,7 @@ export function createMSTeamsReplyDispatcher(params: {
68
68
  core.channel.reply.createReplyDispatcherWithTyping({
69
69
  ...prefixOptions,
70
70
  humanDelay: core.channel.reply.resolveHumanDelayConfig(params.cfg, params.agentId),
71
+ typingCallbacks,
71
72
  deliver: async (payload) => {
72
73
  const tableMode = core.channel.text.resolveMarkdownTableMode({
73
74
  cfg: params.cfg,
@@ -121,7 +122,6 @@ export function createMSTeamsReplyDispatcher(params: {
121
122
  hint,
122
123
  });
123
124
  },
124
- onReplyStart: typingCallbacks.onReplyStart,
125
125
  });
126
126
 
127
127
  return {