@openclaw/msteams 2026.5.7-beta.1 → 2026.5.9-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.
package/dist/api.js CHANGED
@@ -1,3 +1,3 @@
1
- import { t as msteamsPlugin } from "./channel-BOwKBAvY.js";
2
- import { i as msteamsSetupAdapter, n as openDelegatedOAuthUrl, r as createMSTeamsSetupWizardBase, t as msteamsSetupWizard } from "./setup-surface-BLkFQYIQ.js";
1
+ import { t as msteamsPlugin } from "./channel-VjDbklu8.js";
2
+ import { i as msteamsSetupAdapter, n as openDelegatedOAuthUrl, r as createMSTeamsSetupWizardBase, t as msteamsSetupWizard } from "./setup-surface-BeztPcxk.js";
3
3
  export { createMSTeamsSetupWizardBase, msteamsPlugin, msteamsSetupAdapter, msteamsSetupWizard, openDelegatedOAuthUrl };
@@ -1,13 +1,14 @@
1
- import { o as buildProbeChannelStatusSummary, r as PAIRING_APPROVED_MESSAGE, s as chunkTextForOutbound, t as DEFAULT_ACCOUNT_ID, u as createDefaultChannelRuntimeState } from "./runtime-api-DV1iVMn1.js";
2
- import { h as resolveMSTeamsCredentials } from "./graph-users-9uQJepqr.js";
3
- import { a as parseMSTeamsTeamChannelInput, c as resolveMSTeamsUserAllowlist, i as parseMSTeamsConversationId, n as normalizeMSTeamsMessagingTarget, r as normalizeMSTeamsUserInput, s as resolveMSTeamsChannelAllowlist, t as looksLikeMSTeamsTargetId } from "./resolve-allowlist-D41JSziq.js";
1
+ import { o as buildProbeChannelStatusSummary, r as PAIRING_APPROVED_MESSAGE, s as chunkTextForOutbound, t as DEFAULT_ACCOUNT_ID, u as createDefaultChannelRuntimeState } from "./runtime-api-D92XOrVf.js";
2
+ import { h as resolveMSTeamsCredentials } from "./graph-users-C9HaPg1v.js";
3
+ import { a as parseMSTeamsTeamChannelInput, c as resolveMSTeamsUserAllowlist, i as parseMSTeamsConversationId, n as normalizeMSTeamsMessagingTarget, r as normalizeMSTeamsUserInput, s as resolveMSTeamsChannelAllowlist, t as looksLikeMSTeamsTargetId } from "./resolve-allowlist-C23ohQKQ.js";
4
4
  import { t as MSTeamsChannelConfigSchema } from "./config-schema-DwOEthCC.js";
5
- import { r as resolveMSTeamsGroupToolPolicy } from "./policy-DTnU2GR7.js";
6
- import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-BLkFQYIQ.js";
5
+ import { r as resolveMSTeamsGroupToolPolicy } from "./policy-D32Kf1ED.js";
6
+ import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-BeztPcxk.js";
7
7
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
8
8
  import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
9
9
  import { createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
10
10
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin, stripChannelTargetPrefix, stripTargetKindPrefix } from "openclaw/plugin-sdk/channel-core";
11
+ import { createChannelMessageAdapterFromOutbound } from "openclaw/plugin-sdk/channel-message";
11
12
  import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
12
13
  import { createAllowlistProviderGroupPolicyWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, projectConfigWarningCollector } from "openclaw/plugin-sdk/channel-policy";
13
14
  import { createChannelDirectoryAdapter, createRuntimeDirectoryLiveAdapter, listDirectoryEntriesFromSources } from "openclaw/plugin-sdk/directory-runtime";
@@ -182,7 +183,7 @@ const collectMSTeamsSecurityWarnings = createAllowlistProviderGroupPolicyWarning
182
183
  resolveGroupPolicy: ({ cfg }) => cfg.channels?.msteams?.groupPolicy,
183
184
  collect: ({ groupPolicy }) => groupPolicy === "open" ? ["- MS Teams groups: groupPolicy=\"open\" allows any member to trigger (mention-gated). Set channels.msteams.groupPolicy=\"allowlist\" + channels.msteams.groupAllowFrom to restrict senders."] : []
184
185
  });
185
- const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-BC1ruIfN.js"), "msTeamsChannelRuntime");
186
+ const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-VrE9r2nS.js"), "msTeamsChannelRuntime");
186
187
  const resolveMSTeamsChannelConfig = (cfg) => ({
187
188
  allowFrom: cfg.channels?.msteams?.allowFrom,
188
189
  defaultTo: cfg.channels?.msteams?.defaultTo
@@ -376,6 +377,41 @@ function describeMSTeamsMessageTool({ cfg }) {
376
377
  } : null
377
378
  };
378
379
  }
380
+ const msteamsChannelOutbound = {
381
+ deliveryMode: "direct",
382
+ chunker: chunkTextForOutbound,
383
+ chunkerMode: "markdown",
384
+ textChunkLimit: 4e3,
385
+ pollMaxOptions: 12,
386
+ deliveryCapabilities: { durableFinal: {
387
+ text: true,
388
+ media: true,
389
+ messageSendingHooks: true
390
+ } },
391
+ ...createRuntimeOutboundDelegates({
392
+ getRuntime: loadMSTeamsChannelRuntime,
393
+ sendText: { resolve: (runtime) => runtime.msteamsOutbound.sendText },
394
+ sendMedia: { resolve: (runtime) => runtime.msteamsOutbound.sendMedia },
395
+ sendPoll: { resolve: (runtime) => runtime.msteamsOutbound.sendPoll }
396
+ })
397
+ };
398
+ const msteamsMessageAdapter = createChannelMessageAdapterFromOutbound({
399
+ id: "msteams",
400
+ outbound: msteamsChannelOutbound,
401
+ live: {
402
+ capabilities: {
403
+ draftPreview: true,
404
+ previewFinalization: true,
405
+ progressUpdates: true,
406
+ nativeStreaming: true
407
+ },
408
+ finalizer: { capabilities: {
409
+ finalEdit: true,
410
+ normalFallback: true,
411
+ previewReceipt: true
412
+ } }
413
+ }
414
+ });
379
415
  const msteamsPlugin = createChatChannelPlugin({
380
416
  base: {
381
417
  id: "msteams",
@@ -428,6 +464,7 @@ const msteamsPlugin = createChatChannelPlugin({
428
464
  hint: "<conversationId|user:ID|conversation:ID>"
429
465
  }
430
466
  },
467
+ message: msteamsMessageAdapter,
431
468
  directory: createChannelDirectoryAdapter({
432
469
  self: async ({ cfg }) => {
433
470
  const creds = resolveMSTeamsCredentials(cfg.channels?.msteams);
@@ -928,7 +965,7 @@ const msteamsPlugin = createChatChannelPlugin({
928
965
  })
929
966
  }),
930
967
  gateway: { startAccount: async (ctx) => {
931
- const { monitorMSTeamsProvider } = await import("./src-CP7V_TeZ.js");
968
+ const { monitorMSTeamsProvider } = await import("./src-CiKTGSje.js");
932
969
  const port = ctx.cfg.channels?.msteams?.webhook?.port ?? 3978;
933
970
  ctx.setStatus({
934
971
  accountId: ctx.accountId,
@@ -966,19 +1003,7 @@ const msteamsPlugin = createChatChannelPlugin({
966
1003
  hasRepliedRef
967
1004
  };
968
1005
  } },
969
- outbound: {
970
- deliveryMode: "direct",
971
- chunker: chunkTextForOutbound,
972
- chunkerMode: "markdown",
973
- textChunkLimit: 4e3,
974
- pollMaxOptions: 12,
975
- ...createRuntimeOutboundDelegates({
976
- getRuntime: loadMSTeamsChannelRuntime,
977
- sendText: { resolve: (runtime) => runtime.msteamsOutbound.sendText },
978
- sendMedia: { resolve: (runtime) => runtime.msteamsOutbound.sendMedia },
979
- sendPoll: { resolve: (runtime) => runtime.msteamsOutbound.sendPoll }
980
- })
981
- }
1006
+ outbound: msteamsChannelOutbound
982
1007
  });
983
1008
  //#endregion
984
1009
  export { msteamsPlugin as t };
@@ -1,2 +1,2 @@
1
- import { t as msteamsPlugin } from "./channel-BOwKBAvY.js";
1
+ import { t as msteamsPlugin } from "./channel-VjDbklu8.js";
2
2
  export { msteamsPlugin };
@@ -1,6 +1,6 @@
1
- import { s as chunkTextForOutbound } from "./runtime-api-DV1iVMn1.js";
2
- import { a as fetchGraphJson, c as normalizeQuery, d as postGraphJson, f as resolveGraphToken, i as fetchGraphAbsoluteUrl, l as patchGraphJson, n as deleteGraphRequest, o as listChannelsForTeam, r as escapeOData, s as listTeamsByName, t as searchGraphUsers, u as postGraphBetaJson } from "./graph-users-9uQJepqr.js";
3
- import { S as createMSTeamsConversationStoreFs, a as sendMessageMSTeams, b as createMSTeamsPollStoreFs, i as sendAdaptiveCardMSTeams, n as deleteMessageMSTeams, o as sendPollMSTeams, r as editMessageMSTeams, t as probeMSTeams } from "./probe-D_H8yFps.js";
1
+ import { s as chunkTextForOutbound } from "./runtime-api-D92XOrVf.js";
2
+ import { a as fetchGraphJson, c as normalizeQuery, d as postGraphJson, f as resolveGraphToken, i as fetchGraphAbsoluteUrl, l as patchGraphJson, n as deleteGraphRequest, o as listChannelsForTeam, r as escapeOData, s as listTeamsByName, t as searchGraphUsers, u as postGraphBetaJson } from "./graph-users-C9HaPg1v.js";
3
+ import { S as createMSTeamsConversationStoreFs, a as sendMessageMSTeams, b as createMSTeamsPollStoreFs, i as sendAdaptiveCardMSTeams, n as deleteMessageMSTeams, o as sendPollMSTeams, r as editMessageMSTeams, t as probeMSTeams } from "./probe-Bs955MJZ.js";
4
4
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
5
5
  import { createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
6
6
  import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-send-deps";
@@ -1,14 +1,15 @@
1
- import { L as getMSTeamsRuntime, g as fetchWithSsrFGuard$1 } from "./runtime-api-DV1iVMn1.js";
1
+ import { L as getMSTeamsRuntime, g as fetchWithSsrFGuard$1 } from "./runtime-api-D92XOrVf.js";
2
2
  import { n as refreshMSTeamsDelegatedTokens } from "./oauth.token-xxpoLWy5.js";
3
3
  import { createRequire } from "node:module";
4
- import { isRecord as isRecord$1, normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
4
+ import { isRecord as isRecord$2, normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
5
5
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
6
6
  import { Buffer } from "node:buffer";
7
7
  import { lookup } from "node:dns/promises";
8
8
  import { buildHostnameAllowlistPolicyFromSuffixAllowlist, isHttpsUrlAllowedByHostnameSuffixAllowlist, isPrivateIpAddress, normalizeHostnameSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
9
- import * as fs$1 from "node:fs";
10
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
- import path, { dirname } from "node:path";
9
+ import * as fs from "node:fs";
10
+ import { readFileSync } from "node:fs";
11
+ import path, { basename, dirname } from "node:path";
12
+ import { privateFileStoreSync } from "openclaw/plugin-sdk/security-runtime";
12
13
  import { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
13
14
  //#region extensions/msteams/src/attachments/shared.ts
14
15
  const IMAGE_EXT_RE = /\.(avif|bmp|gif|heic|heif|jpe?g|png|tiff?|webp)$/i;
@@ -122,7 +123,7 @@ function tryBuildGraphSharesUrlForSharedLink(url) {
122
123
  function readNestedString(value, keys) {
123
124
  let current = value;
124
125
  for (const key of keys) {
125
- if (!isRecord$1(current)) return;
126
+ if (!isRecord$2(current)) return;
126
127
  current = current[key];
127
128
  }
128
129
  return normalizeOptionalString(current);
@@ -153,7 +154,7 @@ function isLikelyImageAttachment(att) {
153
154
  const name = typeof att.name === "string" ? att.name : "";
154
155
  if (contentType.startsWith("image/")) return true;
155
156
  if (IMAGE_EXT_RE.test(name)) return true;
156
- if (contentType === "application/vnd.microsoft.teams.file.download.info" && isRecord$1(att.content)) {
157
+ if (contentType === "application/vnd.microsoft.teams.file.download.info" && isRecord$2(att.content)) {
157
158
  const fileType = typeof att.content.fileType === "string" ? att.content.fileType : "";
158
159
  if (fileType && IMAGE_EXT_RE.test(`x.${fileType}`)) return true;
159
160
  const fileName = typeof att.content.fileName === "string" ? att.content.fileName : "";
@@ -166,7 +167,7 @@ function isLikelyImageAttachment(att) {
166
167
  * Used when downloading all files, not just images.
167
168
  */
168
169
  function isDownloadableAttachment(att) {
169
- if ((normalizeContentType(att.contentType) ?? "") === "application/vnd.microsoft.teams.file.download.info" && isRecord$1(att.content) && typeof att.content.downloadUrl === "string") return true;
170
+ if ((normalizeContentType(att.contentType) ?? "") === "application/vnd.microsoft.teams.file.download.info" && isRecord$2(att.content) && typeof att.content.downloadUrl === "string") return true;
170
171
  if (typeof att.contentUrl === "string" && att.contentUrl.trim()) return true;
171
172
  return false;
172
173
  }
@@ -176,7 +177,7 @@ function isHtmlAttachment(att) {
176
177
  function extractHtmlFromAttachment(att) {
177
178
  if (!isHtmlAttachment(att)) return;
178
179
  if (typeof att.content === "string") return att.content;
179
- if (!isRecord$1(att.content)) return;
180
+ if (!isRecord$2(att.content)) return;
180
181
  return typeof att.content.text === "string" ? att.content.text : typeof att.content.body === "string" ? att.content.body : typeof att.content.content === "string" ? att.content.content : void 0;
181
182
  }
182
183
  function isLikelyBase64Payload(value) {
@@ -387,7 +388,7 @@ async function safeFetchWithPolicy(params) {
387
388
  }
388
389
  //#endregion
389
390
  //#region extensions/msteams/src/errors.ts
390
- function isRecord(value) {
391
+ function isRecord$1(value) {
391
392
  return typeof value === "object" && value !== null && !Array.isArray(value);
392
393
  }
393
394
  function formatUnknownError(err) {
@@ -405,7 +406,7 @@ function formatUnknownError(err) {
405
406
  }
406
407
  }
407
408
  function extractStatusCode(err) {
408
- if (!isRecord(err)) return null;
409
+ if (!isRecord$1(err)) return null;
409
410
  const direct = err.statusCode ?? err.status;
410
411
  if (typeof direct === "number" && Number.isFinite(direct)) return direct;
411
412
  if (typeof direct === "string") {
@@ -413,7 +414,7 @@ function extractStatusCode(err) {
413
414
  if (Number.isFinite(parsed)) return parsed;
414
415
  }
415
416
  const response = err.response;
416
- if (isRecord(response)) {
417
+ if (isRecord$1(response)) {
417
418
  const status = response.status;
418
419
  if (typeof status === "number" && Number.isFinite(status)) return status;
419
420
  if (typeof status === "string") {
@@ -424,20 +425,20 @@ function extractStatusCode(err) {
424
425
  return null;
425
426
  }
426
427
  function extractErrorCode(err) {
427
- if (!isRecord(err)) return null;
428
+ if (!isRecord$1(err)) return null;
428
429
  const direct = err.code;
429
430
  if (typeof direct === "string" && direct.trim()) return direct;
430
431
  const response = err.response;
431
- if (!isRecord(response)) return null;
432
+ if (!isRecord$1(response)) return null;
432
433
  const body = response.body;
433
- if (isRecord(body)) {
434
+ if (isRecord$1(body)) {
434
435
  const error = body.error;
435
- if (isRecord(error) && typeof error.code === "string" && error.code.trim()) return error.code;
436
+ if (isRecord$1(error) && typeof error.code === "string" && error.code.trim()) return error.code;
436
437
  }
437
438
  return null;
438
439
  }
439
440
  function extractRetryAfterMs(err) {
440
- if (!isRecord(err)) return null;
441
+ if (!isRecord$1(err)) return null;
441
442
  const direct = err.retryAfterMs ?? err.retry_after_ms;
442
443
  if (typeof direct === "number" && Number.isFinite(direct) && direct >= 0) return direct;
443
444
  const retryAfter = err.retryAfter ?? err.retry_after;
@@ -447,10 +448,10 @@ function extractRetryAfterMs(err) {
447
448
  if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1e3;
448
449
  }
449
450
  const response = err.response;
450
- if (!isRecord(response)) return null;
451
+ if (!isRecord$1(response)) return null;
451
452
  const headers = response.headers;
452
453
  if (!headers) return null;
453
- if (isRecord(headers)) {
454
+ if (isRecord$1(headers)) {
454
455
  const raw = headers["retry-after"] ?? headers["Retry-After"];
455
456
  if (typeof raw === "string") {
456
457
  const parsed = Number.parseFloat(raw);
@@ -512,6 +513,13 @@ function classifyMSTeamsSendError(err) {
512
513
  statusCode,
513
514
  errorCode
514
515
  };
516
+ if (statusCode == null) {
517
+ const networkCode = isRecord$1(err) && typeof err.code === "string" ? err.code : null;
518
+ if (networkCode === "ECONNREFUSED" || networkCode === "ENOTFOUND" || networkCode === "EHOSTUNREACH" || networkCode === "ETIMEDOUT" || networkCode === "ECONNRESET") return {
519
+ kind: "network",
520
+ errorCode: networkCode
521
+ };
522
+ }
515
523
  return {
516
524
  kind: "unknown",
517
525
  statusCode: statusCode ?? void 0,
@@ -536,6 +544,7 @@ function formatMSTeamsSendErrorHint(classification) {
536
544
  if (classification.errorCode === "ContentStreamNotAllowed") return "Teams expired the content stream; stop streaming earlier and fall back to normal message delivery";
537
545
  if (classification.kind === "throttled") return "Teams throttled the bot; backing off may help";
538
546
  if (classification.kind === "transient") return "transient Teams/Bot Framework error; retry may succeed";
547
+ if (classification.kind === "network") return "transport-level failure sending reply to Teams Bot Connector (smba.trafficmanager.net) — check egress firewall rules allow outbound HTTPS to smba.trafficmanager.net";
539
548
  }
540
549
  //#endregion
541
550
  //#region extensions/msteams/src/user-agent.ts
@@ -617,7 +626,7 @@ function createFederatedApp(creds, sdk) {
617
626
  if (!creds.certificatePath) throw new Error("Federated credentials require either a certificate path or managed identity.");
618
627
  let privateKey;
619
628
  try {
620
- privateKey = fs$1.readFileSync(creds.certificatePath, "utf-8");
629
+ privateKey = fs.readFileSync(creds.certificatePath, "utf-8");
621
630
  } catch (err) {
622
631
  const msg = err instanceof Error ? err.message : String(err);
623
632
  throw new Error(`Failed to read certificate file at '${creds.certificatePath}': ${msg}`, { cause: err });
@@ -1045,11 +1054,23 @@ async function createBotFrameworkJwtValidator(creds) {
1045
1054
  if (!isJwtPayloadObject(verifiedPayload)) return false;
1046
1055
  if (getAudienceClaims(verifiedPayload).includes(BOT_FRAMEWORK_GLOBAL_AUDIENCE) && !hasExpectedBotIdentity(verifiedPayload, creds.appId)) return false;
1047
1056
  return true;
1048
- } catch {
1057
+ } catch (err) {
1058
+ if (isJwksNetworkError(err)) throw err;
1049
1059
  return false;
1050
1060
  }
1051
1061
  } };
1052
1062
  }
1063
+ /**
1064
+ * Return true when the error originated from a network-level failure fetching
1065
+ * the JWKS endpoint (DNS resolution, connection refused, TLS handshake, etc.)
1066
+ * rather than from token verification logic.
1067
+ */
1068
+ function isJwksNetworkError(err) {
1069
+ if (!(err instanceof Error)) return false;
1070
+ const code = err.code;
1071
+ if (code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "EHOSTUNREACH" || code === "ETIMEDOUT" || code === "ECONNRESET") return true;
1072
+ return /jwks|key fetch|getSigningKey/i.test(err.message) && /network|fetch|connect/i.test(err.message);
1073
+ }
1053
1074
  //#endregion
1054
1075
  //#region extensions/msteams/src/token-response.ts
1055
1076
  function readAccessToken(value) {
@@ -1135,8 +1156,7 @@ function loadDelegatedTokens() {
1135
1156
  }
1136
1157
  function saveDelegatedTokens(tokens) {
1137
1158
  const tokenPath = resolveDelegatedTokenPath();
1138
- mkdirSync(dirname(tokenPath), { recursive: true });
1139
- writeFileSync(tokenPath, JSON.stringify(tokens, null, 2), "utf8");
1159
+ privateFileStoreSync(dirname(tokenPath)).writeJson(basename(tokenPath), tokens);
1140
1160
  }
1141
1161
  async function resolveDelegatedAccessToken(params) {
1142
1162
  const tokens = loadDelegatedTokens();
@@ -1351,4 +1371,4 @@ async function searchGraphUsers(params) {
1351
1371
  })).value ?? [];
1352
1372
  }
1353
1373
  //#endregion
1354
- export { ATTACHMENT_TAG_RE as A, isLikelyImageAttachment as B, loadMSTeamsSdkWithAuth as C, formatMSTeamsSendErrorHint as D, classifyMSTeamsSendError as E, estimateBase64DecodedBytes as F, resolveAttachmentFetchPolicy as G, isUrlAllowed as H, extractHtmlFromAttachment as I, safeFetchWithPolicy as J, resolveMediaSsrfPolicy as K, extractInlineImageCandidates as L, IMG_SRC_RE as M, applyAuthorizationHeaderForUrl as N, formatUnknownError as O, encodeGraphShareId as P, inferPlaceholder as R, createMSTeamsTokenProvider as S, ensureUserAgentHeader as T, normalizeContentType as U, isRecord$1 as V, readNestedString as W, tryBuildGraphSharesUrlForSharedLink as X, safeHostForUrl as Y, resolveMSTeamsStorePath as _, fetchGraphJson as a, createBotFrameworkJwtValidator as b, normalizeQuery as c, postGraphJson as d, resolveGraphToken as f, saveDelegatedTokens as g, resolveMSTeamsCredentials as h, fetchGraphAbsoluteUrl as i, GRAPH_ROOT as j, isRevokedProxyError as k, patchGraphJson as l, loadDelegatedTokens as m, deleteGraphRequest as n, listChannelsForTeam as o, hasConfiguredMSTeamsCredentials as p, resolveRequestUrl as q, escapeOData as r, listTeamsByName as s, searchGraphUsers as t, postGraphBetaJson as u, normalizeSecretInputString as v, buildUserAgent as w, createMSTeamsAdapter as x, readAccessToken as y, isDownloadableAttachment as z };
1374
+ export { ATTACHMENT_TAG_RE as A, isLikelyImageAttachment as B, loadMSTeamsSdkWithAuth as C, formatMSTeamsSendErrorHint as D, classifyMSTeamsSendError as E, estimateBase64DecodedBytes as F, resolveAttachmentFetchPolicy as G, isUrlAllowed as H, extractHtmlFromAttachment as I, safeFetchWithPolicy as J, resolveMediaSsrfPolicy as K, extractInlineImageCandidates as L, IMG_SRC_RE as M, applyAuthorizationHeaderForUrl as N, formatUnknownError as O, encodeGraphShareId as P, inferPlaceholder as R, createMSTeamsTokenProvider as S, ensureUserAgentHeader as T, normalizeContentType as U, isRecord$2 as V, readNestedString as W, tryBuildGraphSharesUrlForSharedLink as X, safeHostForUrl as Y, resolveMSTeamsStorePath as _, fetchGraphJson as a, createBotFrameworkJwtValidator as b, normalizeQuery as c, postGraphJson as d, resolveGraphToken as f, saveDelegatedTokens as g, resolveMSTeamsCredentials as h, fetchGraphAbsoluteUrl as i, GRAPH_ROOT as j, isRevokedProxyError as k, patchGraphJson as l, loadDelegatedTokens as m, deleteGraphRequest as n, listChannelsForTeam as o, hasConfiguredMSTeamsCredentials as p, resolveRequestUrl as q, escapeOData as r, listTeamsByName as s, searchGraphUsers as t, postGraphBetaJson as u, normalizeSecretInputString as v, buildUserAgent as w, createMSTeamsAdapter as x, readAccessToken as y, isDownloadableAttachment as z };
@@ -1,4 +1,4 @@
1
- import { C as normalizeChannelSlug, D as resolveChannelEntryMatchWithFallback, E as resolveAllowlistMatchSimple, M as resolveNestedAllowlistDecision, P as resolveToolsBySender, i as buildChannelKeyCandidates, p as evaluateSenderGroupAccessForPolicy, v as isDangerousNameMatchingEnabled } from "./runtime-api-DV1iVMn1.js";
1
+ import { C as normalizeChannelSlug, D as resolveChannelEntryMatchWithFallback, E as resolveAllowlistMatchSimple, M as resolveNestedAllowlistDecision, P as resolveToolsBySender, i as buildChannelKeyCandidates, p as evaluateSenderGroupAccessForPolicy, v as isDangerousNameMatchingEnabled } from "./runtime-api-D92XOrVf.js";
2
2
  //#region extensions/msteams/src/policy.ts
3
3
  function resolveMSTeamsRouteConfig(params) {
4
4
  const teamId = params.teamId?.trim();
@@ -1,11 +1,14 @@
1
- import { L as getMSTeamsRuntime, O as resolveChannelMediaMaxBytes, _ as getFileExtension, b as loadOutboundMediaFromUrl, d as detectMime, h as extractOriginalFilename, m as extensionForMime, w as normalizeStringEntries } from "./runtime-api-DV1iVMn1.js";
2
- import { C as loadMSTeamsSdkWithAuth, D as formatMSTeamsSendErrorHint, E as classifyMSTeamsSendError, O as formatUnknownError, S as createMSTeamsTokenProvider, _ as resolveMSTeamsStorePath, h as resolveMSTeamsCredentials, k as isRevokedProxyError, m as loadDelegatedTokens, w as buildUserAgent, x as createMSTeamsAdapter, y as readAccessToken } from "./graph-users-9uQJepqr.js";
3
- import { convertMarkdownTables, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, sleep } from "openclaw/plugin-sdk/text-runtime";
1
+ import { L as getMSTeamsRuntime, O as resolveChannelMediaMaxBytes, _ as getFileExtension, b as loadOutboundMediaFromUrl, d as detectMime, h as extractOriginalFilename, m as extensionForMime, w as normalizeStringEntries } from "./runtime-api-D92XOrVf.js";
2
+ import { C as loadMSTeamsSdkWithAuth, D as formatMSTeamsSendErrorHint, E as classifyMSTeamsSendError, O as formatUnknownError, S as createMSTeamsTokenProvider, _ as resolveMSTeamsStorePath, h as resolveMSTeamsCredentials, k as isRevokedProxyError, m as loadDelegatedTokens, w as buildUserAgent, x as createMSTeamsAdapter, y as readAccessToken } from "./graph-users-C9HaPg1v.js";
3
+ import { a as resolveMSTeamsRouteConfig, i as resolveMSTeamsReplyPolicy } from "./policy-D32Kf1ED.js";
4
+ import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-message";
5
+ import { convertMarkdownTables, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, sleep } from "openclaw/plugin-sdk/text-runtime";
4
6
  import { withFileLock } from "openclaw/plugin-sdk/file-lock";
5
7
  import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
6
8
  import { lookup } from "node:dns/promises";
7
- import fs from "node:fs";
9
+ import { isPrivateIpAddress } from "openclaw/plugin-sdk/ssrf-policy";
8
10
  import path from "node:path";
11
+ import { pathExists } from "openclaw/plugin-sdk/security-runtime";
9
12
  import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
10
13
  import crypto from "node:crypto";
11
14
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
@@ -72,11 +75,7 @@ async function writeJsonFile(filePath, value) {
72
75
  await writeJsonFileAtomically(filePath, value);
73
76
  }
74
77
  async function ensureJsonFile(filePath, fallback) {
75
- try {
76
- await fs.promises.access(filePath);
77
- } catch {
78
- await writeJsonFile(filePath, fallback);
79
- }
78
+ if (!await pathExists(filePath)) await writeJsonFile(filePath, fallback);
80
79
  }
81
80
  async function withFileLock$1(filePath, fallback, fn) {
82
81
  await ensureJsonFile(filePath, fallback);
@@ -182,14 +181,6 @@ function createMSTeamsConversationStoreFs(params) {
182
181
  const STORE_FILENAME$1 = "msteams-polls.json";
183
182
  const MAX_POLLS = 1e3;
184
183
  const POLL_TTL_MS = 720 * 60 * 60 * 1e3;
185
- function isRecord(value) {
186
- return typeof value === "object" && value !== null && !Array.isArray(value);
187
- }
188
- function normalizeOptionalString$1(value) {
189
- if (typeof value !== "string") return;
190
- const trimmed = value.trim();
191
- return trimmed ? trimmed : void 0;
192
- }
193
184
  function normalizeChoiceValue(value) {
194
185
  if (typeof value === "string") {
195
186
  const trimmed = value.trim();
@@ -214,7 +205,7 @@ function readNestedValue(value, keys) {
214
205
  return current;
215
206
  }
216
207
  function readNestedString(value, keys) {
217
- return normalizeOptionalString$1(readNestedValue(value, keys));
208
+ return normalizeOptionalString(readNestedValue(value, keys));
218
209
  }
219
210
  function extractMSTeamsPollVote(activity) {
220
211
  const value = activity?.value;
@@ -395,9 +386,6 @@ function createMSTeamsPollStoreFs(params) {
395
386
  * - Building FileInfoCard attachments (to confirm upload completion)
396
387
  * - Parsing fileConsent/invoke activities
397
388
  */
398
- function normalizeLowercaseStringOrEmpty$1(value) {
399
- return typeof value === "string" ? value.trim().toLowerCase() : "";
400
- }
401
389
  /**
402
390
  * Allowlist of domains that are valid targets for file consent uploads.
403
391
  * These are the Microsoft/SharePoint domains that Teams legitimately provides
@@ -418,31 +406,10 @@ const CONSENT_UPLOAD_HOST_ALLOWLIST = [
418
406
  "graph.microsoft.cn"
419
407
  ];
420
408
  /**
421
- * Returns true if the given IPv4 or IPv6 address is in a private, loopback,
422
- * or link-local range that must never be reached via consent uploads.
409
+ * Returns true if the given IPv4 or IPv6 address is private, internal, or
410
+ * special-use and must never be reached via consent uploads.
423
411
  */
424
- function isPrivateOrReservedIP(ip) {
425
- const ipv4MappedMatch = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(ip);
426
- if (ipv4MappedMatch) return isPrivateOrReservedIP(ipv4MappedMatch[1]);
427
- const v4Parts = ip.split(".");
428
- if (v4Parts.length === 4) {
429
- const octets = v4Parts.map(Number);
430
- if (octets.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return false;
431
- const [a, b] = octets;
432
- if (a === 10) return true;
433
- if (a === 172 && b >= 16 && b <= 31) return true;
434
- if (a === 192 && b === 168) return true;
435
- if (a === 127) return true;
436
- if (a === 169 && b === 254) return true;
437
- if (a === 0) return true;
438
- }
439
- const normalized = normalizeLowercaseStringOrEmpty$1(ip);
440
- if (normalized === "::1") return true;
441
- if (normalized.startsWith("fe80:") || normalized.startsWith("fe80")) return true;
442
- if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true;
443
- if (normalized === "::") return true;
444
- return false;
445
- }
412
+ const isPrivateOrReservedIP = isPrivateIpAddress;
446
413
  /**
447
414
  * Validate that a consent upload URL is safe to PUT to.
448
415
  * Checks:
@@ -460,7 +427,7 @@ async function validateConsentUploadUrl(url, opts) {
460
427
  throw new Error("Consent upload URL is not a valid URL");
461
428
  }
462
429
  if (parsed.protocol !== "https:") throw new Error(`Consent upload URL must use HTTPS, got ${parsed.protocol}`);
463
- const hostname = normalizeLowercaseStringOrEmpty$1(parsed.hostname);
430
+ const hostname = normalizeLowercaseStringOrEmpty(parsed.hostname);
464
431
  if (!(opts?.allowlist ?? CONSENT_UPLOAD_HOST_ALLOWLIST).some((entry) => hostname === entry || hostname.endsWith(`.${entry}`))) throw new Error(`Consent upload URL hostname "${hostname}" is not in the allowed domains`);
465
432
  const resolveFn = opts?.resolveFn ?? ((name) => lookup(name, { all: true }));
466
433
  let resolved;
@@ -1554,7 +1521,7 @@ async function sendMSTeamsMessages(params) {
1554
1521
  const resolvedThreadId = params.conversationRef.threadId ?? params.conversationRef.activityId;
1555
1522
  if (params.replyStyle === "thread") {
1556
1523
  const ctx = params.context;
1557
- if (!ctx) throw new Error("Missing context for replyStyle=thread");
1524
+ if (!ctx) return await sendProactively(messages, 0, resolvedThreadId);
1558
1525
  const messageIds = [];
1559
1526
  for (const [idx, message] of messages.entries()) {
1560
1527
  const result = await withRevokedProxyFallback({
@@ -1579,6 +1546,23 @@ async function sendMSTeamsMessages(params) {
1579
1546
  }
1580
1547
  //#endregion
1581
1548
  //#region extensions/msteams/src/send-context.ts
1549
+ function resolveMSTeamsProactiveReplyStyle(params) {
1550
+ const threadRootId = params.ref.threadId ?? params.ref.activityId;
1551
+ if (params.conversationType !== "channel" || !threadRootId) return "top-level";
1552
+ const routeConfig = resolveMSTeamsRouteConfig({
1553
+ cfg: params.cfg,
1554
+ teamId: params.ref.teamId,
1555
+ conversationId: params.conversationId,
1556
+ allowNameMatching: false
1557
+ });
1558
+ const { replyStyle } = resolveMSTeamsReplyPolicy({
1559
+ isDirectMessage: false,
1560
+ globalConfig: params.cfg,
1561
+ teamConfig: routeConfig.teamConfig,
1562
+ channelConfig: routeConfig.channelConfig
1563
+ });
1564
+ return replyStyle;
1565
+ }
1582
1566
  /**
1583
1567
  * Parse the target value into a conversation reference lookup key.
1584
1568
  * Supported formats:
@@ -1646,6 +1630,12 @@ async function resolveMSTeamsSendContext(params) {
1646
1630
  if (storedConversationType === "personal") conversationType = "personal";
1647
1631
  else if (storedConversationType === "channel") conversationType = "channel";
1648
1632
  else conversationType = "groupChat";
1633
+ const replyStyle = resolveMSTeamsProactiveReplyStyle({
1634
+ cfg: msteamsCfg,
1635
+ conversationId,
1636
+ ref,
1637
+ conversationType
1638
+ });
1649
1639
  const sharePointSiteId = msteamsCfg.sharePointSiteId;
1650
1640
  const mediaMaxBytes = resolveChannelMediaMaxBytes({
1651
1641
  cfg: params.cfg,
@@ -1678,6 +1668,7 @@ async function resolveMSTeamsSendContext(params) {
1678
1668
  adapter,
1679
1669
  log,
1680
1670
  conversationType,
1671
+ replyStyle,
1681
1672
  tokenProvider,
1682
1673
  sharePointSiteId,
1683
1674
  mediaMaxBytes,
@@ -1693,6 +1684,29 @@ const FILE_CONSENT_THRESHOLD_BYTES = 4 * 1024 * 1024;
1693
1684
  * Higher than the default because OneDrive upload handles large files well.
1694
1685
  */
1695
1686
  const MSTEAMS_MAX_MEDIA_BYTES = 100 * 1024 * 1024;
1687
+ function createMSTeamsSendReceipt(params) {
1688
+ return createMessageReceiptFromOutboundResults({
1689
+ kind: params.kind,
1690
+ results: params.platformMessageIds.map((messageId) => ({
1691
+ channel: "msteams",
1692
+ messageId,
1693
+ conversationId: params.conversationId
1694
+ }))
1695
+ });
1696
+ }
1697
+ function createMSTeamsSendResult(params) {
1698
+ const platformMessageIds = (params.platformMessageIds?.length ? [...params.platformMessageIds] : [params.messageId]).map((messageId) => messageId.trim()).filter((messageId) => messageId && messageId !== "unknown");
1699
+ return {
1700
+ messageId: params.messageId,
1701
+ conversationId: params.conversationId,
1702
+ receipt: createMSTeamsSendReceipt({
1703
+ conversationId: params.conversationId,
1704
+ platformMessageIds,
1705
+ kind: params.kind
1706
+ }),
1707
+ ...params.pendingUploadId ? { pendingUploadId: params.pendingUploadId } : {}
1708
+ };
1709
+ }
1696
1710
  /**
1697
1711
  * Send a message to a Teams conversation or user.
1698
1712
  *
@@ -1774,11 +1788,12 @@ async function sendMessageMSTeams(params) {
1774
1788
  messageId,
1775
1789
  uploadId
1776
1790
  });
1777
- return {
1791
+ return createMSTeamsSendResult({
1778
1792
  messageId,
1779
1793
  conversationId,
1794
+ kind: "card",
1780
1795
  pendingUploadId: uploadId
1781
- };
1796
+ });
1782
1797
  }
1783
1798
  if (conversationType === "personal") {
1784
1799
  const base64 = media.buffer.toString("base64");
@@ -1833,10 +1848,11 @@ async function sendMessageMSTeams(params) {
1833
1848
  messageId,
1834
1849
  fileName: driveItem.name
1835
1850
  });
1836
- return {
1851
+ return createMSTeamsSendResult({
1837
1852
  messageId,
1838
- conversationId
1839
- };
1853
+ conversationId,
1854
+ kind: "media"
1855
+ });
1840
1856
  }
1841
1857
  log.debug?.("uploading to OneDrive (no SharePoint site configured)", {
1842
1858
  fileName,
@@ -1867,10 +1883,11 @@ async function sendMessageMSTeams(params) {
1867
1883
  messageId,
1868
1884
  shareUrl: uploaded.shareUrl
1869
1885
  });
1870
- return {
1886
+ return createMSTeamsSendResult({
1871
1887
  messageId,
1872
- conversationId
1873
- };
1888
+ conversationId,
1889
+ kind: "media"
1890
+ });
1874
1891
  } catch (err) {
1875
1892
  const classification = classifyMSTeamsSendError(err);
1876
1893
  const hint = formatMSTeamsSendErrorHint(classification);
@@ -1884,11 +1901,11 @@ async function sendMessageMSTeams(params) {
1884
1901
  * Send a text message with optional base64 media URL.
1885
1902
  */
1886
1903
  async function sendTextWithMedia(ctx, text, mediaUrl) {
1887
- const { adapter, appId, conversationId, ref, log, tokenProvider, sharePointSiteId, mediaMaxBytes } = ctx;
1888
- let messageIds;
1904
+ const { adapter, appId, conversationId, ref, log, tokenProvider, sharePointSiteId, mediaMaxBytes, replyStyle } = ctx;
1905
+ let platformMessageIds;
1889
1906
  try {
1890
- messageIds = await sendMSTeamsMessages({
1891
- replyStyle: "top-level",
1907
+ platformMessageIds = await sendMSTeamsMessages({
1908
+ replyStyle,
1892
1909
  adapter,
1893
1910
  appId,
1894
1911
  conversationRef: ref,
@@ -1913,14 +1930,19 @@ async function sendTextWithMedia(ctx, text, mediaUrl) {
1913
1930
  const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
1914
1931
  throw new Error(`msteams send failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`, { cause: err });
1915
1932
  }
1916
- const messageId = messageIds[0] ?? "unknown";
1933
+ const messageId = platformMessageIds[0] ?? "unknown";
1917
1934
  log.info("sent proactive message", {
1918
1935
  conversationId,
1919
1936
  messageId
1920
1937
  });
1921
1938
  return {
1922
1939
  messageId,
1923
- conversationId
1940
+ conversationId,
1941
+ receipt: createMSTeamsSendReceipt({
1942
+ conversationId,
1943
+ platformMessageIds,
1944
+ kind: mediaUrl ? "media" : "text"
1945
+ })
1924
1946
  };
1925
1947
  }
1926
1948
  async function sendProactiveActivityRaw({ adapter, appId, ref, activity }) {
@@ -1,4 +1,4 @@
1
- import { c as normalizeQuery, f as resolveGraphToken, o as listChannelsForTeam, s as listTeamsByName, t as searchGraphUsers } from "./graph-users-9uQJepqr.js";
1
+ import { c as normalizeQuery, f as resolveGraphToken, o as listChannelsForTeam, s as listTeamsByName, t as searchGraphUsers } from "./graph-users-C9HaPg1v.js";
2
2
  import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/allow-from";
3
3
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
4
4
  //#region extensions/msteams/src/resolve-allowlist.ts
@@ -1,9 +1,9 @@
1
1
  import { mergeAllowlist, resolveAllowlistMatchSimple, summarizeMapping } from "openclaw/plugin-sdk/allow-from";
2
+ import { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-message";
2
3
  import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
3
4
  import { evaluateSenderGroupAccessForPolicy, readStoreAllowFromForDmPolicy, resolveDmGroupAccessWithLists, resolveEffectiveAllowFromLists, resolveSenderScopedGroupPolicy, resolveToolsBySender } from "openclaw/plugin-sdk/channel-policy";
4
5
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
5
6
  import { logTypingFailure } from "openclaw/plugin-sdk/channel-logging";
6
- import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
7
7
  import { PAIRING_APPROVED_MESSAGE, buildProbeChannelStatusSummary, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/channel-status";
8
8
  import { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback, resolveNestedAllowlistDecision } from "openclaw/plugin-sdk/channel-targets";
9
9
  import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
@@ -25,4 +25,4 @@ const { setRuntime: setMSTeamsRuntime, getRuntime: getMSTeamsRuntime, tryGetRunt
25
25
  errorMessage: "MSTeams runtime not initialized"
26
26
  });
27
27
  //#endregion
28
- export { resolveDmGroupAccessWithLists as A, normalizeChannelSlug as C, resolveChannelEntryMatchWithFallback as D, resolveAllowlistMatchSimple as E, summarizeMapping as F, withFileLock$1 as I, getMSTeamsRuntime as L, resolveNestedAllowlistDecision as M, resolveSenderScopedGroupPolicy as N, resolveChannelMediaMaxBytes as O, resolveToolsBySender as P, getOptionalMSTeamsRuntime as R, mergeAllowlist as S, readStoreAllowFromForDmPolicy as T, getFileExtension as _, buildMediaPayload as a, loadOutboundMediaFromUrl as b, createChannelPairingController as c, detectMime as d, dispatchReplyFromConfigWithSettledDispatcher$1 as f, fetchWithSsrFGuard$1 as g, extractOriginalFilename as h, buildChannelKeyCandidates as i, resolveEffectiveAllowFromLists as j, resolveDefaultGroupPolicy as k, createChannelReplyPipeline as l, extensionForMime as m, DEFAULT_WEBHOOK_MAX_BODY_BYTES as n, buildProbeChannelStatusSummary as o, evaluateSenderGroupAccessForPolicy as p, PAIRING_APPROVED_MESSAGE as r, chunkTextForOutbound as s, DEFAULT_ACCOUNT_ID as t, createDefaultChannelRuntimeState as u, isDangerousNameMatchingEnabled as v, normalizeStringEntries as w, logTypingFailure as x, keepHttpServerTaskAlive as y, setMSTeamsRuntime as z };
28
+ export { resolveDmGroupAccessWithLists as A, normalizeChannelSlug as C, resolveChannelEntryMatchWithFallback as D, resolveAllowlistMatchSimple as E, summarizeMapping as F, withFileLock$1 as I, getMSTeamsRuntime as L, resolveNestedAllowlistDecision as M, resolveSenderScopedGroupPolicy as N, resolveChannelMediaMaxBytes as O, resolveToolsBySender as P, getOptionalMSTeamsRuntime as R, mergeAllowlist as S, readStoreAllowFromForDmPolicy as T, getFileExtension as _, buildMediaPayload as a, loadOutboundMediaFromUrl as b, createChannelMessageReplyPipeline as c, detectMime as d, dispatchReplyFromConfigWithSettledDispatcher$1 as f, fetchWithSsrFGuard$1 as g, extractOriginalFilename as h, buildChannelKeyCandidates as i, resolveEffectiveAllowFromLists as j, resolveDefaultGroupPolicy as k, createChannelPairingController as l, extensionForMime as m, DEFAULT_WEBHOOK_MAX_BODY_BYTES as n, buildProbeChannelStatusSummary as o, evaluateSenderGroupAccessForPolicy as p, PAIRING_APPROVED_MESSAGE as r, chunkTextForOutbound as s, DEFAULT_ACCOUNT_ID as t, createDefaultChannelRuntimeState as u, isDangerousNameMatchingEnabled as v, normalizeStringEntries as w, logTypingFailure as x, keepHttpServerTaskAlive as y, setMSTeamsRuntime as z };
@@ -1,2 +1,2 @@
1
- import { A as resolveDmGroupAccessWithLists, C as normalizeChannelSlug, D as resolveChannelEntryMatchWithFallback, E as resolveAllowlistMatchSimple, F as summarizeMapping, I as withFileLock, M as resolveNestedAllowlistDecision, N as resolveSenderScopedGroupPolicy, O as resolveChannelMediaMaxBytes, P as resolveToolsBySender, S as mergeAllowlist, T as readStoreAllowFromForDmPolicy, _ as getFileExtension, a as buildMediaPayload, b as loadOutboundMediaFromUrl, c as createChannelPairingController, d as detectMime, f as dispatchReplyFromConfigWithSettledDispatcher, g as fetchWithSsrFGuard, h as extractOriginalFilename, i as buildChannelKeyCandidates, j as resolveEffectiveAllowFromLists, k as resolveDefaultGroupPolicy, l as createChannelReplyPipeline, m as extensionForMime, n as DEFAULT_WEBHOOK_MAX_BODY_BYTES, o as buildProbeChannelStatusSummary, p as evaluateSenderGroupAccessForPolicy, r as PAIRING_APPROVED_MESSAGE, s as chunkTextForOutbound, t as DEFAULT_ACCOUNT_ID, u as createDefaultChannelRuntimeState, v as isDangerousNameMatchingEnabled, w as normalizeStringEntries, x as logTypingFailure, y as keepHttpServerTaskAlive, z as setMSTeamsRuntime } from "./runtime-api-DV1iVMn1.js";
2
- export { DEFAULT_ACCOUNT_ID, DEFAULT_WEBHOOK_MAX_BODY_BYTES, PAIRING_APPROVED_MESSAGE, buildChannelKeyCandidates, buildMediaPayload, buildProbeChannelStatusSummary, chunkTextForOutbound, createChannelPairingController, createChannelReplyPipeline, createDefaultChannelRuntimeState, detectMime, dispatchReplyFromConfigWithSettledDispatcher, evaluateSenderGroupAccessForPolicy, extensionForMime, extractOriginalFilename, fetchWithSsrFGuard, getFileExtension, isDangerousNameMatchingEnabled, keepHttpServerTaskAlive, loadOutboundMediaFromUrl, logTypingFailure, mergeAllowlist, normalizeChannelSlug, normalizeStringEntries, readStoreAllowFromForDmPolicy, resolveAllowlistMatchSimple, resolveChannelEntryMatchWithFallback, resolveChannelMediaMaxBytes, resolveDefaultGroupPolicy, resolveDmGroupAccessWithLists, resolveEffectiveAllowFromLists, resolveNestedAllowlistDecision, resolveSenderScopedGroupPolicy, resolveToolsBySender, setMSTeamsRuntime, summarizeMapping, withFileLock };
1
+ import { A as resolveDmGroupAccessWithLists, C as normalizeChannelSlug, D as resolveChannelEntryMatchWithFallback, E as resolveAllowlistMatchSimple, F as summarizeMapping, I as withFileLock, M as resolveNestedAllowlistDecision, N as resolveSenderScopedGroupPolicy, O as resolveChannelMediaMaxBytes, P as resolveToolsBySender, S as mergeAllowlist, T as readStoreAllowFromForDmPolicy, _ as getFileExtension, a as buildMediaPayload, b as loadOutboundMediaFromUrl, c as createChannelMessageReplyPipeline, d as detectMime, f as dispatchReplyFromConfigWithSettledDispatcher, g as fetchWithSsrFGuard, h as extractOriginalFilename, i as buildChannelKeyCandidates, j as resolveEffectiveAllowFromLists, k as resolveDefaultGroupPolicy, l as createChannelPairingController, m as extensionForMime, n as DEFAULT_WEBHOOK_MAX_BODY_BYTES, o as buildProbeChannelStatusSummary, p as evaluateSenderGroupAccessForPolicy, r as PAIRING_APPROVED_MESSAGE, s as chunkTextForOutbound, t as DEFAULT_ACCOUNT_ID, u as createDefaultChannelRuntimeState, v as isDangerousNameMatchingEnabled, w as normalizeStringEntries, x as logTypingFailure, y as keepHttpServerTaskAlive, z as setMSTeamsRuntime } from "./runtime-api-D92XOrVf.js";
2
+ export { DEFAULT_ACCOUNT_ID, DEFAULT_WEBHOOK_MAX_BODY_BYTES, PAIRING_APPROVED_MESSAGE, buildChannelKeyCandidates, buildMediaPayload, buildProbeChannelStatusSummary, chunkTextForOutbound, createChannelMessageReplyPipeline, createChannelPairingController, createDefaultChannelRuntimeState, detectMime, dispatchReplyFromConfigWithSettledDispatcher, evaluateSenderGroupAccessForPolicy, extensionForMime, extractOriginalFilename, fetchWithSsrFGuard, getFileExtension, isDangerousNameMatchingEnabled, keepHttpServerTaskAlive, loadOutboundMediaFromUrl, logTypingFailure, mergeAllowlist, normalizeChannelSlug, normalizeStringEntries, readStoreAllowFromForDmPolicy, resolveAllowlistMatchSimple, resolveChannelEntryMatchWithFallback, resolveChannelMediaMaxBytes, resolveDefaultGroupPolicy, resolveDmGroupAccessWithLists, resolveEffectiveAllowFromLists, resolveNestedAllowlistDecision, resolveSenderScopedGroupPolicy, resolveToolsBySender, setMSTeamsRuntime, summarizeMapping, withFileLock };
@@ -1,6 +1,6 @@
1
- import { h as resolveMSTeamsCredentials } from "./graph-users-9uQJepqr.js";
1
+ import { h as resolveMSTeamsCredentials } from "./graph-users-C9HaPg1v.js";
2
2
  import { t as MSTeamsChannelConfigSchema } from "./config-schema-DwOEthCC.js";
3
- import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-BLkFQYIQ.js";
3
+ import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-BeztPcxk.js";
4
4
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
5
5
  import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
6
6
  import { createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
@@ -1,5 +1,5 @@
1
- import { O as formatUnknownError, g as saveDelegatedTokens, h as resolveMSTeamsCredentials, p as hasConfiguredMSTeamsCredentials, v as normalizeSecretInputString } from "./graph-users-9uQJepqr.js";
2
- import { c as resolveMSTeamsUserAllowlist, o as parseMSTeamsTeamEntry, s as resolveMSTeamsChannelAllowlist } from "./resolve-allowlist-D41JSziq.js";
1
+ import { O as formatUnknownError, g as saveDelegatedTokens, h as resolveMSTeamsCredentials, p as hasConfiguredMSTeamsCredentials, v as normalizeSecretInputString } from "./graph-users-C9HaPg1v.js";
2
+ import { c as resolveMSTeamsUserAllowlist, o as parseMSTeamsTeamEntry, s as resolveMSTeamsChannelAllowlist } from "./resolve-allowlist-C23ohQKQ.js";
3
3
  import { DEFAULT_ACCOUNT_ID, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicy, createTopLevelChannelGroupPolicySetter, mergeAllowFromEntries, splitSetupEntries } from "openclaw/plugin-sdk/setup";
4
4
  import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
5
5
  //#region extensions/msteams/src/setup-core.ts
@@ -1,9 +1,10 @@
1
- import { A as resolveDmGroupAccessWithLists, F as summarizeMapping, L as getMSTeamsRuntime, N as resolveSenderScopedGroupPolicy, O as resolveChannelMediaMaxBytes, R as getOptionalMSTeamsRuntime, S as mergeAllowlist, T as readStoreAllowFromForDmPolicy, a as buildMediaPayload, c as createChannelPairingController, f as dispatchReplyFromConfigWithSettledDispatcher$1, j as resolveEffectiveAllowFromLists, k as resolveDefaultGroupPolicy, l as createChannelReplyPipeline, n as DEFAULT_WEBHOOK_MAX_BODY_BYTES, p as evaluateSenderGroupAccessForPolicy$1, t as DEFAULT_ACCOUNT_ID, v as isDangerousNameMatchingEnabled, x as logTypingFailure, y as keepHttpServerTaskAlive } from "./runtime-api-DV1iVMn1.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, 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-9uQJepqr.js";
3
- import { c as resolveMSTeamsUserAllowlist, s as resolveMSTeamsChannelAllowlist } from "./resolve-allowlist-D41JSziq.js";
4
- import { a as resolveMSTeamsRouteConfig, i as resolveMSTeamsReplyPolicy, n as resolveMSTeamsAllowlistMatch, t as isMSTeamsGroupAllowed } from "./policy-DTnU2GR7.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-D_H8yFps.js";
1
+ import { A as resolveDmGroupAccessWithLists, F as summarizeMapping, L as getMSTeamsRuntime, N as resolveSenderScopedGroupPolicy, O as resolveChannelMediaMaxBytes, R as getOptionalMSTeamsRuntime, S as mergeAllowlist, T as readStoreAllowFromForDmPolicy, a as buildMediaPayload, c as createChannelMessageReplyPipeline, f as dispatchReplyFromConfigWithSettledDispatcher$1, j as resolveEffectiveAllowFromLists, k as resolveDefaultGroupPolicy, l as createChannelPairingController, n as DEFAULT_WEBHOOK_MAX_BODY_BYTES, p as evaluateSenderGroupAccessForPolicy$1, t as DEFAULT_ACCOUNT_ID, v as isDangerousNameMatchingEnabled, x as logTypingFailure, y as keepHttpServerTaskAlive } from "./runtime-api-D92XOrVf.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-C9HaPg1v.js";
3
+ import { c as resolveMSTeamsUserAllowlist, s as resolveMSTeamsChannelAllowlist } from "./resolve-allowlist-C23ohQKQ.js";
4
+ import { a as resolveMSTeamsRouteConfig, i as resolveMSTeamsReplyPolicy, n as resolveMSTeamsAllowlistMatch, t as isMSTeamsGroupAllowed } from "./policy-D32Kf1ED.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-Bs955MJZ.js";
6
6
  import { formatAllowlistMatchMeta } from "openclaw/plugin-sdk/allow-from";
7
+ import { createLiveMessageState, createPreviewMessageReceipt, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, markLiveMessageFinalized } from "openclaw/plugin-sdk/channel-message";
7
8
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/text-runtime";
8
9
  import { createDraftStreamLoop } from "openclaw/plugin-sdk/channel-lifecycle";
9
10
  import { readResponseWithLimit } from "openclaw/plugin-sdk/media-runtime";
@@ -11,14 +12,16 @@ import { dispatchReplyFromConfigWithSettledDispatcher, hasFinalInboundReplyDispa
11
12
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
12
13
  import { Buffer as Buffer$1 } from "node:buffer";
13
14
  import path from "node:path";
14
- import fs from "node:fs/promises";
15
+ import { appendRegularFile } from "openclaw/plugin-sdk/security-runtime";
16
+ import { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
15
17
  import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
18
+ import fs from "node:fs/promises";
16
19
  import { logInboundDrop, resolveInboundMentionDecision, resolveInboundSessionEnvelopeContext } from "openclaw/plugin-sdk/channel-inbound";
17
20
  import { resolveDualTextControlCommandGate } from "openclaw/plugin-sdk/command-gating";
18
21
  import { filterSupplementalContextItems, resolveChannelContextVisibilityMode, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/context-visibility-runtime";
19
22
  import { evaluateSenderGroupAccessForPolicy } from "openclaw/plugin-sdk/group-access";
20
23
  import { DEFAULT_GROUP_HISTORY_LIMIT, buildPendingHistoryContextFromMap, recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history";
21
- import { createChannelProgressDraftGate, formatChannelProgressDraftLine, formatChannelProgressDraftLineForEntry, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, resolveChannelPreviewStreamMode, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress } from "openclaw/plugin-sdk/channel-streaming";
24
+ import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelProgressDraftGate, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, resolveChannelPreviewStreamMode, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress } from "openclaw/plugin-sdk/channel-streaming";
22
25
  //#region extensions/msteams/src/feedback-reflection-prompt.ts
23
26
  /** Max chars of the thumbed-down response to include in the reflection prompt. */
24
27
  const MAX_RESPONSE_CHARS = 500;
@@ -126,8 +129,7 @@ async function storeSessionLearning(params) {
126
129
  let learnings = exists ? existingLearnings : legacyLearnings;
127
130
  learnings.push(params.learning);
128
131
  if (learnings.length > 10) learnings = learnings.slice(-10);
129
- await fs.mkdir(path.dirname(learningsFile), { recursive: true });
130
- await fs.writeFile(learningsFile, JSON.stringify(learnings, null, 2), "utf-8");
132
+ await writeJsonFileAtomically(learningsFile, learnings);
131
133
  if (!exists && legacyLearningsFile !== learningsFile) await fs.rm(legacyLearningsFile, { force: true }).catch(() => void 0);
132
134
  }
133
135
  //#endregion
@@ -842,7 +844,7 @@ function resolveDownloadCandidate(att) {
842
844
  const contentType = normalizeContentType(att.contentType);
843
845
  const name = normalizeOptionalString(att.name) ?? "";
844
846
  if (contentType === "application/vnd.microsoft.teams.file.download.info") {
845
- if (!isRecord(att.content)) return null;
847
+ if (!isRecord$1(att.content)) return null;
846
848
  const downloadUrl = normalizeOptionalString(att.content.downloadUrl) ?? "";
847
849
  if (!downloadUrl) return null;
848
850
  const fileType = normalizeOptionalString(att.content.fileType) ?? "";
@@ -1612,6 +1614,7 @@ var TeamsHttpStream = class {
1612
1614
  this.finalized = false;
1613
1615
  this.streamFailed = false;
1614
1616
  this.lastStreamedText = "";
1617
+ this.finalMessageId = void 0;
1615
1618
  this.streamStartedAt = void 0;
1616
1619
  this.sendActivity = options.sendActivity;
1617
1620
  this.feedbackLoopEnabled = options.feedbackLoopEnabled ?? false;
@@ -1678,22 +1681,23 @@ var TeamsHttpStream = class {
1678
1681
  * Finalize the stream — send the final message activity.
1679
1682
  */
1680
1683
  async finalize() {
1681
- if (this.finalized) return;
1684
+ if (this.finalized) return this.finalMessageId;
1682
1685
  this.finalized = true;
1683
1686
  this.stopped = true;
1684
1687
  this.loop.stop();
1685
1688
  await this.loop.waitForInFlight();
1686
- if (!this.accumulatedText.trim()) return;
1689
+ if (!this.accumulatedText.trim()) return this.finalMessageId;
1687
1690
  if (this.streamFailed) {
1688
1691
  if (this.streamId) try {
1689
- await this.sendActivity({
1692
+ const response = await this.sendActivity({
1690
1693
  type: "message",
1691
1694
  text: this.lastStreamedText || "",
1692
1695
  channelData: { feedbackLoopEnabled: this.feedbackLoopEnabled },
1693
1696
  entities: [AI_GENERATED_ENTITY, buildStreamInfoEntity(this.streamId, "final")]
1694
1697
  });
1698
+ this.finalMessageId = extractId(response);
1695
1699
  } catch {}
1696
- return;
1700
+ return this.finalMessageId;
1697
1701
  }
1698
1702
  try {
1699
1703
  const entities = [AI_GENERATED_ENTITY];
@@ -1704,11 +1708,13 @@ var TeamsHttpStream = class {
1704
1708
  channelData: { feedbackLoopEnabled: this.feedbackLoopEnabled },
1705
1709
  entities
1706
1710
  };
1707
- await this.sendActivity(finalActivity);
1711
+ const response = await this.sendActivity(finalActivity);
1712
+ this.finalMessageId = extractId(response);
1708
1713
  } catch (err) {
1709
1714
  this.streamFailed = true;
1710
1715
  this.onError?.(err);
1711
1716
  }
1717
+ return this.finalMessageId;
1712
1718
  }
1713
1719
  /** Whether streaming successfully delivered content (at least one chunk sent, not failed). */
1714
1720
  get hasContent() {
@@ -1726,6 +1732,14 @@ var TeamsHttpStream = class {
1726
1732
  get isFinalized() {
1727
1733
  return this.finalized;
1728
1734
  }
1735
+ /** Platform id returned by the final message activity, when available. */
1736
+ get messageId() {
1737
+ return this.finalMessageId;
1738
+ }
1739
+ /** Stream id returned by the first streaminfo activity, when available. */
1740
+ get previewStreamId() {
1741
+ return this.streamId;
1742
+ }
1729
1743
  /** Whether streaming fell back (not used in this implementation). */
1730
1744
  get isFallback() {
1731
1745
  return false;
@@ -1781,6 +1795,13 @@ function createTeamsReplyStreamController(params) {
1781
1795
  let progressLines = [];
1782
1796
  let lastInformativeText = "";
1783
1797
  let pendingFinalize;
1798
+ let liveState = createLiveMessageState({ canFinalizeInPlace: Boolean(stream) });
1799
+ const markStreamFinalized = () => {
1800
+ if (!stream || stream.isFailed) return;
1801
+ const messageId = stream.messageId ?? stream.previewStreamId;
1802
+ if (!messageId) return;
1803
+ liveState = markLiveMessageFinalized(liveState, createPreviewMessageReceipt({ id: messageId }));
1804
+ };
1784
1805
  const renderInformativeUpdate = async () => {
1785
1806
  if (!stream) return;
1786
1807
  const informativeText = formatChannelProgressDraftText({
@@ -1806,9 +1827,12 @@ function createTeamsReplyStreamController(params) {
1806
1827
  if (!stream || streamMode !== "progress") return;
1807
1828
  if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
1808
1829
  if (shouldStreamPreviewToolProgress) {
1809
- const normalized = line?.replace(/\s+/g, " ").trim();
1830
+ const normalized = normalizeProgressLineIdentity(line);
1810
1831
  if (normalized) {
1811
- if (progressLines.at(-1) !== normalized) progressLines = [...progressLines, normalized].slice(-resolveChannelProgressDraftMaxLines(params.msteamsConfig));
1832
+ if (normalizeProgressLineIdentity(progressLines.at(-1)) !== normalized) {
1833
+ const progressLine = typeof line === "object" && line !== void 0 ? line : normalized;
1834
+ progressLines = [...progressLines, progressLine].slice(-resolveChannelProgressDraftMaxLines(params.msteamsConfig));
1835
+ }
1812
1836
  }
1813
1837
  }
1814
1838
  await noteProgressWork();
@@ -1827,6 +1851,39 @@ function createTeamsReplyStreamController(params) {
1827
1851
  text: remainingText
1828
1852
  };
1829
1853
  };
1854
+ const finalizeProgressPayload = async (payload, hasMedia) => {
1855
+ if (!stream || !payload.text) return payload;
1856
+ return (await deliverWithFinalizableLivePreviewAdapter({
1857
+ kind: "final",
1858
+ payload,
1859
+ liveState,
1860
+ adapter: defineFinalizableLivePreviewAdapter({
1861
+ draft: {
1862
+ flush: async () => {},
1863
+ clear: async () => {},
1864
+ id: () => stream.previewStreamId
1865
+ },
1866
+ buildFinalEdit: (candidate) => candidate.text ? { text: candidate.text } : void 0,
1867
+ editFinal: async (_previewId, edit) => {
1868
+ const finalized = await stream.replaceInformativeWithFinal(edit.text);
1869
+ informativeUpdateSent = false;
1870
+ if (!finalized || stream.isFailed) throw new Error("Teams progress stream finalization failed");
1871
+ },
1872
+ resolveFinalizedId: (previewId) => stream.messageId ?? stream.previewStreamId ?? previewId,
1873
+ createPreviewReceipt: (id) => createPreviewMessageReceipt({ id }),
1874
+ onPreviewFinalized: (_id, _receipt, state) => {
1875
+ liveState = state;
1876
+ },
1877
+ logPreviewEditFailure: (err) => {
1878
+ params.log.debug?.(`stream finalization failed: ${formatUnknownError(err)}`);
1879
+ }
1880
+ }),
1881
+ deliverNormally: async () => false
1882
+ })).kind === "preview-finalized" ? hasMedia ? {
1883
+ ...payload,
1884
+ text: void 0
1885
+ } : void 0 : payload;
1886
+ };
1830
1887
  return {
1831
1888
  async onReplyStart() {},
1832
1889
  async noteProgressWork(options) {
@@ -1851,13 +1908,7 @@ function createTeamsReplyStreamController(params) {
1851
1908
  const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
1852
1909
  if (stream && streamMode === "progress" && informativeUpdateSent && !stream.isFinalized) {
1853
1910
  if (!payload.text) return payload;
1854
- const finalized = await stream.replaceInformativeWithFinal(payload.text);
1855
- informativeUpdateSent = false;
1856
- if (!finalized || stream.isFailed) return payload;
1857
- return hasMedia ? {
1858
- ...payload,
1859
- text: void 0
1860
- } : void 0;
1911
+ return await finalizeProgressPayload(payload, hasMedia);
1861
1912
  }
1862
1913
  if (!stream || !streamReceivedTokens) return payload;
1863
1914
  if (stream.isFailed) {
@@ -1866,7 +1917,9 @@ function createTeamsReplyStreamController(params) {
1866
1917
  }
1867
1918
  if (!stream.hasContent || stream.isFinalized) return payload;
1868
1919
  streamReceivedTokens = false;
1869
- pendingFinalize = stream.finalize();
1920
+ pendingFinalize = stream.finalize().then(() => {
1921
+ markStreamFinalized();
1922
+ });
1870
1923
  if (!hasMedia) return;
1871
1924
  return {
1872
1925
  ...payload,
@@ -1876,11 +1929,17 @@ function createTeamsReplyStreamController(params) {
1876
1929
  async finalize() {
1877
1930
  progressDraftGate.cancel();
1878
1931
  await pendingFinalize;
1879
- await stream?.finalize();
1932
+ if (!pendingFinalize) {
1933
+ await stream?.finalize();
1934
+ markStreamFinalized();
1935
+ }
1880
1936
  },
1881
1937
  hasStream() {
1882
1938
  return Boolean(stream);
1883
1939
  },
1940
+ liveState() {
1941
+ return liveState;
1942
+ },
1884
1943
  /**
1885
1944
  * Whether the Teams streaming card is currently receiving LLM tokens.
1886
1945
  * Used to gate side-channel keepalive activity so we don't overlay plain
@@ -1905,6 +1964,9 @@ function createTeamsReplyStreamController(params) {
1905
1964
  }
1906
1965
  };
1907
1966
  }
1967
+ function normalizeProgressLineIdentity(line) {
1968
+ return (typeof line === "string" ? line : line?.text)?.replace(/\s+/g, " ").trim() ?? "";
1969
+ }
1908
1970
  //#endregion
1909
1971
  //#region extensions/msteams/src/reply-dispatcher.ts
1910
1972
  function createMSTeamsReplyDispatcher(params) {
@@ -1952,7 +2014,7 @@ function createMSTeamsReplyDispatcher(params) {
1952
2014
  if (streamActiveRef.current()) return;
1953
2015
  await rawSendTypingIndicator();
1954
2016
  } : async () => {};
1955
- const { onModelSelected, typingCallbacks, ...replyPipeline } = createChannelReplyPipeline({
2017
+ const { onModelSelected, typingCallbacks, ...replyPipeline } = createChannelMessageReplyPipeline({
1956
2018
  cfg: params.cfg,
1957
2019
  agentId: params.agentId,
1958
2020
  channel: "msteams",
@@ -2145,7 +2207,7 @@ function createMSTeamsReplyDispatcher(params) {
2145
2207
  ...streamController.shouldSuppressDefaultToolProgressMessages() ? { suppressDefaultToolProgressMessages: true } : {},
2146
2208
  ...streamController.shouldStreamPreviewToolProgress() ? {
2147
2209
  onToolStart: async (payload) => {
2148
- await streamController.pushProgressLine(formatChannelProgressDraftLineForEntry(msteamsCfg, {
2210
+ await streamController.pushProgressLine(buildChannelProgressDraftLineForEntry(msteamsCfg, {
2149
2211
  event: "tool",
2150
2212
  name: payload.name,
2151
2213
  phase: payload.phase,
@@ -2153,7 +2215,7 @@ function createMSTeamsReplyDispatcher(params) {
2153
2215
  }, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
2154
2216
  },
2155
2217
  onItemEvent: async (payload) => {
2156
- await streamController.pushProgressLine(formatChannelProgressDraftLineForEntry(msteamsCfg, {
2218
+ await streamController.pushProgressLine(buildChannelProgressDraftLineForEntry(msteamsCfg, {
2157
2219
  event: "item",
2158
2220
  itemKind: payload.kind,
2159
2221
  title: payload.title,
@@ -2167,7 +2229,7 @@ function createMSTeamsReplyDispatcher(params) {
2167
2229
  },
2168
2230
  onPlanUpdate: async (payload) => {
2169
2231
  if (payload.phase !== "update") return;
2170
- await streamController.pushProgressLine(formatChannelProgressDraftLine({
2232
+ await streamController.pushProgressLine(buildChannelProgressDraftLine({
2171
2233
  event: "plan",
2172
2234
  phase: payload.phase,
2173
2235
  title: payload.title,
@@ -2177,7 +2239,7 @@ function createMSTeamsReplyDispatcher(params) {
2177
2239
  },
2178
2240
  onApprovalEvent: async (payload) => {
2179
2241
  if (payload.phase !== "requested") return;
2180
- await streamController.pushProgressLine(formatChannelProgressDraftLine({
2242
+ await streamController.pushProgressLine(buildChannelProgressDraftLine({
2181
2243
  event: "approval",
2182
2244
  phase: payload.phase,
2183
2245
  title: payload.title,
@@ -2188,7 +2250,7 @@ function createMSTeamsReplyDispatcher(params) {
2188
2250
  },
2189
2251
  onCommandOutput: async (payload) => {
2190
2252
  if (payload.phase !== "end") return;
2191
- await streamController.pushProgressLine(formatChannelProgressDraftLine({
2253
+ await streamController.pushProgressLine(buildChannelProgressDraftLine({
2192
2254
  event: "command-output",
2193
2255
  phase: payload.phase,
2194
2256
  title: payload.title,
@@ -2199,7 +2261,7 @@ function createMSTeamsReplyDispatcher(params) {
2199
2261
  },
2200
2262
  onPatchSummary: async (payload) => {
2201
2263
  if (payload.phase !== "end") return;
2202
- await streamController.pushProgressLine(formatChannelProgressDraftLine({
2264
+ await streamController.pushProgressLine(buildChannelProgressDraftLine({
2203
2265
  event: "patch",
2204
2266
  phase: payload.phase,
2205
2267
  title: payload.title,
@@ -2430,7 +2492,7 @@ function extractTextFromHtmlAttachments(attachments) {
2430
2492
  for (const attachment of attachments) {
2431
2493
  if (attachment.contentType !== "text/html") continue;
2432
2494
  const content = attachment.content;
2433
- const raw = typeof content === "string" ? content : isRecord(content) && typeof content.text === "string" ? content.text : isRecord(content) && typeof content.body === "string" ? content.body : "";
2495
+ const raw = typeof content === "string" ? content : isRecord$1(content) && typeof content.text === "string" ? content.text : isRecord$1(content) && typeof content.body === "string" ? content.body : "";
2434
2496
  if (!raw) continue;
2435
2497
  const text = raw.replace(/<at[^>]*>.*?<\/at>/gis, " ").replace(/<a\b[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gis, "$2 $1").replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<[^>]+>/g, " ").replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/\s+/g, " ").trim();
2436
2498
  if (text) return text;
@@ -3021,7 +3083,7 @@ function createMSTeamsMessageHandler(deps) {
3021
3083
  logVerboseMessage(`msteams: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${teamsTo}`);
3022
3084
  } catch (err) {
3023
3085
  log.error("dispatch failed", { error: formatUnknownError(err) });
3024
- runtime.error?.(`msteams dispatch failed: ${formatUnknownError(err)}`);
3086
+ runtime.error(`msteams dispatch failed: ${formatUnknownError(err)}`);
3025
3087
  try {
3026
3088
  await context.sendActivity("⚠️ Something went wrong. Please try again.");
3027
3089
  } catch {}
@@ -3062,7 +3124,7 @@ function createMSTeamsMessageHandler(deps) {
3062
3124
  });
3063
3125
  },
3064
3126
  onError: (err) => {
3065
- runtime.error?.(`msteams debounce flush failed: ${formatUnknownError(err)}`);
3127
+ runtime.error(`msteams debounce flush failed: ${formatUnknownError(err)}`);
3066
3128
  }
3067
3129
  });
3068
3130
  return async function handleTeamsMessage(context) {
@@ -3603,8 +3665,11 @@ async function handleFeedbackInvoke(context, deps) {
3603
3665
  try {
3604
3666
  const storePath = core.channel.session.resolveStorePath(deps.cfg.session?.store, { agentId: route.agentId });
3605
3667
  const safeKey = route.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
3606
- const transcriptFile = path.join(storePath, `${safeKey}.jsonl`);
3607
- await fs.appendFile(transcriptFile, JSON.stringify(feedbackEvent) + "\n", "utf-8").catch(() => {});
3668
+ await appendRegularFile({
3669
+ filePath: path.join(storePath, `${safeKey}.jsonl`),
3670
+ content: `${JSON.stringify(feedbackEvent)}\n`,
3671
+ rejectSymlinkParents: true
3672
+ }).catch(() => {});
3608
3673
  } catch {}
3609
3674
  const conversationRef = {
3610
3675
  activityId: activity.id,
@@ -3744,7 +3809,7 @@ function registerMSTeamsHandlers(handler, deps) {
3744
3809
  try {
3745
3810
  await handleTeamsMessage(context);
3746
3811
  } catch (err) {
3747
- deps.runtime.error?.(`msteams handler failed: ${formatUnknownError(err)}`);
3812
+ deps.runtime.error(`msteams handler failed: ${formatUnknownError(err)}`);
3748
3813
  }
3749
3814
  await next();
3750
3815
  });
@@ -3788,7 +3853,7 @@ function registerMSTeamsHandlers(handler, deps) {
3788
3853
  try {
3789
3854
  await handleReaction(context, "added");
3790
3855
  } catch (err) {
3791
- deps.runtime.error?.(`msteams reaction handler failed: ${String(err)}`);
3856
+ deps.runtime.error(`msteams reaction handler failed: ${String(err)}`);
3792
3857
  }
3793
3858
  await next();
3794
3859
  });
@@ -3796,7 +3861,7 @@ function registerMSTeamsHandlers(handler, deps) {
3796
3861
  try {
3797
3862
  await handleReaction(context, "removed");
3798
3863
  } catch (err) {
3799
- deps.runtime.error?.(`msteams reaction handler failed: ${String(err)}`);
3864
+ deps.runtime.error(`msteams reaction handler failed: ${String(err)}`);
3800
3865
  }
3801
3866
  await next();
3802
3867
  });
@@ -3941,7 +4006,17 @@ async function monitorMSTeamsProvider(opts) {
3941
4006
  let allowFrom = msteamsCfg.allowFrom;
3942
4007
  let groupAllowFrom = msteamsCfg.groupAllowFrom;
3943
4008
  let teamsConfig = msteamsCfg.teams;
4009
+ const allowNameMatching = isDangerousNameMatchingEnabled(msteamsCfg);
3944
4010
  const cleanAllowEntry = (entry) => entry.replace(/^(msteams|teams):/i, "").replace(/^user:/i, "").trim();
4011
+ const isStableUserId = (entry) => /^[0-9a-fA-F-]{16,}$/.test(entry);
4012
+ const cleanAllowEntries = (entries) => entries?.map((entry) => cleanAllowEntry(entry)).filter((entry) => entry && entry !== "*") ?? [];
4013
+ const mergeStableUserIds = (entries) => {
4014
+ const additions = cleanAllowEntries(entries).filter((entry) => isStableUserId(entry));
4015
+ return additions.length > 0 ? mergeAllowlist({
4016
+ existing: entries,
4017
+ additions
4018
+ }) : entries;
4019
+ };
3945
4020
  const resolveAllowlistUsers = async (label, entries) => {
3946
4021
  if (entries.length === 0) return {
3947
4022
  additions: [],
@@ -3962,23 +4037,27 @@ async function monitorMSTeamsProvider(opts) {
3962
4037
  };
3963
4038
  };
3964
4039
  try {
3965
- const allowEntries = allowFrom?.map((entry) => cleanAllowEntry(entry)).filter((entry) => entry && entry !== "*") ?? [];
3966
- if (allowEntries.length > 0) {
3967
- const { additions } = await resolveAllowlistUsers("msteams users", allowEntries);
3968
- allowFrom = mergeAllowlist({
3969
- existing: allowFrom,
3970
- additions
3971
- });
3972
- }
3973
- if (Array.isArray(groupAllowFrom) && groupAllowFrom.length > 0) {
3974
- const groupEntries = groupAllowFrom.map((entry) => cleanAllowEntry(entry)).filter((entry) => entry && entry !== "*");
3975
- if (groupEntries.length > 0) {
3976
- const { additions } = await resolveAllowlistUsers("msteams group users", groupEntries);
3977
- groupAllowFrom = mergeAllowlist({
3978
- existing: groupAllowFrom,
4040
+ allowFrom = mergeStableUserIds(allowFrom);
4041
+ if (Array.isArray(groupAllowFrom) && groupAllowFrom.length > 0) groupAllowFrom = mergeStableUserIds(groupAllowFrom);
4042
+ if (allowNameMatching) {
4043
+ const allowEntries = cleanAllowEntries(allowFrom).filter((entry) => !isStableUserId(entry));
4044
+ if (allowEntries.length > 0) {
4045
+ const { additions } = await resolveAllowlistUsers("msteams users", allowEntries);
4046
+ allowFrom = mergeAllowlist({
4047
+ existing: allowFrom,
3979
4048
  additions
3980
4049
  });
3981
4050
  }
4051
+ if (Array.isArray(groupAllowFrom) && groupAllowFrom.length > 0) {
4052
+ const groupEntries = cleanAllowEntries(groupAllowFrom).filter((entry) => !isStableUserId(entry));
4053
+ if (groupEntries.length > 0) {
4054
+ const { additions } = await resolveAllowlistUsers("msteams group users", groupEntries);
4055
+ groupAllowFrom = mergeAllowlist({
4056
+ existing: groupAllowFrom,
4057
+ additions
4058
+ });
4059
+ }
4060
+ }
3982
4061
  }
3983
4062
  if (teamsConfig && Object.keys(teamsConfig).length > 0) {
3984
4063
  const entries = [];
@@ -4046,7 +4125,7 @@ async function monitorMSTeamsProvider(opts) {
4046
4125
  }
4047
4126
  }
4048
4127
  } catch (err) {
4049
- runtime.log?.(`msteams resolve failed; using config entries. ${formatUnknownError(err)}`);
4128
+ runtime?.error(`msteams resolve failed; falling back to raw config entries — allowlist members resolved via Graph may be missing. ${formatUnknownError(err)}`);
4050
4129
  }
4051
4130
  msteamsCfg = {
4052
4131
  ...msteamsCfg,
@@ -4116,7 +4195,8 @@ async function monitorMSTeamsProvider(opts) {
4116
4195
  }
4117
4196
  next();
4118
4197
  }).catch((err) => {
4119
- log.debug?.(`JWT validation error: ${formatUnknownError(err)}`);
4198
+ if (err instanceof Error && /ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ETIMEDOUT|ECONNRESET/i.test(err.code ?? err.message)) runtime?.error(`msteams: JWKS key fetch failed — check egress to login.botframework.com:443 (firewall or DNS may be blocking it). Bot will 401 all inbound requests until this is resolved. Error: ${formatUnknownError(err)}`);
4199
+ else log.debug?.(`JWT validation error: ${formatUnknownError(err)}`);
4120
4200
  res.status(401).json({ error: "Unauthorized" });
4121
4201
  });
4122
4202
  });
package/dist/test-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as msteamsPlugin } from "./channel-BOwKBAvY.js";
1
+ import { t as msteamsPlugin } from "./channel-VjDbklu8.js";
2
2
  export { msteamsPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/msteams",
3
- "version": "2026.5.7-beta.1",
3
+ "version": "2026.5.9-beta.1",
4
4
  "description": "OpenClaw Microsoft Teams channel plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,12 +9,12 @@
9
9
  "type": "module",
10
10
  "dependencies": {
11
11
  "@azure/identity": "4.13.1",
12
- "@microsoft/teams.api": "2.0.9",
13
- "@microsoft/teams.apps": "2.0.9",
12
+ "@microsoft/teams.api": "2.0.10",
13
+ "@microsoft/teams.apps": "2.0.10",
14
14
  "express": "5.2.1",
15
15
  "jsonwebtoken": "9.0.3",
16
16
  "jwks-rsa": "4.0.1",
17
- "typebox": "1.1.37"
17
+ "typebox": "1.1.38"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@openclaw/plugin-sdk": "workspace:*",
@@ -22,7 +22,7 @@
22
22
  "openclaw": "workspace:*"
23
23
  },
24
24
  "peerDependencies": {
25
- "openclaw": ">=2026.5.7-beta.1"
25
+ "openclaw": ">=2026.5.9-beta.1"
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.7-beta.1"
61
+ "pluginApi": ">=2026.5.9-beta.1"
62
62
  },
63
63
  "build": {
64
- "openclawVersion": "2026.5.7-beta.1"
64
+ "openclawVersion": "2026.5.9-beta.1"
65
65
  },
66
66
  "release": {
67
67
  "publishToClawHub": true,