@openclaw/msteams 2026.5.12 → 2026.5.14-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.
@@ -1,7 +1,223 @@
1
- import { O as formatUnknownError, g as saveDelegatedTokens, h as resolveMSTeamsCredentials, p as hasConfiguredMSTeamsCredentials, v as normalizeSecretInputString } from "./graph-users-BQJvcsX8.js";
2
- import { c as resolveMSTeamsUserAllowlist, o as parseMSTeamsTeamEntry, s as resolveMSTeamsChannelAllowlist } from "./resolve-allowlist-DPCTpYxi.js";
1
+ import { O as formatUnknownError, c as normalizeQuery, f as resolveGraphToken, g as saveDelegatedTokens, h as resolveMSTeamsCredentials, o as listChannelsForTeam, p as hasConfiguredMSTeamsCredentials, s as listTeamsByName, t as searchGraphUsers, v as normalizeSecretInputString } from "./graph-users-ChPPxUzD.js";
2
+ import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/allow-from";
3
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
4
  import { DEFAULT_ACCOUNT_ID, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicy, createTopLevelChannelGroupPolicySetter, mergeAllowFromEntries, splitSetupEntries } from "openclaw/plugin-sdk/setup";
4
5
  import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
6
+ //#region extensions/msteams/src/resolve-allowlist.ts
7
+ function stripProviderPrefix(raw) {
8
+ return raw.replace(/^(msteams|teams):/i, "");
9
+ }
10
+ function normalizeMSTeamsMessagingTarget(raw) {
11
+ let trimmed = raw.trim();
12
+ if (!trimmed) return;
13
+ trimmed = stripProviderPrefix(trimmed).trim();
14
+ if (/^conversation:/i.test(trimmed)) {
15
+ const id = trimmed.slice(13).trim();
16
+ return id ? `conversation:${id}` : void 0;
17
+ }
18
+ if (/^user:/i.test(trimmed)) {
19
+ const id = trimmed.slice(5).trim();
20
+ return id ? `user:${id}` : void 0;
21
+ }
22
+ return trimmed || void 0;
23
+ }
24
+ function normalizeMSTeamsUserInput(raw) {
25
+ return stripProviderPrefix(raw).replace(/^(user|conversation):/i, "").trim();
26
+ }
27
+ function parseMSTeamsConversationId(raw) {
28
+ const trimmed = stripProviderPrefix(raw).trim();
29
+ if (!/^conversation:/i.test(trimmed)) return null;
30
+ return trimmed.slice(13).trim();
31
+ }
32
+ /**
33
+ * Detect whether a raw target string looks like a Microsoft Teams conversation
34
+ * or user id that cron announce delivery and other explicit-target paths can
35
+ * forward verbatim to the channel adapter.
36
+ *
37
+ * Accepts both prefixed and bare formats:
38
+ * - `conversation:<id>` — explicit conversation prefix
39
+ * - `user:<aad-guid>` — user id (16+ hex chars, UUID-like)
40
+ * - `19:abc@thread.tacv2` / `19:abc@thread.skype` — channel / legacy group
41
+ * - `19:{userId}_{appId}@unq.gbl.spaces` — Graph 1:1 chat thread format
42
+ * - `a:1xxx` — Bot Framework personal (1:1) chat id
43
+ * - `8:orgid:xxx` — Bot Framework org-scoped personal chat id
44
+ * - `29:xxx` — Bot Framework user id
45
+ *
46
+ * Display-name user targets such as `user:John Smith` intentionally return
47
+ * false so that the Graph API directory lookup still runs for them.
48
+ */
49
+ function looksLikeMSTeamsTargetId(raw) {
50
+ const trimmed = raw.trim();
51
+ if (!trimmed) return false;
52
+ if (/^conversation:/i.test(trimmed)) return true;
53
+ if (/^user:/i.test(trimmed)) {
54
+ const id = trimmed.slice(5).trim();
55
+ return /^[0-9a-fA-F-]{16,}$/.test(id);
56
+ }
57
+ if (/^19:.+@thread\.(tacv2|skype)$/i.test(trimmed)) return true;
58
+ if (/^19:.+@unq\.gbl\.spaces$/i.test(trimmed)) return true;
59
+ if (/^a:1[A-Za-z0-9_-]+$/i.test(trimmed)) return true;
60
+ if (/^8:orgid:[A-Za-z0-9-]+$/i.test(trimmed)) return true;
61
+ if (/^29:[A-Za-z0-9_-]+$/i.test(trimmed)) return true;
62
+ return /@thread\b/i.test(trimmed);
63
+ }
64
+ function normalizeMSTeamsTeamKey(raw) {
65
+ return stripProviderPrefix(raw).replace(/^team:/i, "").trim() || void 0;
66
+ }
67
+ function normalizeMSTeamsChannelKey(raw) {
68
+ return (raw?.trim().replace(/^#/, "").trim() ?? "") || void 0;
69
+ }
70
+ function normalizeMSTeamsConversationTargetId(raw) {
71
+ const trimmed = stripProviderPrefix(raw).trim();
72
+ return parseMSTeamsConversationId(trimmed) ?? trimmed;
73
+ }
74
+ function looksLikeMSTeamsThreadConversationId(raw) {
75
+ const normalized = normalizeMSTeamsConversationTargetId(raw);
76
+ return /^19:.+@thread\./i.test(normalized);
77
+ }
78
+ function parseMSTeamsTeamChannelInput(raw) {
79
+ const trimmed = stripProviderPrefix(raw).trim();
80
+ if (!trimmed) return {};
81
+ const parts = trimmed.split("/");
82
+ const team = normalizeMSTeamsTeamKey(parts[0] ?? "");
83
+ const channel = parts.length > 1 ? normalizeMSTeamsChannelKey(parts.slice(1).join("/")) : void 0;
84
+ return {
85
+ ...team ? { team } : {},
86
+ ...channel ? { channel } : {}
87
+ };
88
+ }
89
+ function parseMSTeamsTeamEntry(raw) {
90
+ const { team, channel } = parseMSTeamsTeamChannelInput(raw);
91
+ if (!team) return null;
92
+ return {
93
+ teamKey: team,
94
+ ...channel ? { channelKey: channel } : {}
95
+ };
96
+ }
97
+ async function resolveMSTeamsChannelAllowlist(params) {
98
+ let tokenPromise;
99
+ const getToken = () => {
100
+ tokenPromise ??= resolveGraphToken(params.cfg);
101
+ return tokenPromise;
102
+ };
103
+ return await mapAllowlistResolutionInputs({
104
+ inputs: params.entries,
105
+ mapInput: async (input) => {
106
+ const { team, channel } = parseMSTeamsTeamChannelInput(input);
107
+ if (!team) return {
108
+ input,
109
+ resolved: false
110
+ };
111
+ if (looksLikeMSTeamsThreadConversationId(team)) {
112
+ const teamId = normalizeMSTeamsConversationTargetId(team);
113
+ if (!channel) return {
114
+ input,
115
+ resolved: true,
116
+ teamId,
117
+ teamName: teamId
118
+ };
119
+ if (!looksLikeMSTeamsThreadConversationId(channel)) return {
120
+ input,
121
+ resolved: false,
122
+ teamId,
123
+ teamName: teamId,
124
+ note: "channel id required for conversation-id team"
125
+ };
126
+ const channelId = normalizeMSTeamsConversationTargetId(channel);
127
+ return {
128
+ input,
129
+ resolved: true,
130
+ teamId,
131
+ teamName: teamId,
132
+ channelId,
133
+ channelName: channelId
134
+ };
135
+ }
136
+ const token = await getToken();
137
+ const teams = /^[0-9a-fA-F-]{16,}$/.test(team) ? [{
138
+ id: team,
139
+ displayName: team
140
+ }] : await listTeamsByName(token, team);
141
+ if (teams.length === 0) return {
142
+ input,
143
+ resolved: false,
144
+ note: "team not found"
145
+ };
146
+ const teamMatch = teams[0];
147
+ const graphTeamId = teamMatch.id?.trim();
148
+ const teamName = teamMatch.displayName?.trim() || team;
149
+ if (!graphTeamId) return {
150
+ input,
151
+ resolved: false,
152
+ note: "team id missing"
153
+ };
154
+ let teamChannels = [];
155
+ try {
156
+ teamChannels = await listChannelsForTeam(token, graphTeamId);
157
+ } catch {}
158
+ const teamId = teamChannels.find((ch) => normalizeOptionalLowercaseString(ch.displayName) === "general")?.id?.trim() || graphTeamId;
159
+ if (!channel) return {
160
+ input,
161
+ resolved: true,
162
+ teamId,
163
+ teamName,
164
+ note: teams.length > 1 ? "multiple teams; chose first" : void 0
165
+ };
166
+ const normalizedChannel = normalizeOptionalLowercaseString(channel);
167
+ const channelMatch = teamChannels.find((item) => item.id === channel) ?? teamChannels.find((item) => normalizeOptionalLowercaseString(item.displayName) === normalizedChannel) ?? teamChannels.find((item) => normalizeLowercaseStringOrEmpty(item.displayName ?? "").includes(normalizedChannel ?? ""));
168
+ if (!channelMatch?.id) return {
169
+ input,
170
+ resolved: false,
171
+ note: "channel not found"
172
+ };
173
+ return {
174
+ input,
175
+ resolved: true,
176
+ teamId,
177
+ teamName,
178
+ channelId: channelMatch.id,
179
+ channelName: channelMatch.displayName ?? channel,
180
+ note: teamChannels.length > 1 ? "multiple channels; chose first" : void 0
181
+ };
182
+ }
183
+ });
184
+ }
185
+ async function resolveMSTeamsUserAllowlist(params) {
186
+ const token = await resolveGraphToken(params.cfg);
187
+ return await mapAllowlistResolutionInputs({
188
+ inputs: params.entries,
189
+ mapInput: async (input) => {
190
+ const query = normalizeQuery(normalizeMSTeamsUserInput(input));
191
+ if (!query) return {
192
+ input,
193
+ resolved: false
194
+ };
195
+ if (/^[0-9a-fA-F-]{16,}$/.test(query)) return {
196
+ input,
197
+ resolved: true,
198
+ id: query
199
+ };
200
+ const users = await searchGraphUsers({
201
+ token,
202
+ query,
203
+ top: 10
204
+ });
205
+ const match = users[0];
206
+ if (!match?.id) return {
207
+ input,
208
+ resolved: false
209
+ };
210
+ return {
211
+ input,
212
+ resolved: true,
213
+ id: match.id,
214
+ name: match.displayName ?? void 0,
215
+ note: users.length > 1 ? "multiple matches; chose first" : void 0
216
+ };
217
+ }
218
+ });
219
+ }
220
+ //#endregion
5
221
  //#region extensions/msteams/src/setup-core.ts
6
222
  const msteamsSetupAdapter = {
7
223
  resolveAccountId: () => DEFAULT_ACCOUNT_ID,
@@ -271,7 +487,7 @@ const msteamsSetupWizard = {
271
487
  }
272
488
  };
273
489
  try {
274
- const { loginMSTeamsDelegated } = await import("./oauth-BWJyilR1.js");
490
+ const { loginMSTeamsDelegated } = await import("./oauth-DsVj42gA.js");
275
491
  const progress = params.prompter.progress("MSTeams Delegated OAuth");
276
492
  saveDelegatedTokens(await loginMSTeamsDelegated({
277
493
  isRemote: true,
@@ -310,4 +526,4 @@ const msteamsSetupWizard = {
310
526
  })
311
527
  };
312
528
  //#endregion
313
- export { msteamsSetupAdapter as i, openDelegatedOAuthUrl as n, createMSTeamsSetupWizardBase as r, msteamsSetupWizard as t };
529
+ export { looksLikeMSTeamsTargetId as a, parseMSTeamsConversationId as c, resolveMSTeamsUserAllowlist as d, msteamsSetupAdapter as i, parseMSTeamsTeamChannelInput as l, openDelegatedOAuthUrl as n, normalizeMSTeamsMessagingTarget as o, createMSTeamsSetupWizardBase as r, normalizeMSTeamsUserInput as s, msteamsSetupWizard as t, resolveMSTeamsChannelAllowlist as u };
@@ -1,16 +1,15 @@
1
1
  import { A as summarizeMapping, D as resolveDefaultGroupPolicy, E as resolveChannelMediaMaxBytes, M as getMSTeamsRuntime, N as getOptionalMSTeamsRuntime, _ as isDangerousNameMatchingEnabled, a as buildMediaPayload, b as logTypingFailure, c as createChannelMessageReplyPipeline, f as dispatchReplyFromConfigWithSettledDispatcher$1, l as createChannelPairingController, n as DEFAULT_WEBHOOK_MAX_BODY_BYTES, t as DEFAULT_ACCOUNT_ID, v as keepHttpServerTaskAlive, x as mergeAllowlist } from "./runtime-api-C3EIaIpt.js";
2
- import { A as ATTACHMENT_TAG_RE, B as isLikelyImageAttachment, C as loadMSTeamsSdkWithAuth, D as formatMSTeamsSendErrorHint, E as classifyMSTeamsSendError, F as estimateBase64DecodedBytes, G as resolveAttachmentFetchPolicy, H as isUrlAllowed, I as extractHtmlFromAttachment, J as safeFetchWithPolicy, K as resolveMediaSsrfPolicy, L as extractInlineImageCandidates, M as IMG_SRC_RE, N as applyAuthorizationHeaderForUrl, O as formatUnknownError, P as encodeGraphShareId, R as inferPlaceholder, S as createMSTeamsTokenProvider, T as ensureUserAgentHeader, U as normalizeContentType, V as isRecord$1, W as readNestedString, X as tryBuildGraphSharesUrlForSharedLink, Y as safeHostForUrl, _ as resolveMSTeamsStorePath, a as fetchGraphJson, b as createBotFrameworkJwtValidator, h as resolveMSTeamsCredentials, j as GRAPH_ROOT, q as resolveRequestUrl, w as buildUserAgent, x as createMSTeamsAdapter, z as isDownloadableAttachment } from "./graph-users-BQJvcsX8.js";
3
- import { c as resolveMSTeamsUserAllowlist, s as resolveMSTeamsChannelAllowlist } from "./resolve-allowlist-DPCTpYxi.js";
4
- import { i as resolveMSTeamsRouteConfig, r as resolveMSTeamsReplyPolicy, t as resolveMSTeamsAllowlistMatch } from "./policy-bM71GXRd.js";
5
- import { C as readJsonFile, S as createMSTeamsConversationStoreFs, T as writeJsonFile, _ as buildFileInfoCard, b as createMSTeamsPollStoreFs, c as renderReplyPayloadsToMessages, d as withRevokedProxyFallback, f as resolveGraphChatId, g as removePendingUploadFs, h as getPendingUploadFs, l as sendMSTeamsMessages, m as removePendingUpload, p as getPendingUpload, s as buildConversationReference, u as AI_GENERATED_ENTITY, v as parseFileConsentInvoke, w as withFileLock, x as extractMSTeamsPollVote, y as uploadToConsentUrl } from "./probe-4kXMWuAw.js";
2
+ import { A as ATTACHMENT_TAG_RE, B as isLikelyImageAttachment, C as loadMSTeamsSdkWithAuth, D as formatMSTeamsSendErrorHint, E as classifyMSTeamsSendError, F as estimateBase64DecodedBytes, G as resolveAttachmentFetchPolicy, H as isUrlAllowed, I as extractHtmlFromAttachment, J as safeFetchWithPolicy, K as resolveMediaSsrfPolicy, L as extractInlineImageCandidates, M as IMG_SRC_RE, N as applyAuthorizationHeaderForUrl, O as formatUnknownError, P as encodeGraphShareId, R as inferPlaceholder, S as createMSTeamsTokenProvider, T as ensureUserAgentHeader, U as normalizeContentType, V as isRecord$1, W as readNestedString, X as tryBuildGraphSharesUrlForSharedLink, Y as safeHostForUrl, _ as resolveMSTeamsStorePath, a as fetchGraphJson, b as createBotFrameworkJwtValidator, h as resolveMSTeamsCredentials, j as GRAPH_ROOT, q as resolveRequestUrl, w as buildUserAgent, x as createMSTeamsAdapter, z as isDownloadableAttachment } from "./graph-users-ChPPxUzD.js";
3
+ import { d as resolveMSTeamsUserAllowlist, u as resolveMSTeamsChannelAllowlist } from "./setup-surface-CQrMX-nJ.js";
4
+ import { i as resolveMSTeamsRouteConfig, n as resolveMSTeamsAllowlistMatch, r as resolveMSTeamsReplyPolicy } from "./channel-C5CVTygn.js";
5
+ import { C as readJsonFile, S as createMSTeamsConversationStoreFs, T as writeJsonFile, _ as buildFileInfoCard, b as createMSTeamsPollStoreFs, c as renderReplyPayloadsToMessages, d as withRevokedProxyFallback, f as resolveGraphChatId, g as removePendingUploadFs, h as getPendingUploadFs, l as sendMSTeamsMessages, m as removePendingUpload, p as getPendingUpload, s as buildConversationReference, u as AI_GENERATED_ENTITY, v as parseFileConsentInvoke, w as withFileLock, x as extractMSTeamsPollVote, y as uploadToConsentUrl } from "./probe-I2DM0U-s.js";
6
6
  import { formatAllowlistMatchMeta } from "openclaw/plugin-sdk/allow-from";
7
7
  import { createLiveMessageState, createPreviewMessageReceipt, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, markLiveMessageFinalized } from "openclaw/plugin-sdk/channel-message";
8
8
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
9
9
  import { createDraftStreamLoop } from "openclaw/plugin-sdk/channel-lifecycle";
10
- import { readResponseWithLimit } from "openclaw/plugin-sdk/media-runtime";
10
+ import { saveResponseMedia } from "openclaw/plugin-sdk/media-runtime";
11
11
  import { dispatchReplyFromConfigWithSettledDispatcher, hasFinalInboundReplyDispatch, resolveInboundReplyDispatchCounts } from "openclaw/plugin-sdk/inbound-reply-dispatch";
12
12
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
13
- import { Buffer as Buffer$1 } from "node:buffer";
14
13
  import path from "node:path";
15
14
  import { appendRegularFile } from "openclaw/plugin-sdk/security-runtime";
16
15
  import { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
@@ -20,7 +19,7 @@ import { channelIngressRoutes, resolveStableChannelMessageIngress } from "opencl
20
19
  import { logInboundDrop, resolveInboundMentionDecision, resolveInboundSessionEnvelopeContext } from "openclaw/plugin-sdk/channel-inbound";
21
20
  import { filterSupplementalContextItems, resolveChannelContextVisibilityMode, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/context-visibility-runtime";
22
21
  import { DEFAULT_GROUP_HISTORY_LIMIT, buildPendingHistoryContextFromMap, recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history";
23
- import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelProgressDraftGate, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, resolveChannelPreviewStreamMode, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress } from "openclaw/plugin-sdk/channel-streaming";
22
+ import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelProgressDraftGate, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, normalizeChannelProgressDraftLineIdentity, resolveChannelPreviewStreamMode, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress } from "openclaw/plugin-sdk/channel-streaming";
24
23
  //#region extensions/msteams/src/feedback-reflection-prompt.ts
25
24
  /** Max chars of the thumbed-down response to include in the reflection prompt. */
26
25
  const MAX_RESPONSE_CHARS = 500;
@@ -624,7 +623,7 @@ async function fetchBotFrameworkAttachmentInfo(params) {
624
623
  return;
625
624
  }
626
625
  }
627
- async function fetchBotFrameworkAttachmentView(params) {
626
+ async function saveBotFrameworkAttachmentView(params) {
628
627
  const url = `${normalizeServiceUrl(params.serviceUrl)}/v3/attachments/${encodeURIComponent(params.attachmentId)}/views/${encodeURIComponent(params.viewId)}`;
629
628
  let response;
630
629
  try {
@@ -646,12 +645,16 @@ async function fetchBotFrameworkAttachmentView(params) {
646
645
  const contentLength = response.headers.get("content-length");
647
646
  if (contentLength && Number(contentLength) > params.maxBytes) return;
648
647
  try {
649
- const arrayBuffer = await response.arrayBuffer();
650
- const buffer = Buffer$1.from(arrayBuffer);
651
- if (buffer.byteLength > params.maxBytes) return;
652
- return buffer;
648
+ return await getMSTeamsRuntime().channel.media.saveResponseMedia(response, {
649
+ sourceUrl: url,
650
+ filePathHint: params.fileNameHint,
651
+ maxBytes: params.maxBytes,
652
+ fallbackContentType: params.contentTypeHint,
653
+ subdir: "inbound",
654
+ originalFilename: params.preserveFilenames ? params.fileNameHint : void 0
655
+ });
653
656
  } catch (err) {
654
- params.logger?.warn?.("msteams botFramework attachmentView body read failed", { error: err instanceof Error ? err.message : String(err) });
657
+ params.logger?.warn?.("msteams botFramework attachmentView save failed", { error: err instanceof Error ? err.message : String(err) });
655
658
  return;
656
659
  }
657
660
  }
@@ -691,40 +694,31 @@ async function downloadMSTeamsBotFrameworkAttachment(params) {
691
694
  const viewId = typeof candidateView?.viewId === "string" && candidateView.viewId ? candidateView.viewId : void 0;
692
695
  if (!viewId) return;
693
696
  if (typeof candidateView?.size === "number" && candidateView.size > 0 && candidateView.size > params.maxBytes) return;
694
- const buffer = await fetchBotFrameworkAttachmentView({
697
+ const fileNameHint = typeof params.fileNameHint === "string" && params.fileNameHint || typeof info.name === "string" && info.name || void 0;
698
+ const contentTypeHint = typeof params.contentTypeHint === "string" && params.contentTypeHint || typeof info.type === "string" && info.type || void 0;
699
+ const saved = await saveBotFrameworkAttachmentView({
695
700
  serviceUrl: params.serviceUrl,
696
701
  attachmentId: params.attachmentId,
697
702
  viewId,
698
703
  accessToken,
699
704
  maxBytes: params.maxBytes,
705
+ fileNameHint,
706
+ contentTypeHint,
707
+ preserveFilenames: params.preserveFilenames,
700
708
  policy,
701
709
  fetchFn: params.fetchFn,
702
710
  resolveFn: params.resolveFn,
703
711
  logger: params.logger
704
712
  });
705
- if (!buffer) return;
706
- const fileNameHint = typeof params.fileNameHint === "string" && params.fileNameHint || typeof info.name === "string" && info.name || void 0;
707
- const contentTypeHint = typeof params.contentTypeHint === "string" && params.contentTypeHint || typeof info.type === "string" && info.type || void 0;
708
- const mime = await getMSTeamsRuntime().media.detectMime({
709
- buffer,
710
- headerMime: contentTypeHint,
711
- filePath: fileNameHint
712
- });
713
- try {
714
- const originalFilename = params.preserveFilenames ? fileNameHint : void 0;
715
- const saved = await getMSTeamsRuntime().channel.media.saveMediaBuffer(buffer, mime ?? contentTypeHint, "inbound", params.maxBytes, originalFilename);
716
- return {
717
- path: saved.path,
713
+ if (!saved) return;
714
+ return {
715
+ path: saved.path,
716
+ contentType: saved.contentType,
717
+ placeholder: inferPlaceholder({
718
718
  contentType: saved.contentType,
719
- placeholder: inferPlaceholder({
720
- contentType: saved.contentType,
721
- fileName: fileNameHint
722
- })
723
- };
724
- } catch (err) {
725
- params.logger?.warn?.("msteams botFramework save failed", { error: err instanceof Error ? err.message : String(err) });
726
- return;
727
- }
719
+ fileName: fileNameHint
720
+ })
721
+ };
728
722
  }
729
723
  /**
730
724
  * Download media for every attachment referenced by a Bot Framework personal
@@ -780,8 +774,8 @@ async function downloadMSTeamsBotFrameworkAttachments(params) {
780
774
  * Direct fetch path used when the caller's `fetchImpl` has already validated
781
775
  * the URL against a hostname allowlist (for example `safeFetchWithPolicy`).
782
776
  *
783
- * Bypasses the strict SSRF dispatcher on `fetchRemoteMedia` because:
784
- * 1. The pinned undici dispatcher used by `fetchRemoteMedia` is incompatible
777
+ * Bypasses the strict SSRF dispatcher on `readRemoteMediaBuffer` because:
778
+ * 1. The pinned undici dispatcher used by `readRemoteMediaBuffer` is incompatible
785
779
  * with Node 24+'s built-in undici v7 (fails with "invalid onRequestStart
786
780
  * method"), which silently breaks SharePoint/OneDrive downloads. See
787
781
  * issue #63396.
@@ -789,43 +783,35 @@ async function downloadMSTeamsBotFrameworkAttachments(params) {
789
783
  * (`safeFetch` validates every redirect hop against the hostname
790
784
  * allowlist before following).
791
785
  */
792
- async function fetchRemoteMediaDirect(params) {
793
- const response = await params.fetchImpl(params.url, { redirect: "follow" });
794
- if (!response.ok) {
795
- const statusText = response.statusText ? ` ${response.statusText}` : "";
796
- throw new Error(`HTTP ${response.status}${statusText}`);
797
- }
798
- const contentLength = response.headers.get("content-length");
799
- if (contentLength) {
800
- const length = Number(contentLength);
801
- if (Number.isFinite(length) && length > params.maxBytes) throw new Error(`content length ${length} exceeds maxBytes ${params.maxBytes}`);
802
- }
803
- return {
804
- buffer: await readResponseWithLimit(response, params.maxBytes, { onOverflow: ({ size, maxBytes }) => /* @__PURE__ */ new Error(`payload size ${size} exceeds maxBytes ${maxBytes}`) }),
805
- contentType: response.headers.get("content-type") ?? void 0
806
- };
786
+ async function saveRemoteMediaDirect(params) {
787
+ return await saveResponseMedia(await params.fetchImpl(params.url, { redirect: "follow" }), {
788
+ sourceUrl: params.url,
789
+ filePathHint: params.filePathHint,
790
+ maxBytes: params.maxBytes,
791
+ fallbackContentType: params.contentTypeHint,
792
+ originalFilename: params.originalFilename
793
+ });
807
794
  }
808
795
  async function downloadAndStoreMSTeamsRemoteMedia(params) {
809
- let fetched;
810
- if (params.useDirectFetch && params.fetchImpl) fetched = await fetchRemoteMediaDirect({
796
+ const originalFilename = params.preserveFilenames ? params.filePathHint : void 0;
797
+ let saved;
798
+ if (params.useDirectFetch && params.fetchImpl) saved = await saveRemoteMediaDirect({
811
799
  url: params.url,
800
+ filePathHint: params.filePathHint,
812
801
  fetchImpl: params.fetchImpl,
813
- maxBytes: params.maxBytes
802
+ maxBytes: params.maxBytes,
803
+ contentTypeHint: params.contentTypeHint,
804
+ originalFilename
814
805
  });
815
- else fetched = await getMSTeamsRuntime().channel.media.fetchRemoteMedia({
806
+ else saved = await getMSTeamsRuntime().channel.media.saveRemoteMedia({
816
807
  url: params.url,
817
808
  fetchImpl: params.fetchImpl,
818
809
  filePathHint: params.filePathHint,
819
810
  maxBytes: params.maxBytes,
820
- ssrfPolicy: params.ssrfPolicy
811
+ ssrfPolicy: params.ssrfPolicy,
812
+ fallbackContentType: params.contentTypeHint,
813
+ originalFilename
821
814
  });
822
- const mime = await getMSTeamsRuntime().media.detectMime({
823
- buffer: fetched.buffer,
824
- headerMime: fetched.contentType ?? params.contentTypeHint,
825
- filePath: params.filePathHint
826
- });
827
- const originalFilename = params.preserveFilenames ? params.filePathHint : void 0;
828
- const saved = await getMSTeamsRuntime().channel.media.saveMediaBuffer(fetched.buffer, mime ?? params.contentTypeHint, "inbound", params.maxBytes, originalFilename);
829
815
  return {
830
816
  path: saved.path,
831
817
  contentType: saved.contentType,
@@ -1106,28 +1092,38 @@ async function downloadGraphHostedContent(params) {
1106
1092
  params.logger?.warn?.("msteams graph hostedContent base64 decode failed", { error: err instanceof Error ? err.message : String(err) });
1107
1093
  continue;
1108
1094
  }
1109
- } else if (item.id) try {
1110
- const { response: valRes, release } = await fetchWithSsrFGuard({
1111
- url: `${params.messageUrl}/hostedContents/${encodeURIComponent(item.id)}/$value`,
1112
- fetchImpl: params.fetchFn ?? fetch,
1113
- init: { headers: ensureUserAgentHeader({ Authorization: `Bearer ${params.accessToken}` }) },
1114
- policy: params.ssrfPolicy,
1115
- auditContext: "msteams.graph.hostedContent.value"
1116
- });
1095
+ } else if (item.id) {
1117
1096
  try {
1118
- if (!valRes.ok) continue;
1119
- const cl = valRes.headers.get("content-length");
1120
- if (cl && Number(cl) > params.maxBytes) continue;
1121
- const ab = await valRes.arrayBuffer();
1122
- buffer = Buffer.from(ab);
1123
- } finally {
1124
- await release();
1097
+ const valueUrl = `${params.messageUrl}/hostedContents/${encodeURIComponent(item.id)}/$value`;
1098
+ const { response: valRes, release } = await fetchWithSsrFGuard({
1099
+ url: valueUrl,
1100
+ fetchImpl: params.fetchFn ?? fetch,
1101
+ init: { headers: ensureUserAgentHeader({ Authorization: `Bearer ${params.accessToken}` }) },
1102
+ policy: params.ssrfPolicy,
1103
+ auditContext: "msteams.graph.hostedContent.value"
1104
+ });
1105
+ try {
1106
+ if (!valRes.ok) continue;
1107
+ const saved = await getMSTeamsRuntime().channel.media.saveResponseMedia(valRes, {
1108
+ sourceUrl: valueUrl,
1109
+ maxBytes: params.maxBytes,
1110
+ fallbackContentType: item.contentType ?? void 0,
1111
+ subdir: "inbound"
1112
+ });
1113
+ out.push({
1114
+ path: saved.path,
1115
+ contentType: saved.contentType,
1116
+ placeholder: inferPlaceholder({ contentType: saved.contentType })
1117
+ });
1118
+ } finally {
1119
+ await release();
1120
+ }
1121
+ } catch (err) {
1122
+ params.logger?.warn?.("msteams graph hostedContent value fetch failed", { error: err instanceof Error ? err.message : String(err) });
1123
+ continue;
1125
1124
  }
1126
- } catch (err) {
1127
- params.logger?.warn?.("msteams graph hostedContent value fetch failed", { error: err instanceof Error ? err.message : String(err) });
1128
1125
  continue;
1129
- }
1130
- else continue;
1126
+ } else continue;
1131
1127
  if (buffer.byteLength > params.maxBytes) continue;
1132
1128
  const mime = await getMSTeamsRuntime().media.detectMime({
1133
1129
  buffer,
@@ -1824,13 +1820,8 @@ function createTeamsReplyStreamController(params) {
1824
1820
  if (!stream || streamMode !== "progress") return;
1825
1821
  if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
1826
1822
  if (shouldStreamPreviewToolProgress) {
1827
- const normalized = normalizeProgressLineIdentity(line);
1828
- if (normalized) {
1829
- if (normalizeProgressLineIdentity(progressLines.at(-1)) !== normalized) {
1830
- const progressLine = typeof line === "object" && line !== void 0 ? line : normalized;
1831
- progressLines = [...progressLines, progressLine].slice(-resolveChannelProgressDraftMaxLines(params.msteamsConfig));
1832
- }
1833
- }
1823
+ const normalized = normalizeChannelProgressDraftLineIdentity(line);
1824
+ if (normalized) progressLines = mergeChannelProgressDraftLine(progressLines, typeof line === "object" && line !== void 0 ? line : normalized, { maxLines: resolveChannelProgressDraftMaxLines(params.msteamsConfig) });
1834
1825
  }
1835
1826
  await noteProgressWork();
1836
1827
  };
@@ -1961,9 +1952,6 @@ function createTeamsReplyStreamController(params) {
1961
1952
  }
1962
1953
  };
1963
1954
  }
1964
- function normalizeProgressLineIdentity(line) {
1965
- return (typeof line === "string" ? line : line?.text)?.replace(/\s+/g, " ").trim() ?? "";
1966
- }
1967
1955
  //#endregion
1968
1956
  //#region extensions/msteams/src/reply-dispatcher.ts
1969
1957
  function createMSTeamsReplyDispatcher(params) {
@@ -2214,6 +2202,7 @@ function createMSTeamsReplyDispatcher(params) {
2214
2202
  onItemEvent: async (payload) => {
2215
2203
  await streamController.pushProgressLine(buildChannelProgressDraftLineForEntry(msteamsCfg, {
2216
2204
  event: "item",
2205
+ itemId: payload.itemId,
2217
2206
  itemKind: payload.kind,
2218
2207
  title: payload.title,
2219
2208
  name: payload.name,
package/dist/test-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as msteamsPlugin } from "./channel-BApPsQGS.js";
1
+ import { t as msteamsPlugin } from "./channel-C5CVTygn.js";
2
2
  export { msteamsPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/msteams",
3
- "version": "2026.5.12",
3
+ "version": "2026.5.14-beta.2",
4
4
  "description": "OpenClaw Microsoft Teams channel plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,8 +9,8 @@
9
9
  "type": "module",
10
10
  "dependencies": {
11
11
  "@azure/identity": "4.13.1",
12
- "@microsoft/teams.api": "2.0.10",
13
- "@microsoft/teams.apps": "2.0.10",
12
+ "@microsoft/teams.api": "2.0.11",
13
+ "@microsoft/teams.apps": "2.0.11",
14
14
  "express": "5.2.1",
15
15
  "jsonwebtoken": "9.0.3",
16
16
  "jwks-rsa": "4.0.1",
@@ -22,7 +22,7 @@
22
22
  "openclaw": "workspace:*"
23
23
  },
24
24
  "peerDependencies": {
25
- "openclaw": ">=2026.5.12"
25
+ "openclaw": ">=2026.5.14-beta.2"
26
26
  },
27
27
  "peerDependenciesMeta": {
28
28
  "openclaw": {
@@ -58,10 +58,10 @@
58
58
  "minHostVersion": ">=2026.4.10"
59
59
  },
60
60
  "compat": {
61
- "pluginApi": ">=2026.5.12"
61
+ "pluginApi": ">=2026.5.14-beta.2"
62
62
  },
63
63
  "build": {
64
- "openclawVersion": "2026.5.12"
64
+ "openclawVersion": "2026.5.14-beta.2"
65
65
  },
66
66
  "release": {
67
67
  "publishToClawHub": true,