@openclaw/msteams 2026.7.2-beta.7 → 2026.8.1-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,11 +1,12 @@
1
- const require_polls = require("./polls-BWKNpBSg.cjs");
1
+ const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
2
2
  const require_runtime = require("./runtime-gZTAuFux.cjs");
3
3
  require("./runtime-api.cjs");
4
- const require_resolve_allowlist = require("./resolve-allowlist-CEj9sABO.cjs");
5
- const require_channel = require("./channel-C7QxRjBW.cjs");
6
- const require_setup_surface = require("./setup-surface-ByWjaXEn.cjs");
7
- const require_sso_token_store = require("./sso-token-store-TKYk_Ms7.cjs");
8
- const require_messenger = require("./messenger-DTiaLTDO.cjs");
4
+ const require_resolve_allowlist = require("./resolve-allowlist-C6YcNC9a.cjs");
5
+ const require_channel = require("./channel-DfCbi0d_.cjs");
6
+ const require_setup_surface = require("./setup-surface-DAhGL2h8.cjs");
7
+ const require_polls = require("./polls-DvuJvaDG.cjs");
8
+ const require_sso_token_store = require("./sso-token-store-ChhO1f4i.cjs");
9
+ const require_messenger = require("./messenger-BqA8G3pY.cjs");
9
10
  let openclaw_plugin_sdk_channel_outbound = require("openclaw/plugin-sdk/channel-outbound");
10
11
  let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
11
12
  let openclaw_plugin_sdk_allow_from = require("openclaw/plugin-sdk/allow-from");
@@ -14,10 +15,10 @@ let openclaw_plugin_sdk_ssrf_runtime = require("openclaw/plugin-sdk/ssrf-runtime
14
15
  let openclaw_plugin_sdk_provider_http = require("openclaw/plugin-sdk/provider-http");
15
16
  let openclaw_plugin_sdk_number_runtime = require("openclaw/plugin-sdk/number-runtime");
16
17
  let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text-utility-runtime");
18
+ let openclaw_plugin_sdk_error_runtime = require("openclaw/plugin-sdk/error-runtime");
17
19
  let openclaw_plugin_sdk_routing = require("openclaw/plugin-sdk/routing");
18
20
  let node_crypto = require("node:crypto");
19
- node_crypto = require_polls.__toESM(node_crypto, 1);
20
- let openclaw_plugin_sdk_error_runtime = require("openclaw/plugin-sdk/error-runtime");
21
+ node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
21
22
  let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
22
23
  let openclaw_plugin_sdk_channel_ingress_runtime = require("openclaw/plugin-sdk/channel-ingress-runtime");
23
24
  let openclaw_plugin_sdk_context_visibility_runtime = require("openclaw/plugin-sdk/context-visibility-runtime");
@@ -26,7 +27,9 @@ let openclaw_plugin_sdk_agent_runtime = require("openclaw/plugin-sdk/agent-runti
26
27
  let openclaw_plugin_sdk_extension_shared = require("openclaw/plugin-sdk/extension-shared");
27
28
  let openclaw_plugin_sdk_plugin_runtime = require("openclaw/plugin-sdk/plugin-runtime");
28
29
  let openclaw_plugin_sdk_dedupe_runtime = require("openclaw/plugin-sdk/dedupe-runtime");
30
+ let openclaw_plugin_sdk_plugin_state_runtime = require("openclaw/plugin-sdk/plugin-state-runtime");
29
31
  let openclaw_plugin_sdk_collection_runtime = require("openclaw/plugin-sdk/collection-runtime");
32
+ let openclaw_plugin_sdk_gateway_runtime = require("openclaw/plugin-sdk/gateway-runtime");
30
33
  let openclaw_plugin_sdk_channel_pairing = require("openclaw/plugin-sdk/channel-pairing");
31
34
  let openclaw_plugin_sdk_account_id = require("openclaw/plugin-sdk/account-id");
32
35
  let openclaw_plugin_sdk_runtime_group_policy = require("openclaw/plugin-sdk/runtime-group-policy");
@@ -237,50 +240,53 @@ async function resolveMSTeamsSenderAccess(params) {
237
240
  channelName: activity.channelData?.channel?.name,
238
241
  allowNameMatching
239
242
  });
240
- return {
241
- ...await (0, openclaw_plugin_sdk_channel_ingress_runtime.resolveStableChannelMessageIngress)({
242
- channelId: "msteams",
243
- accountId: pairing.accountId,
244
- identity: msteamsIngressIdentity,
245
- cfg: params.cfg,
246
- readStoreAllowFrom: pairing.readAllowFromStore,
247
- subject: {
248
- stableId: senderId,
249
- aliases: {
250
- "sender-name": senderName,
251
- ...!isDirectMessage ? { "conversation-id": conversationId } : {}
252
- }
253
- },
254
- conversation: {
255
- kind: isDirectMessage ? "direct" : convType === "channel" ? "channel" : "group",
256
- id: conversationId,
257
- parentId: activity.channelData?.team?.id
258
- },
259
- route: (0, openclaw_plugin_sdk_channel_ingress_runtime.channelIngressRoutes)(!isDirectMessage && channelGate.allowlistConfigured && {
260
- id: "msteams:team-channel",
261
- kind: "nestedAllowlist",
262
- allowed: channelGate.allowed,
263
- precedence: 0,
264
- matchId: "msteams-route",
265
- ...channelGate.allowed && groupPolicy === "allowlist" ? {
266
- senderPolicy: "deny-when-empty",
267
- senderAllowFromSource: "effective-group"
268
- } : {}
269
- }),
270
- dmPolicy,
271
- groupPolicy,
272
- policy: {
273
- groupAllowFromFallbackToAllowFrom: true,
274
- mutableIdentifierMatching: allowNameMatching ? "enabled" : "disabled"
275
- },
276
- allowFrom: configuredDmAllowFrom,
277
- groupAllowFrom,
278
- command: {
279
- allowTextCommands: true,
280
- hasControlCommand: params.hasControlCommand === true,
281
- directGroupAllowFrom: isDirectMessage ? "effective" : "none"
243
+ const resolved = await (0, openclaw_plugin_sdk_channel_ingress_runtime.resolveStableChannelMessageIngress)({
244
+ channelId: "msteams",
245
+ accountId: pairing.accountId,
246
+ identity: msteamsIngressIdentity,
247
+ cfg: params.cfg,
248
+ readStoreAllowFrom: pairing.readAllowFromStore,
249
+ subject: {
250
+ stableId: senderId,
251
+ aliases: {
252
+ "sender-name": senderName,
253
+ ...!isDirectMessage ? { "conversation-id": conversationId } : {}
282
254
  }
255
+ },
256
+ conversation: {
257
+ kind: isDirectMessage ? "direct" : convType === "channel" ? "channel" : "group",
258
+ id: conversationId,
259
+ threadId: params.conversationThreadId
260
+ },
261
+ ...params.contextBinding ? { contextBinding: params.contextBinding } : {},
262
+ route: (0, openclaw_plugin_sdk_channel_ingress_runtime.channelIngressRoutes)(!isDirectMessage && channelGate.allowlistConfigured && {
263
+ id: "msteams:team-channel",
264
+ kind: "nestedAllowlist",
265
+ allowed: channelGate.allowed,
266
+ precedence: 0,
267
+ matchId: "msteams-route",
268
+ ...channelGate.allowed && groupPolicy === "allowlist" ? {
269
+ senderPolicy: "deny-when-empty",
270
+ senderAllowFromSource: "effective-group"
271
+ } : {}
283
272
  }),
273
+ dmPolicy,
274
+ groupPolicy,
275
+ policy: {
276
+ groupAllowFromFallbackToAllowFrom: true,
277
+ mutableIdentifierMatching: allowNameMatching ? "enabled" : "disabled"
278
+ },
279
+ allowFrom: configuredDmAllowFrom,
280
+ groupAllowFrom,
281
+ command: {
282
+ allowTextCommands: true,
283
+ hasControlCommand: params.hasControlCommand === true,
284
+ directGroupAllowFrom: isDirectMessage ? "effective" : "none"
285
+ }
286
+ });
287
+ return {
288
+ ...resolved,
289
+ channelIngress: resolved,
284
290
  pairing,
285
291
  isDirectMessage,
286
292
  conversationId,
@@ -381,7 +387,7 @@ async function admitMSTeamsMessage(params) {
381
387
  params.log.debug?.("dropping group message (groupPolicy: allowlist, no allowlist)", { conversationId: params.conversationId });
382
388
  return null;
383
389
  }
384
- if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_not_allowlisted") {
390
+ if (!senderAccess.allowed) {
385
391
  const allowMatch = require_channel.resolveMSTeamsAllowlistMatch({
386
392
  allowFrom: effectiveGroupAllowFrom,
387
393
  senderId,
@@ -473,7 +479,7 @@ async function fetchBotFrameworkAttachmentInfo(params) {
473
479
  timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
474
480
  });
475
481
  } catch (err) {
476
- params.logger?.warn?.("msteams botFramework attachmentInfo fetch failed", { error: err instanceof Error ? err.message : String(err) });
482
+ params.logger?.warn?.("msteams botFramework attachmentInfo fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
477
483
  return;
478
484
  }
479
485
  if (!response.ok) {
@@ -484,7 +490,7 @@ async function fetchBotFrameworkAttachmentInfo(params) {
484
490
  try {
485
491
  return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(response, "msteams botFramework attachmentInfo");
486
492
  } catch (err) {
487
- params.logger?.warn?.("msteams botFramework attachmentInfo parse failed", { error: err instanceof Error ? err.message : String(err) });
493
+ params.logger?.warn?.("msteams botFramework attachmentInfo parse failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
488
494
  return;
489
495
  }
490
496
  }
@@ -506,7 +512,7 @@ async function saveBotFrameworkAttachmentView(params) {
506
512
  timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
507
513
  });
508
514
  } catch (err) {
509
- params.logger?.warn?.("msteams botFramework attachmentView fetch failed", { error: err instanceof Error ? err.message : String(err) });
515
+ params.logger?.warn?.("msteams botFramework attachmentView fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
510
516
  return;
511
517
  }
512
518
  if (!response.ok) {
@@ -519,7 +525,7 @@ async function saveBotFrameworkAttachmentView(params) {
519
525
  contentLength = (0, openclaw_plugin_sdk_media_runtime.parseMediaContentLength)(response.headers.get("content-length"));
520
526
  } catch (err) {
521
527
  await response.body?.cancel().catch(() => void 0);
522
- params.logger?.warn?.("msteams botFramework attachmentView invalid content-length", { error: err instanceof Error ? err.message : String(err) });
528
+ params.logger?.warn?.("msteams botFramework attachmentView invalid content-length", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
523
529
  return;
524
530
  }
525
531
  if (contentLength !== null && contentLength > params.maxBytes) {
@@ -536,7 +542,7 @@ async function saveBotFrameworkAttachmentView(params) {
536
542
  originalFilename: params.preserveFilenames ? params.fileNameHint : void 0
537
543
  });
538
544
  } catch (err) {
539
- params.logger?.warn?.("msteams botFramework attachmentView save failed", { error: err instanceof Error ? err.message : String(err) });
545
+ params.logger?.warn?.("msteams botFramework attachmentView save failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
540
546
  return;
541
547
  } finally {
542
548
  await response.body?.cancel().catch(() => void 0);
@@ -564,7 +570,7 @@ async function downloadMSTeamsBotFrameworkAttachment(params) {
564
570
  work: () => tokenProvider.getAccessToken(BOT_FRAMEWORK_SCOPE)
565
571
  });
566
572
  } catch (err) {
567
- params.logger?.warn?.("msteams botFramework token acquisition failed", { error: err instanceof Error ? err.message : String(err) });
573
+ params.logger?.warn?.("msteams botFramework token acquisition failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
568
574
  return;
569
575
  }
570
576
  if (!accessToken) return;
@@ -665,7 +671,7 @@ async function downloadMSTeamsBotFrameworkAttachments(params) {
665
671
  sourceId: attachmentId
666
672
  });
667
673
  params.logger?.warn?.("msteams botFramework attachment download failed", {
668
- error: err instanceof Error ? err.message : String(err),
674
+ error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
669
675
  attachmentId
670
676
  });
671
677
  }
@@ -878,9 +884,6 @@ function scopeCandidatesForUrl(url) {
878
884
  return ["https://api.botframework.com", "https://graph.microsoft.com"];
879
885
  }
880
886
  }
881
- function isRedirectStatus(status) {
882
- return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
883
- }
884
887
  async function resolveInlineDataImageMime(inline) {
885
888
  const mime = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(await require_runtime.getMSTeamsRuntime().media.detectMime({
886
889
  buffer: inline.data,
@@ -903,7 +906,7 @@ async function fetchWithAuthFallback(params) {
903
906
  const tokenProvider = params.tokenProvider;
904
907
  if (firstAttempt.status !== 401 && firstAttempt.status !== 403) return firstAttempt;
905
908
  if (!require_resolve_allowlist.isUrlAllowed(params.url, params.policy.authAllowHosts)) return firstAttempt;
906
- await firstAttempt.body?.cancel();
909
+ let fallbackAttempt = firstAttempt;
907
910
  const scopes = scopeCandidatesForUrl(params.url);
908
911
  const fetchFn = params.fetchFn ?? fetch;
909
912
  for (const scope of scopes) try {
@@ -926,15 +929,11 @@ async function fetchWithAuthFallback(params) {
926
929
  resolveFn: params.resolveFn,
927
930
  timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
928
931
  });
929
- if (authAttempt.ok) return authAttempt;
930
- if (isRedirectStatus(authAttempt.status)) return authAttempt;
931
- if (authAttempt.status !== 401 && authAttempt.status !== 403) {
932
- await authAttempt.body?.cancel();
933
- continue;
934
- }
935
- await authAttempt.body?.cancel();
932
+ await fallbackAttempt.body?.cancel().catch(() => void 0);
933
+ if (authAttempt.ok || require_resolve_allowlist.isRedirectStatus(authAttempt.status)) return authAttempt;
934
+ fallbackAttempt = authAttempt;
936
935
  } catch {}
937
- return firstAttempt;
936
+ return fallbackAttempt;
938
937
  }
939
938
  /**
940
939
  * Download all file attachments from a Teams message (images, documents, etc.).
@@ -1015,7 +1014,7 @@ async function downloadMSTeamsAttachments(params) {
1015
1014
  }, candidate.sourceId));
1016
1015
  } catch (err) {
1017
1016
  out.push(withSourceId({ kind: candidate.mediaKind }, candidate.sourceId));
1018
- params.logger?.warn?.("msteams inline attachment decode failed", { error: err instanceof Error ? err.message : String(err) });
1017
+ params.logger?.warn?.("msteams inline attachment decode failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
1019
1018
  }
1020
1019
  continue;
1021
1020
  }
@@ -1047,7 +1046,7 @@ async function downloadMSTeamsAttachments(params) {
1047
1046
  out.push(withSourceId(media, candidate.sourceId));
1048
1047
  } catch (err) {
1049
1048
  out.push(withSourceId({ kind: candidate.mediaKind }, candidate.sourceId));
1050
- const msg = err instanceof Error ? err.message : String(err);
1049
+ const msg = (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err);
1051
1050
  params.logger?.warn?.(`msteams attachment download failed host=${safeHostForLog(candidate.url)} error=${msg}`);
1052
1051
  }
1053
1052
  }
@@ -1086,6 +1085,10 @@ function buildMSTeamsGraphMessageUrl(params) {
1086
1085
  if (!chatId) return;
1087
1086
  return `${require_resolve_allowlist.GRAPH_ROOT}/chats/${encodeURIComponent(chatId)}/messages/${encodeURIComponent(messageId)}`;
1088
1087
  }
1088
+ async function releaseGraphResponse(response, release) {
1089
+ if (!response.bodyUsed) response.body?.cancel().catch(() => void 0);
1090
+ await release();
1091
+ }
1089
1092
  async function fetchGraphCollection(params) {
1090
1093
  const fetchFn = params.fetchFn ?? fetch;
1091
1094
  const { response, release } = await (0, openclaw_plugin_sdk_ssrf_runtime.fetchWithSsrFGuard)({
@@ -1098,13 +1101,10 @@ async function fetchGraphCollection(params) {
1098
1101
  });
1099
1102
  try {
1100
1103
  const status = response.status;
1101
- if (!response.ok) {
1102
- await response.body?.cancel().catch(() => void 0);
1103
- return {
1104
- status,
1105
- items: []
1106
- };
1107
- }
1104
+ if (!response.ok) return {
1105
+ status,
1106
+ items: []
1107
+ };
1108
1108
  try {
1109
1109
  return {
1110
1110
  status,
@@ -1117,7 +1117,7 @@ async function fetchGraphCollection(params) {
1117
1117
  };
1118
1118
  }
1119
1119
  } finally {
1120
- await release();
1120
+ await releaseGraphResponse(response, release);
1121
1121
  }
1122
1122
  }
1123
1123
  function normalizeGraphAttachment(att) {
@@ -1149,7 +1149,7 @@ async function downloadGraphHostedContent(params) {
1149
1149
  deadline: params.deadline
1150
1150
  });
1151
1151
  } catch (err) {
1152
- params.logger?.warn?.("msteams graph hostedContents fetch failed", { error: err instanceof Error ? err.message : String(err) });
1152
+ params.logger?.warn?.("msteams graph hostedContents fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
1153
1153
  return {
1154
1154
  media: [],
1155
1155
  count: 0
@@ -1194,11 +1194,11 @@ async function downloadGraphHostedContent(params) {
1194
1194
  sourceId: item.id
1195
1195
  });
1196
1196
  } finally {
1197
- await release();
1197
+ await releaseGraphResponse(valRes, release);
1198
1198
  }
1199
1199
  } catch (err) {
1200
1200
  out.push(createGraphHostedContentFact(item));
1201
- params.logger?.warn?.("msteams graph hostedContent value fetch failed", { error: err instanceof Error ? err.message : String(err) });
1201
+ params.logger?.warn?.("msteams graph hostedContent value fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
1202
1202
  continue;
1203
1203
  }
1204
1204
  }
@@ -1227,9 +1227,9 @@ async function downloadMSTeamsGraphMedia(params) {
1227
1227
  } catch (err) {
1228
1228
  params.logger?.debug?.("graph media token acquisition failed", {
1229
1229
  messageUrl,
1230
- error: err instanceof Error ? err.message : String(err)
1230
+ error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)
1231
1231
  });
1232
- params.logger?.warn?.("msteams graph token acquisition failed", { error: err instanceof Error ? err.message : String(err) });
1232
+ params.logger?.warn?.("msteams graph token acquisition failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
1233
1233
  return {
1234
1234
  media: [],
1235
1235
  messageUrl,
@@ -1260,10 +1260,10 @@ async function downloadMSTeamsGraphMedia(params) {
1260
1260
  } catch (err) {
1261
1261
  params.logger?.debug?.("graph media message parse failed", {
1262
1262
  messageUrl,
1263
- error: err instanceof Error ? err.message : String(err)
1263
+ error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)
1264
1264
  });
1265
1265
  params.logger?.warn?.("msteams graph message parse failed", {
1266
- error: err instanceof Error ? err.message : String(err),
1266
+ error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
1267
1267
  messageUrl
1268
1268
  });
1269
1269
  msgData = {};
@@ -1275,14 +1275,14 @@ async function downloadMSTeamsGraphMedia(params) {
1275
1275
  status: messageStatus
1276
1276
  });
1277
1277
  } finally {
1278
- await release();
1278
+ await releaseGraphResponse(msgRes, release);
1279
1279
  }
1280
1280
  } catch (err) {
1281
1281
  params.logger?.debug?.("graph media message fetch failed", {
1282
1282
  messageUrl,
1283
- error: err instanceof Error ? err.message : String(err)
1283
+ error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)
1284
1284
  });
1285
- params.logger?.warn?.("msteams graph message fetch failed", { error: err instanceof Error ? err.message : String(err) });
1285
+ params.logger?.warn?.("msteams graph message fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
1286
1286
  }
1287
1287
  for (const att of referenceAttachments) {
1288
1288
  const name = att.name ?? "file";
@@ -1348,7 +1348,7 @@ async function downloadMSTeamsGraphMedia(params) {
1348
1348
  } catch (err) {
1349
1349
  sharePointMedia.push(unavailableMedia);
1350
1350
  params.logger?.warn?.("msteams SharePoint reference download failed", {
1351
- error: err instanceof Error ? err.message : String(err),
1351
+ error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
1352
1352
  name
1353
1353
  });
1354
1354
  }
@@ -1387,7 +1387,7 @@ async function downloadMSTeamsGraphMedia(params) {
1387
1387
  });
1388
1388
  } catch (err) {
1389
1389
  params.logger?.warn?.("msteams graph attachment download failed", {
1390
- error: err instanceof Error ? err.message : String(err),
1390
+ error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
1391
1391
  messageUrl
1392
1392
  });
1393
1393
  }
@@ -1654,12 +1654,22 @@ function createTeamsReplyStreamController(params) {
1654
1654
  let emittedText = "";
1655
1655
  let acknowledgedText = "";
1656
1656
  let acknowledgedStreamId;
1657
+ let replacementFinalPending = false;
1658
+ let replacementEmitFailed = false;
1659
+ let replacementSettlementPending = false;
1660
+ let replacementTextAwaitingAcknowledgement;
1661
+ let deferredReplacementEntries = [];
1662
+ let finalMetadataQueued = false;
1663
+ let failedSegmentFallbackPrepared = false;
1657
1664
  const streamEvents = stream?.events;
1658
1665
  let streamChunkSubscription;
1659
1666
  if (typeof streamEvents?.on === "function" && typeof streamEvents.off === "function") streamChunkSubscription = streamEvents.on("chunk", (activity) => {
1660
- if (activity.type !== "typing" || activity.channelData?.streamType !== "streaming" || !activity.id || !activity.text || acknowledgedStreamId !== void 0 && activity.id !== acknowledgedStreamId || !activity.text.startsWith(acknowledgedText) || !emittedText.startsWith(activity.text)) return;
1667
+ const replacementAcknowledgementPending = replacementTextAwaitingAcknowledgement !== void 0;
1668
+ const replacementAcknowledgement = typeof activity.text === "string" && replacementAcknowledgementPending && activity.text === replacementTextAwaitingAcknowledgement;
1669
+ if (activity.type !== "typing" || activity.channelData?.streamType !== "streaming" || !activity.id || !activity.text || acknowledgedStreamId !== void 0 && activity.id !== acknowledgedStreamId || (replacementAcknowledgementPending ? !replacementAcknowledgement : !activity.text.startsWith(acknowledgedText)) || !emittedText.startsWith(activity.text)) return;
1661
1670
  acknowledgedStreamId = activity.id;
1662
1671
  acknowledgedText = activity.text;
1672
+ if (activity.text === replacementTextAwaitingAcknowledgement) replacementTextAwaitingAcknowledgement = void 0;
1663
1673
  });
1664
1674
  const wasCanceled = () => canceledLocally || Boolean(stream?.canceled);
1665
1675
  const releaseStreamChunkSubscription = () => {
@@ -1667,6 +1677,14 @@ function createTeamsReplyStreamController(params) {
1667
1677
  streamEvents?.off(streamChunkSubscription);
1668
1678
  streamChunkSubscription = void 0;
1669
1679
  };
1680
+ const acknowledgedNativeDelivery = () => {
1681
+ if (!acknowledgedStreamId || !acknowledgedText) return { visibleReplySent: false };
1682
+ return {
1683
+ visibleReplySent: true,
1684
+ content: acknowledgedText,
1685
+ messageId: acknowledgedStreamId
1686
+ };
1687
+ };
1670
1688
  const fallbackPayloadAfterAcknowledgedText = (payload) => {
1671
1689
  if (!acknowledgedText || typeof payload.text !== "string" || !payload.text.startsWith(acknowledgedText)) return payload;
1672
1690
  const remainingText = payload.text.slice(acknowledgedText.length);
@@ -1684,6 +1702,35 @@ function createTeamsReplyStreamController(params) {
1684
1702
  mediaUrls: void 0
1685
1703
  } : payload;
1686
1704
  };
1705
+ const finalStreamActivity = (text) => ({
1706
+ type: "message",
1707
+ ...text ? { text } : {},
1708
+ entities: [{
1709
+ type: "https://schema.org/Message",
1710
+ "@type": "Message",
1711
+ "@context": "https://schema.org",
1712
+ "@id": "",
1713
+ additionalType: ["AIGeneratedContent"]
1714
+ }],
1715
+ channelData: params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {}
1716
+ });
1717
+ const deferredReplacementLogicalContent = () => {
1718
+ return deferredReplacementEntries.map((entry) => entry.payload.text).filter((text) => Boolean(text)).join("\n") || void 0;
1719
+ };
1720
+ const takeDeferredReplacementPayloads = (replacementFallback) => {
1721
+ const payloads = deferredReplacementEntries.flatMap((entry) => {
1722
+ if (entry.kind === "payload") return [entry.payload];
1723
+ const hasMedia = Boolean(entry.payload.mediaUrl || entry.payload.mediaUrls?.length);
1724
+ const text = replacementFallback?.text;
1725
+ if (!text && !hasMedia) return [];
1726
+ return [{
1727
+ ...entry.payload,
1728
+ text: text || void 0
1729
+ }];
1730
+ });
1731
+ deferredReplacementEntries = [];
1732
+ return payloads;
1733
+ };
1687
1734
  /**
1688
1735
  * Render the current informative status line into the streaming card. Pulls
1689
1736
  * the rotating "Thinking..." label from msteams config (or the plugin-sdk
@@ -1708,14 +1755,19 @@ function createTeamsReplyStreamController(params) {
1708
1755
  canceledLocally = true;
1709
1756
  return;
1710
1757
  }
1711
- params.log?.debug?.(`stream informative update failed: ${err instanceof Error ? err.message : String(err)}`);
1758
+ params.log?.debug?.(`stream informative update failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
1712
1759
  }
1713
1760
  };
1714
1761
  const progressDraftGate = (0, openclaw_plugin_sdk_channel_outbound.createChannelProgressDraftGate)({ onStart: renderInformativeUpdate });
1715
1762
  return {
1716
1763
  async onReplyStart() {},
1717
1764
  onPartialReply(payload) {
1718
- if (!stream || !payload.text || wasCanceled() || streamMode !== "partial" || streamFinalizationPending) return;
1765
+ if (!stream || !payload.text || wasCanceled() || streamMode !== "partial") return;
1766
+ if (replacementSettlementPending && replacementFinalPending) {
1767
+ pendingFinalPayload = { text: payload.text };
1768
+ return;
1769
+ }
1770
+ if (streamFinalizationPending) return;
1719
1771
  const fullText = payload.text;
1720
1772
  let prefixLength = 0;
1721
1773
  while (prefixLength < emittedText.length && prefixLength < fullText.length && emittedText[prefixLength] === fullText[prefixLength]) prefixLength += 1;
@@ -1724,7 +1776,13 @@ function createTeamsReplyStreamController(params) {
1724
1776
  if (!delta) return;
1725
1777
  if (previousRemainder.trim()) {
1726
1778
  stream.clearText();
1727
- streamFailed = true;
1779
+ if (streamFailed) {
1780
+ streamFinalizationPending = true;
1781
+ return;
1782
+ }
1783
+ replacementFinalPending = true;
1784
+ replacementSettlementPending = true;
1785
+ pendingFinalPayload = { text: fullText };
1728
1786
  streamFinalizationPending = true;
1729
1787
  return;
1730
1788
  }
@@ -1739,7 +1797,7 @@ function createTeamsReplyStreamController(params) {
1739
1797
  return;
1740
1798
  }
1741
1799
  streamFailed = true;
1742
- params.log?.warn?.(`msteams stream emit failed, falling back to block delivery: ${err instanceof Error ? err.message : String(err)}`);
1800
+ params.log?.warn?.(`msteams stream emit failed, falling back to block delivery: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
1743
1801
  }
1744
1802
  },
1745
1803
  /**
@@ -1781,6 +1839,49 @@ function createTeamsReplyStreamController(params) {
1781
1839
  preparePayload(payload) {
1782
1840
  if (!stream) return payload;
1783
1841
  if (wasCanceled()) return;
1842
+ if (replacementSettlementPending) {
1843
+ if (!replacementFinalPending) {
1844
+ deferredReplacementEntries.push({
1845
+ kind: "payload",
1846
+ payload
1847
+ });
1848
+ return;
1849
+ }
1850
+ if (!payload.text) {
1851
+ deferredReplacementEntries.push({
1852
+ kind: "payload",
1853
+ payload
1854
+ });
1855
+ return;
1856
+ }
1857
+ replacementFinalPending = false;
1858
+ deferredReplacementEntries.push({
1859
+ kind: "replacement",
1860
+ payload
1861
+ });
1862
+ pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
1863
+ try {
1864
+ replacementTextAwaitingAcknowledgement = payload.text;
1865
+ stream.emit(finalStreamActivity(payload.text));
1866
+ finalMetadataQueued = true;
1867
+ emittedText = payload.text;
1868
+ tokensEmitted = false;
1869
+ return;
1870
+ } catch (err) {
1871
+ replacementTextAwaitingAcknowledgement = void 0;
1872
+ tokensEmitted = false;
1873
+ if (isStreamCancelledError(err)) {
1874
+ canceledLocally = true;
1875
+ pendingFinalPayload = void 0;
1876
+ deferredReplacementEntries = [];
1877
+ return;
1878
+ }
1879
+ streamFailed = true;
1880
+ replacementEmitFailed = true;
1881
+ params.log?.warn?.(`msteams stream replacement failed, falling back to block delivery: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
1882
+ return;
1883
+ }
1884
+ }
1784
1885
  if (tokensEmitted && !streamFailed) {
1785
1886
  const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
1786
1887
  pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
@@ -1792,11 +1893,9 @@ function createTeamsReplyStreamController(params) {
1792
1893
  } : void 0;
1793
1894
  }
1794
1895
  if (streamFailed) {
1795
- const fallback = fallbackPayloadAfterAcknowledgedText(payload);
1896
+ const fallback = failedSegmentFallbackPrepared ? payload : fallbackPayloadAfterAcknowledgedText(payload);
1897
+ failedSegmentFallbackPrepared = true;
1796
1898
  pendingFinalPayload = void 0;
1797
- acknowledgedText = "";
1798
- acknowledgedStreamId = void 0;
1799
- releaseStreamChunkSubscription();
1800
1899
  return fallback;
1801
1900
  }
1802
1901
  if (streamMode === "progress" && payload.text) try {
@@ -1813,7 +1912,7 @@ function createTeamsReplyStreamController(params) {
1813
1912
  canceledLocally = true;
1814
1913
  return;
1815
1914
  }
1816
- params.log?.debug?.(`progress-mode finalize failed: ${err instanceof Error ? err.message : String(err)}`);
1915
+ params.log?.debug?.(`progress-mode finalize failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
1817
1916
  }
1818
1917
  return payload;
1819
1918
  },
@@ -1828,74 +1927,88 @@ function createTeamsReplyStreamController(params) {
1828
1927
  releaseStreamChunkSubscription();
1829
1928
  return { visibleReplySent: false };
1830
1929
  }
1831
- const content = wasCanceled() ? acknowledgedText || void 0 : pendingFinalPayload?.text ?? (emittedText || void 0);
1930
+ let logicalContent;
1832
1931
  try {
1833
1932
  if (wasCanceled()) {
1834
1933
  pendingFinalPayload = void 0;
1934
+ deferredReplacementEntries = [];
1835
1935
  streamFinalizationPending = false;
1836
- return {
1837
- visibleReplySent: content !== void 0,
1838
- ...content === void 0 ? {} : { content }
1839
- };
1936
+ return acknowledgedNativeDelivery();
1840
1937
  }
1841
- if (!streamFinalizationPending) return {
1842
- visibleReplySent: true,
1843
- ...content === void 0 ? {} : { content }
1844
- };
1845
- const finalEntities = [{
1846
- type: "https://schema.org/Message",
1847
- "@type": "Message",
1848
- "@context": "https://schema.org",
1849
- "@id": "",
1850
- additionalType: ["AIGeneratedContent"]
1851
- }];
1852
- const finalChannelData = params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {};
1853
- stream.emit({
1854
- type: "message",
1855
- entities: finalEntities,
1856
- channelData: finalChannelData
1857
- });
1938
+ if (!streamFinalizationPending) return acknowledgedNativeDelivery();
1939
+ if (replacementSettlementPending && replacementFinalPending && pendingFinalPayload?.text) {
1940
+ replacementFinalPending = false;
1941
+ deferredReplacementEntries.push({
1942
+ kind: "replacement",
1943
+ payload: pendingFinalPayload
1944
+ });
1945
+ replacementTextAwaitingAcknowledgement = pendingFinalPayload.text;
1946
+ logicalContent = deferredReplacementLogicalContent();
1947
+ stream.emit(finalStreamActivity(pendingFinalPayload.text));
1948
+ finalMetadataQueued = true;
1949
+ emittedText = pendingFinalPayload.text;
1950
+ }
1951
+ logicalContent ??= replacementSettlementPending ? deferredReplacementLogicalContent() : void 0;
1952
+ const content = pendingFinalPayload?.text ?? (emittedText || void 0);
1953
+ if (!finalMetadataQueued) stream.emit(finalStreamActivity());
1858
1954
  const result = await stream.close();
1859
1955
  streamFinalizationPending = false;
1860
1956
  if (!result) {
1861
1957
  const fallback = pendingFinalPayload;
1862
1958
  pendingFinalPayload = void 0;
1863
- const fallbackPayload = fallback && !wasCanceled() ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
1959
+ const canceled = wasCanceled();
1960
+ const replacementFallback = replacementSettlementPending && fallback && !canceled ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
1961
+ const fallbackPayload = !replacementSettlementPending && fallback && !canceled ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
1962
+ const postNativePayloads = replacementSettlementPending && !canceled ? takeDeferredReplacementPayloads(replacementFallback) : [];
1963
+ if (canceled) deferredReplacementEntries = [];
1864
1964
  return {
1865
- visibleReplySent: true,
1866
- ...content === void 0 ? {} : { content },
1867
- ...fallbackPayload ? { fallbackPayload } : {}
1965
+ ...acknowledgedNativeDelivery(),
1966
+ ...!canceled && logicalContent ? { logicalContent } : {},
1967
+ ...fallbackPayload ? { fallbackPayload } : {},
1968
+ ...postNativePayloads.length > 0 ? { postNativePayloads } : {}
1868
1969
  };
1869
1970
  }
1971
+ const replacementFallback = replacementSettlementPending && replacementEmitFailed && pendingFinalPayload ? fallbackPayloadAfterAcknowledgedText(pendingFinalPayload) : void 0;
1870
1972
  pendingFinalPayload = void 0;
1871
- const messageId = require_messenger.extractMessageId(result) ?? void 0;
1973
+ const messageId = require_messenger.extractMessageId(result) ?? acknowledgedStreamId;
1974
+ const postNativePayloads = replacementSettlementPending ? takeDeferredReplacementPayloads(replacementFallback) : [];
1975
+ const nativeContent = replacementEmitFailed ? acknowledgedText || void 0 : content;
1872
1976
  return {
1873
- visibleReplySent: true,
1874
- ...content === void 0 ? {} : { content },
1875
- ...messageId ? { messageId } : {}
1977
+ visibleReplySent: replacementEmitFailed ? Boolean(nativeContent) : true,
1978
+ ...nativeContent === void 0 ? {} : { content: nativeContent },
1979
+ ...logicalContent ? { logicalContent } : {},
1980
+ ...messageId && (!replacementEmitFailed || nativeContent) ? { messageId } : {},
1981
+ ...postNativePayloads.length > 0 ? { postNativePayloads } : {}
1876
1982
  };
1877
1983
  } catch (err) {
1878
1984
  if (isStreamCancelledError(err)) {
1879
1985
  canceledLocally = true;
1880
1986
  pendingFinalPayload = void 0;
1987
+ deferredReplacementEntries = [];
1881
1988
  streamFinalizationPending = false;
1882
- return {
1883
- visibleReplySent: true,
1884
- ...content === void 0 ? {} : { content }
1885
- };
1989
+ return acknowledgedNativeDelivery();
1886
1990
  }
1887
1991
  streamFailed = true;
1888
1992
  streamFinalizationPending = false;
1889
- params.log?.warn?.(`msteams stream finalize failed: ${err instanceof Error ? err.message : String(err)}`);
1993
+ params.log?.warn?.(`msteams stream finalize failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
1890
1994
  const fallback = pendingFinalPayload;
1891
1995
  pendingFinalPayload = void 0;
1892
- const fallbackPayload = fallback ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
1996
+ const replacementFallback = replacementSettlementPending && fallback ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
1997
+ const fallbackPayload = !replacementSettlementPending && fallback ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
1998
+ const postNativePayloads = replacementSettlementPending ? takeDeferredReplacementPayloads(replacementFallback) : [];
1893
1999
  return {
1894
- visibleReplySent: true,
1895
- ...content === void 0 ? {} : { content },
1896
- ...fallbackPayload ? { fallbackPayload } : {}
2000
+ ...acknowledgedNativeDelivery(),
2001
+ ...logicalContent ? { logicalContent } : {},
2002
+ ...fallbackPayload ? { fallbackPayload } : {},
2003
+ ...postNativePayloads.length > 0 ? { postNativePayloads } : {}
1897
2004
  };
1898
2005
  } finally {
2006
+ finalMetadataQueued = false;
2007
+ replacementEmitFailed = false;
2008
+ replacementFinalPending = false;
2009
+ replacementSettlementPending = false;
2010
+ replacementTextAwaitingAcknowledgement = void 0;
2011
+ deferredReplacementEntries = [];
1899
2012
  releaseStreamChunkSubscription();
1900
2013
  }
1901
2014
  },
@@ -1998,6 +2111,7 @@ function createMSTeamsReplyDispatcher(params) {
1998
2111
  const blockStreamingEnabled = blockStreamingResolved ?? false;
1999
2112
  const typingIndicatorEnabled = typeof msteamsCfg?.typingIndicator === "boolean" ? msteamsCfg.typingIndicator : true;
2000
2113
  const pendingDeliveries = [];
2114
+ const joinAcceptedContents = (contents) => contents.filter((content) => Boolean(content)).join("\n");
2001
2115
  const sendMessages = async (messages) => {
2002
2116
  return require_messenger.sendMSTeamsMessages({
2003
2117
  replyStyle: params.replyStyle,
@@ -2046,11 +2160,13 @@ function createMSTeamsReplyDispatcher(params) {
2046
2160
  });
2047
2161
  };
2048
2162
  const deliveryOutcome = (delivery) => {
2049
- const messageIds = [...delivery.nativeMessageId ? [delivery.nativeMessageId] : [], ...delivery.messageIds];
2163
+ const acceptedParts = [...delivery.nativeResult ? [delivery.nativeResult] : [], ...delivery.blockResults];
2164
+ const messageIds = acceptedParts.flatMap((part) => part.messageIds);
2165
+ const content = delivery.errors.length > 0 ? joinAcceptedContents(acceptedParts.map((part) => part.content)) : delivery.content;
2050
2166
  return {
2051
- visibleReplySent: delivery.visibleReplySent,
2167
+ visibleReplySent: acceptedParts.length > 0,
2052
2168
  ...messageIds.length > 0 ? { messageIds } : {},
2053
- ...delivery.visibleReplySent && delivery.content !== void 0 ? { content: delivery.content } : {}
2169
+ ...acceptedParts.length > 0 && content !== void 0 ? { content } : {}
2054
2170
  };
2055
2171
  };
2056
2172
  const settlePendingDelivery = (delivery) => {
@@ -2062,7 +2178,7 @@ function createMSTeamsReplyDispatcher(params) {
2062
2178
  return;
2063
2179
  }
2064
2180
  const error = delivery.errors.find((candidate) => !(candidate instanceof openclaw_plugin_sdk_error_runtime.PlatformMessageNotDispatchedError)) ?? delivery.errors[0];
2065
- delivery.finalization.reject(delivery.visibleReplySent ? (0, openclaw_plugin_sdk_channel_inbound.createChannelPartialDeliveryError)(error, {
2181
+ delivery.finalization.reject(outcome.visibleReplySent ? (0, openclaw_plugin_sdk_channel_inbound.createChannelPartialDeliveryError)(error, {
2066
2182
  ...outcome,
2067
2183
  visibleReplySent: true
2068
2184
  }) : error);
@@ -2072,12 +2188,11 @@ function createMSTeamsReplyDispatcher(params) {
2072
2188
  messages,
2073
2189
  finalization: (0, openclaw_plugin_sdk_extension_shared.createDeferred)(),
2074
2190
  content: payload.text,
2191
+ blockResults: [],
2075
2192
  native,
2076
2193
  nativeSettled: !native,
2077
2194
  blockSettled: messages.length === 0,
2078
2195
  settled: false,
2079
- visibleReplySent: false,
2080
- messageIds: [],
2081
2196
  errors: []
2082
2197
  };
2083
2198
  pendingDeliveries.push(delivery);
@@ -2093,9 +2208,11 @@ function createMSTeamsReplyDispatcher(params) {
2093
2208
  const sentIds = [];
2094
2209
  for (const msg of toSend) try {
2095
2210
  const msgIds = await sendMessages([msg]);
2096
- delivery.visibleReplySent ||= msgIds.length > 0;
2097
2211
  const validIds = msgIds.filter((id) => id.trim() && id !== "unknown");
2098
- delivery.messageIds.push(...validIds);
2212
+ if (msgIds.length > 0) delivery.blockResults.push({
2213
+ messageIds: validIds,
2214
+ ...msg.text ? { content: msg.text } : {}
2215
+ });
2099
2216
  sentIds.push(...validIds);
2100
2217
  } catch (msgError) {
2101
2218
  failed += 1;
@@ -2179,11 +2296,16 @@ function createMSTeamsReplyDispatcher(params) {
2179
2296
  settlePendingDelivery(nativeDelivery);
2180
2297
  return;
2181
2298
  }
2182
- nativeDelivery.visibleReplySent ||= nativeResult.visibleReplySent;
2183
- nativeDelivery.nativeMessageId = nativeResult.messageId;
2184
- if (nativeResult.content !== void 0) nativeDelivery.content = nativeResult.content;
2185
- if (nativeResult.fallbackPayload) {
2186
- nativeDelivery.messages.push(...renderReplyPayload(nativeResult.fallbackPayload));
2299
+ if (nativeResult.visibleReplySent) nativeDelivery.nativeResult = {
2300
+ messageIds: nativeResult.messageId ? [nativeResult.messageId] : [],
2301
+ ...nativeResult.content !== void 0 ? { content: nativeResult.content } : {}
2302
+ };
2303
+ const hasPostNativePayloads = Boolean(nativeResult.postNativePayloads?.length);
2304
+ if (nativeResult.logicalContent !== void 0) nativeDelivery.content = nativeResult.logicalContent;
2305
+ else if (nativeResult.content !== void 0 && (!nativeResult.fallbackPayload && !hasPostNativePayloads || nativeDelivery.content === void 0)) nativeDelivery.content = nativeResult.content;
2306
+ const afterNativePayloads = [...nativeResult.fallbackPayload ? [nativeResult.fallbackPayload] : [], ...nativeResult.postNativePayloads ?? []];
2307
+ if (afterNativePayloads.length > 0) {
2308
+ nativeDelivery.messages.push(...afterNativePayloads.flatMap((payload) => renderReplyPayload(payload)));
2187
2309
  nativeDelivery.blockSettled = nativeDelivery.messages.length === 0;
2188
2310
  }
2189
2311
  nativeDelivery.nativeSettled = true;
@@ -2201,10 +2323,10 @@ function createMSTeamsReplyDispatcher(params) {
2201
2323
  const progressCallbacks = streamController.hasStream() ? {
2202
2324
  onReasoningStream: async (payload) => {
2203
2325
  const text = typeof payload?.text === "string" ? payload.text : void 0;
2204
- if (!text) return;
2326
+ if (!text) return false;
2205
2327
  if (payload?.isReasoningSnapshot !== true) {
2206
2328
  await streamController.pushProgressLine(text);
2207
- return;
2329
+ return false;
2208
2330
  }
2209
2331
  await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
2210
2332
  event: "item",
@@ -2213,6 +2335,7 @@ function createMSTeamsReplyDispatcher(params) {
2213
2335
  title: "Reasoning",
2214
2336
  progressText: text
2215
2337
  }));
2338
+ return false;
2216
2339
  },
2217
2340
  onToolStart: async (payload) => {
2218
2341
  const name = typeof payload?.name === "string" ? payload.name : void 0;
@@ -2225,6 +2348,7 @@ function createMSTeamsReplyDispatcher(params) {
2225
2348
  ...typeof payload?.phase === "string" ? { phase: payload.phase } : {},
2226
2349
  ...payload?.args && typeof payload.args === "object" ? { args: payload.args } : {}
2227
2350
  }, detailMode === "explain" || detailMode === "raw" ? { detailMode } : void 0), name ? { toolName: name } : void 0);
2351
+ return false;
2228
2352
  },
2229
2353
  onItemEvent: async (payload) => {
2230
2354
  await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLineForEntry)(msteamsCfg, {
@@ -2240,13 +2364,15 @@ function createMSTeamsReplyDispatcher(params) {
2240
2364
  ...typeof payload?.progressText === "string" ? { progressText: payload.progressText } : {},
2241
2365
  ...typeof payload?.meta === "string" ? { meta: payload.meta } : {}
2242
2366
  }));
2367
+ return false;
2243
2368
  },
2244
2369
  onPlanUpdate: async (payload) => {
2245
- if (payload?.phase !== "update") return;
2370
+ if (payload?.phase !== "update") return false;
2246
2371
  await streamController.pushPlanProgress((0, openclaw_plugin_sdk_channel_outbound.normalizeAgentPlanSteps)(payload.steps), { explanation: typeof payload.explanation === "string" ? payload.explanation : void 0 });
2372
+ return false;
2247
2373
  },
2248
2374
  onApprovalEvent: async (payload) => {
2249
- if (payload?.phase !== "requested") return;
2375
+ if (payload?.phase !== "requested") return false;
2250
2376
  await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
2251
2377
  event: "approval",
2252
2378
  phase: payload.phase,
@@ -2255,10 +2381,11 @@ function createMSTeamsReplyDispatcher(params) {
2255
2381
  ...typeof payload?.reason === "string" ? { reason: payload.reason } : {},
2256
2382
  ...typeof payload?.message === "string" ? { message: payload.message } : {}
2257
2383
  }));
2384
+ return false;
2258
2385
  },
2259
2386
  onCommandOutput: async (payload) => {
2260
- if (payload?.phase !== "end") return;
2261
- await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
2387
+ if (payload?.phase !== "end") return false;
2388
+ await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLineForEntry)(msteamsCfg, {
2262
2389
  event: "command-output",
2263
2390
  ...typeof payload?.itemId === "string" ? { itemId: payload.itemId } : {},
2264
2391
  ...typeof payload?.toolCallId === "string" ? { toolCallId: payload.toolCallId } : {},
@@ -2268,9 +2395,10 @@ function createMSTeamsReplyDispatcher(params) {
2268
2395
  ...typeof payload?.status === "string" ? { status: payload.status } : {},
2269
2396
  ...typeof payload?.exitCode === "number" ? { exitCode: payload.exitCode } : {}
2270
2397
  }));
2398
+ return false;
2271
2399
  },
2272
2400
  onPatchSummary: async (payload) => {
2273
- if (payload?.phase !== "end") return;
2401
+ if (payload?.phase !== "end") return false;
2274
2402
  await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
2275
2403
  event: "patch",
2276
2404
  ...typeof payload?.itemId === "string" ? { itemId: payload.itemId } : {},
@@ -2283,6 +2411,7 @@ function createMSTeamsReplyDispatcher(params) {
2283
2411
  ...Array.isArray(payload?.deleted) && payload.deleted.every((s) => typeof s === "string") ? { deleted: payload.deleted } : {},
2284
2412
  ...typeof payload?.summary === "string" ? { summary: payload.summary } : {}
2285
2413
  }));
2414
+ return false;
2286
2415
  }
2287
2416
  } : {};
2288
2417
  return {
@@ -2292,7 +2421,10 @@ function createMSTeamsReplyDispatcher(params) {
2292
2421
  },
2293
2422
  delivery,
2294
2423
  replyOptions: {
2295
- ...streamController.hasStream() ? { onPartialReply: (payload) => streamController.onPartialReply(payload) } : {},
2424
+ ...streamController.hasStream() ? { onPartialReply: (payload) => {
2425
+ streamController.onPartialReply(payload);
2426
+ return false;
2427
+ } } : {},
2296
2428
  ...progressCallbacks,
2297
2429
  ...shouldSuppressDefaultToolProgressMessages ? { suppressDefaultToolProgressMessages: true } : {},
2298
2430
  disableBlockStreaming: blockStreamingResolved == null ? void 0 : !blockStreamingResolved,
@@ -2310,14 +2442,7 @@ const sentMessages = (0, openclaw_plugin_sdk_dedupe_runtime.createPersistentDedu
2310
2442
  namespace: "msteams.sent-messages",
2311
2443
  maxEntries: 1e3,
2312
2444
  openStore: (options) => require_runtime.getOptionalMSTeamsRuntime()?.state.openKeyedStore(options),
2313
- logError: (error) => {
2314
- try {
2315
- require_runtime.getOptionalMSTeamsRuntime()?.logging.getChildLogger({
2316
- plugin: "msteams",
2317
- feature: "sent-message-state"
2318
- }).warn("Microsoft Teams persistent sent-message state failed", { error: String(error) });
2319
- } catch {}
2320
- },
2445
+ logError: (0, openclaw_plugin_sdk_plugin_state_runtime.createPluginStateErrorReporter)(require_runtime.getOptionalMSTeamsRuntime, "msteams", "sent-message-state", "Microsoft Teams persistent sent-message state failed"),
2321
2446
  readTimestamp: (record) => record.sentAt
2322
2447
  }
2323
2448
  });
@@ -2389,7 +2514,21 @@ async function dispatchMSTeamsInboundTurn(params) {
2389
2514
  }) : true;
2390
2515
  const bodyForAgent = threadContext ? `[Thread history]\n${threadContext}\n[/Thread history]\n\n${agentBody}` : agentBody;
2391
2516
  const nativeChannelId = isChannel && teamAadGroupId ? `${teamAadGroupId}/${graphChannelId}` : void 0;
2392
- const ctxPayload = (0, openclaw_plugin_sdk_channel_inbound.buildChannelInboundEventContext)({
2517
+ const boundIngress = await resolveMSTeamsSenderAccess({
2518
+ cfg,
2519
+ activity,
2520
+ hasControlCommand: admission.isControlCommand,
2521
+ conversationThreadId: facts.threadId,
2522
+ contextBinding: {
2523
+ agentId: route.agentId,
2524
+ sessionKey: route.sessionKey,
2525
+ ...activity.id ? { messageId: activity.id } : {},
2526
+ ...nativeChannelId ? { nativeChannelId } : {},
2527
+ inboundEventKind: "user_request"
2528
+ }
2529
+ });
2530
+ const ctxPayload = core.channel.inbound.buildContext({
2531
+ channelIngress: boundIngress.channelIngress,
2393
2532
  channel: "msteams",
2394
2533
  contextVisibility: contextVisibilityMode,
2395
2534
  supplemental: { quote: quoteInfo ? {
@@ -2422,6 +2561,8 @@ async function dispatchMSTeamsInboundTurn(params) {
2422
2561
  },
2423
2562
  reply: {
2424
2563
  to: teamsTo,
2564
+ originatingTo: `conversation:${conversationId}`,
2565
+ messageThreadId: facts.threadId ?? void 0,
2425
2566
  replyToId: activity.replyToId ?? void 0,
2426
2567
  nativeChannelId
2427
2568
  },
@@ -3660,6 +3801,24 @@ async function runMSTeamsFileConsentInvokeHandler(context, log) {
3660
3801
  }
3661
3802
  }
3662
3803
  //#endregion
3804
+ //#region extensions/msteams/src/monitor-status.ts
3805
+ function publishMSTeamsBlocked(statusSink, lastError) {
3806
+ statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelBlockedPatch)(lastError, { running: true }));
3807
+ }
3808
+ function publishMSTeamsReady(statusSink, now = Date.now()) {
3809
+ statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelReadyPatch)({ lastConnectedAt: now }));
3810
+ }
3811
+ function publishMSTeamsRecovering(statusSink, lastError) {
3812
+ statusSink?.({
3813
+ connected: false,
3814
+ lifecycle: "recovering",
3815
+ lastError
3816
+ });
3817
+ }
3818
+ function publishMSTeamsStopped(statusSink) {
3819
+ statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelStoppedPatch)());
3820
+ }
3821
+ //#endregion
3663
3822
  //#region extensions/msteams/src/msteams-ingress.ts
3664
3823
  const MSTEAMS_INGRESS_VERSION = 1;
3665
3824
  const MSTEAMS_INGRESS_DRAIN_INTERVAL_MS = 500;
@@ -3695,9 +3854,6 @@ function parseClaimedActivity(payload, claimedId) {
3695
3854
  if (facts.eventId !== claimedId) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams activity id changed after durable admission.");
3696
3855
  return parsed;
3697
3856
  }
3698
- function errorText(error) {
3699
- return error instanceof Error ? error.message : String(error);
3700
- }
3701
3857
  function createMSTeamsIngress(options) {
3702
3858
  const queue = options.queue ?? require_runtime.getMSTeamsRuntime().state.openChannelIngressQueue({ accountId: options.accountId });
3703
3859
  const liveContexts = /* @__PURE__ */ new Map();
@@ -3748,13 +3904,13 @@ function createMSTeamsIngress(options) {
3748
3904
  };
3749
3905
  return require_setup_surface.classifyMSTeamsSendError(error).kind === "auth" ? {
3750
3906
  reason: "authentication-failed",
3751
- message: errorText(error)
3907
+ message: (0, openclaw_plugin_sdk_error_runtime.formatErrorMessage)(error)
3752
3908
  } : null;
3753
3909
  },
3754
3910
  onLog: (message) => options.runtime.error?.(`msteams: ${message}`)
3755
3911
  },
3756
3912
  createStoppedError: () => /* @__PURE__ */ new Error("Microsoft Teams ingress stopped."),
3757
- onError: (error) => options.runtime.error?.(`msteams ingress drain failed: ${errorText(error)}`)
3913
+ onError: (error) => options.runtime.error?.(`msteams ingress drain failed: ${(0, openclaw_plugin_sdk_error_runtime.formatErrorMessage)(error)}`)
3758
3914
  });
3759
3915
  let stopTask;
3760
3916
  return {
@@ -3820,6 +3976,7 @@ async function monitorMSTeamsProvider(opts) {
3820
3976
  let msteamsCfg = cfg.channels?.msteams;
3821
3977
  if (!msteamsCfg?.enabled) {
3822
3978
  log.debug?.("msteams provider disabled");
3979
+ publishMSTeamsBlocked(opts.statusSink, "Microsoft Teams provider is disabled");
3823
3980
  return {
3824
3981
  app: null,
3825
3982
  shutdown: async () => {}
@@ -3828,6 +3985,7 @@ async function monitorMSTeamsProvider(opts) {
3828
3985
  const creds = require_resolve_allowlist.resolveMSTeamsCredentials(msteamsCfg);
3829
3986
  if (!creds) {
3830
3987
  log.error("msteams credentials not configured");
3988
+ publishMSTeamsBlocked(opts.statusSink, "Microsoft Teams credentials are not configured");
3831
3989
  return {
3832
3990
  app: null,
3833
3991
  shutdown: async () => {}
@@ -3922,9 +4080,10 @@ async function monitorMSTeamsProvider(opts) {
3922
4080
  };
3923
4081
  const port = msteamsCfg.webhook?.port ?? 3978;
3924
4082
  const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "msteams");
3925
- const MB = 1024 * 1024;
3926
- const agentDefaults = cfg.agents?.defaults;
3927
- const mediaMaxBytes = typeof agentDefaults?.mediaMaxMb === "number" && agentDefaults.mediaMaxMb > 0 ? Math.floor(agentDefaults.mediaMaxMb * MB) : 8 * MB;
4083
+ const mediaMaxBytes = (0, openclaw_plugin_sdk_media_runtime.resolveChannelMediaMaxBytes)({
4084
+ cfg,
4085
+ resolveChannelLimitMb: ({ cfg: channelCfg }) => channelCfg.channels?.msteams?.mediaMaxMb
4086
+ }) ?? 8 * 1024 * 1024;
3928
4087
  const conversationStore = opts.conversationStore ?? require_polls.createMSTeamsConversationStoreState();
3929
4088
  const pollStore = opts.pollStore ?? require_polls.createMSTeamsPollStoreState();
3930
4089
  log.info(`starting provider (port ${port})`);
@@ -4171,17 +4330,21 @@ async function monitorMSTeamsProvider(opts) {
4171
4330
  });
4172
4331
  await app.initialize();
4173
4332
  ingress.start();
4333
+ const privateQaRuntime = require_resolve_allowlist.resolveMSTeamsPrivateQaRuntime();
4174
4334
  const httpServer = await new Promise((resolve, reject) => {
4175
- const server = expressApp.listen(port, (err) => err ? reject(err) : resolve(server));
4335
+ const onListen = (err) => err ? reject(err) : resolve(server);
4336
+ const server = privateQaRuntime ? expressApp.listen(port, privateQaRuntime.listenHost, onListen) : expressApp.listen(port, onListen);
4176
4337
  }).catch(async (err) => {
4177
4338
  log.error("msteams server error", { error: require_setup_surface.formatUnknownError(err) });
4178
4339
  await ingress.stop();
4179
4340
  throw err;
4180
4341
  });
4181
4342
  log.info(`msteams provider started on port ${port}`);
4343
+ publishMSTeamsReady(opts.statusSink);
4182
4344
  applyMSTeamsWebhookTimeouts(httpServer);
4183
4345
  httpServer.on("error", (err) => {
4184
4346
  log.error("msteams server error", { error: require_setup_surface.formatUnknownError(err) });
4347
+ publishMSTeamsRecovering(opts.statusSink, require_setup_surface.formatUnknownError(err));
4185
4348
  });
4186
4349
  const shutdown = async () => {
4187
4350
  log.info("shutting down msteams provider");
@@ -4192,6 +4355,7 @@ async function monitorMSTeamsProvider(opts) {
4192
4355
  });
4193
4356
  });
4194
4357
  await ingress.stop();
4358
+ publishMSTeamsStopped(opts.statusSink);
4195
4359
  };
4196
4360
  await (0, openclaw_plugin_sdk_channel_outbound.keepHttpServerTaskAlive)({
4197
4361
  server: httpServer,