@openclaw/msteams 2026.9.1 → 2026.9.3

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.
@@ -2,14 +2,14 @@ const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
2
2
  const require_runtime = require("./runtime-gZTAuFux.cjs");
3
3
  require("./runtime-api.cjs");
4
4
  const require_resolve_allowlist = require("./resolve-allowlist-CoKmLXYe.cjs");
5
- const require_channel = require("./channel-CqRcu6kk.cjs");
5
+ const require_channel = require("./channel-Cdm6-HsU.cjs");
6
6
  const require_thread_session = require("./thread-session-p8FNgohv.cjs");
7
- const require_channel_setup = require("./channel.setup-D2YO3F4O.cjs");
8
- const require_polls = require("./polls-P86qBpeV.cjs");
9
- const require_sso_token_store = require("./sso-token-store-CNbRhz-A.cjs");
10
- const require_approval_card = require("./approval-card-ClrTjCsL.cjs");
11
- const require_messenger = require("./messenger-BvR2flEt.cjs");
12
- const require_reaction_types = require("./reaction-types-DcJBoyFP.cjs");
7
+ const require_channel_setup = require("./channel.setup-ChwkwdLk.cjs");
8
+ const require_polls = require("./polls-BBdZPx_R.cjs");
9
+ const require_sso_token_store = require("./sso-token-store-DTeYxubp.cjs");
10
+ const require_approval_card = require("./approval-card-CPNH_q3P.cjs");
11
+ const require_messenger = require("./messenger-DbWgzI01.cjs");
12
+ const require_reaction_types = require("./reaction-types-zP91Ncwk.cjs");
13
13
  let openclaw_plugin_sdk_channel_outbound = require("openclaw/plugin-sdk/channel-outbound");
14
14
  let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
15
15
  let openclaw_plugin_sdk_allow_from = require("openclaw/plugin-sdk/allow-from");
@@ -22,18 +22,19 @@ let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text
22
22
  let openclaw_plugin_sdk_routing = require("openclaw/plugin-sdk/routing");
23
23
  let node_crypto = require("node:crypto");
24
24
  node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
25
- let openclaw_plugin_sdk_collection_runtime = require("openclaw/plugin-sdk/collection-runtime");
26
25
  let openclaw_plugin_sdk_error_runtime = require("openclaw/plugin-sdk/error-runtime");
27
26
  let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
28
27
  let openclaw_plugin_sdk_approval_gateway_runtime = require("openclaw/plugin-sdk/approval-gateway-runtime");
29
28
  let openclaw_plugin_sdk_channel_ingress_runtime = require("openclaw/plugin-sdk/channel-ingress-runtime");
30
29
  let openclaw_plugin_sdk_context_visibility_runtime = require("openclaw/plugin-sdk/context-visibility-runtime");
31
30
  let openclaw_plugin_sdk_reply_history = require("openclaw/plugin-sdk/reply-history");
31
+ let openclaw_plugin_sdk_runtime_config_snapshot = require("openclaw/plugin-sdk/runtime-config-snapshot");
32
32
  let openclaw_plugin_sdk_agent_runtime = require("openclaw/plugin-sdk/agent-runtime");
33
33
  let openclaw_plugin_sdk_extension_shared = require("openclaw/plugin-sdk/extension-shared");
34
34
  let openclaw_plugin_sdk_plugin_runtime = require("openclaw/plugin-sdk/plugin-runtime");
35
35
  let openclaw_plugin_sdk_dedupe_runtime = require("openclaw/plugin-sdk/dedupe-runtime");
36
36
  let openclaw_plugin_sdk_plugin_state_runtime = require("openclaw/plugin-sdk/plugin-state-runtime");
37
+ let openclaw_plugin_sdk_collection_runtime = require("openclaw/plugin-sdk/collection-runtime");
37
38
  let openclaw_plugin_sdk_gateway_runtime = require("openclaw/plugin-sdk/gateway-runtime");
38
39
  let openclaw_plugin_sdk_channel_pairing = require("openclaw/plugin-sdk/channel-pairing");
39
40
  let openclaw_plugin_sdk_runtime_group_policy = require("openclaw/plugin-sdk/runtime-group-policy");
@@ -187,7 +188,7 @@ async function maybeHandleMSTeamsApprovalCardSubmit(params) {
187
188
  ignored("missing card token");
188
189
  return true;
189
190
  }
190
- const binding = require_approval_card.getMSTeamsApprovalCardBinding(token);
191
+ const binding = require_approval_card.msTeamsApprovalControls.get(token);
191
192
  if (!binding) {
192
193
  ignored("unknown or expired card token");
193
194
  return true;
@@ -219,15 +220,8 @@ async function maybeHandleMSTeamsApprovalCardSubmit(params) {
219
220
  ignored(`unauthorized actor ${senderId || "unknown"}`);
220
221
  return true;
221
222
  }
222
- const claim = require_approval_card.claimMSTeamsApprovalCardBinding(token);
223
- if (claim.kind !== "claimed") {
224
- ignored(claim.kind === "in-flight" ? "card token resolve already in flight" : "card token already consumed");
225
- return true;
226
- }
227
- const consumed = claim.binding;
228
- let result;
229
- try {
230
- result = await (0, openclaw_plugin_sdk_approval_gateway_runtime.resolveApprovalOverGateway)({
223
+ const outcome = await require_approval_card.msTeamsApprovalControls.settle(token, async (consumed) => {
224
+ const result = await (0, openclaw_plugin_sdk_approval_gateway_runtime.resolveApprovalOverGateway)({
231
225
  cfg: deps.cfg,
232
226
  approvalId: consumed.approvalId,
233
227
  approvalKind: consumed.approvalKind,
@@ -244,16 +238,13 @@ async function maybeHandleMSTeamsApprovalCardSubmit(params) {
244
238
  content: require_approval_card.buildMSTeamsCanonicalApprovalTerminalCard(result)
245
239
  }]
246
240
  });
247
- } catch (error) {
248
- if ((0, openclaw_plugin_sdk_error_runtime.isApprovalNotFoundError)(error)) {
249
- require_approval_card.completeMSTeamsApprovalCardBinding(token);
250
- ignored(`approval expired or no longer exists id=${consumed.approvalId}`);
251
- return true;
252
- }
253
- require_approval_card.releaseMSTeamsApprovalCardBinding(token);
254
- throw error;
241
+ return result;
242
+ });
243
+ if (outcome.kind !== "settled") {
244
+ ignored(outcome.kind === "missing" ? "card token already consumed" : outcome.kind === "in-flight" ? "card token resolve already in flight" : `approval expired or no longer exists id=${outcome.binding.approvalId}`);
245
+ return true;
255
246
  }
256
- require_approval_card.completeMSTeamsApprovalCardBinding(token);
247
+ const { binding: consumed, result } = outcome;
257
248
  deps.log.info("msteams approval resolved", {
258
249
  approvalId: consumed.approvalId,
259
250
  approvalKind: consumed.approvalKind,
@@ -1747,7 +1738,7 @@ function isStreamCancelledError(err) {
1747
1738
  * Streaming is enabled for personal (DM) conversations only; group/channel
1748
1739
  * messages fall through to block delivery.
1749
1740
  *
1750
- * Streaming modes (resolved from `cfg.channels.msteams.streaming.preview`):
1741
+ * Streaming modes (resolved from `cfg.channels.msteams.streaming.mode`):
1751
1742
  * - "partial" (default): per-token streaming via `stream.emit(text)`. Each
1752
1743
  * chunk goes onto the live preview card in Teams.
1753
1744
  * - "progress": no per-token streaming; the preview card carries an
@@ -1760,39 +1751,35 @@ function isStreamCancelledError(err) {
1760
1751
  function createTeamsReplyStreamController(params) {
1761
1752
  const isPersonal = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(params.conversationType) === "personal";
1762
1753
  const streamMode = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelPreviewStreamMode)(params.msteamsConfig, "partial");
1763
- const shouldUseNativeStream = params.allowProviderPreview && isPersonal && (streamMode === "partial" || streamMode === "progress");
1764
- const shouldStreamPreviewToolProgress = streamMode === "progress" && (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingPreviewToolProgress)(params.msteamsConfig, true, streamMode);
1765
- const stream = shouldUseNativeStream ? params.context.stream : void 0;
1754
+ const stream = params.allowProviderPreview && isPersonal && (streamMode === "partial" || streamMode === "progress") ? params.context.stream : void 0;
1766
1755
  let tokensEmitted = false;
1767
1756
  let nativeDispatchStarted = false;
1768
1757
  let nativeDeliveryClaimed = false;
1769
1758
  let streamFinalizationPending = false;
1770
1759
  let canceledLocally = false;
1771
1760
  let streamFailed = false;
1772
- let lastInformativeText = "";
1773
- let progressLines = [];
1774
- let latestPlan;
1775
- let latestPlanExplanation;
1776
1761
  let pendingFinalPayload;
1777
1762
  let emittedText = "";
1778
1763
  let acknowledgedText = "";
1764
+ let acknowledgedLogicalText = "";
1779
1765
  let acknowledgedStreamId;
1780
1766
  let replacementFinalPending = false;
1781
1767
  let replacementEmitFailed = false;
1782
1768
  let replacementSettlementPending = false;
1783
1769
  let replacementTextAwaitingAcknowledgement;
1784
1770
  let deferredReplacementEntries = [];
1785
- let finalMetadataQueued = false;
1771
+ let queuedFinalActivity;
1786
1772
  let failedSegmentFallbackPrepared = false;
1787
1773
  const streamEvents = stream?.events;
1788
1774
  let streamChunkSubscription;
1789
1775
  if (typeof streamEvents?.on === "function" && typeof streamEvents.off === "function") streamChunkSubscription = streamEvents.on("chunk", (activity) => {
1790
1776
  const replacementAcknowledgementPending = replacementTextAwaitingAcknowledgement !== void 0;
1791
- const replacementAcknowledgement = typeof activity.text === "string" && replacementAcknowledgementPending && activity.text === replacementTextAwaitingAcknowledgement;
1792
- 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;
1777
+ const replacementAcknowledgement = typeof activity.text === "string" && replacementAcknowledgementPending && activity.text === replacementTextAwaitingAcknowledgement?.text;
1778
+ if (activity.type !== "typing" || activity.channelData?.streamType !== "streaming" || !activity.id || !activity.text || acknowledgedStreamId !== void 0 && activity.id !== acknowledgedStreamId || (replacementAcknowledgementPending ? !replacementAcknowledgement : !activity.text.startsWith(acknowledgedText)) || !replacementAcknowledgement && !emittedText.startsWith(activity.text)) return;
1793
1779
  acknowledgedStreamId = activity.id;
1794
1780
  acknowledgedText = activity.text;
1795
- if (activity.text === replacementTextAwaitingAcknowledgement) replacementTextAwaitingAcknowledgement = void 0;
1781
+ acknowledgedLogicalText = replacementAcknowledgement ? replacementTextAwaitingAcknowledgement.logicalText : activity.text;
1782
+ if (replacementAcknowledgement) replacementTextAwaitingAcknowledgement = void 0;
1796
1783
  });
1797
1784
  const wasCanceled = () => canceledLocally || Boolean(stream?.canceled);
1798
1785
  const releaseStreamChunkSubscription = () => {
@@ -1809,8 +1796,8 @@ function createTeamsReplyStreamController(params) {
1809
1796
  };
1810
1797
  };
1811
1798
  const fallbackPayloadAfterAcknowledgedText = (payload) => {
1812
- if (!acknowledgedText || typeof payload.text !== "string" || !payload.text.startsWith(acknowledgedText)) return payload;
1813
- const remainingText = payload.text.slice(acknowledgedText.length);
1799
+ if (!acknowledgedLogicalText || typeof payload.text !== "string" || !payload.text.startsWith(acknowledgedLogicalText)) return payload;
1800
+ const remainingText = payload.text.slice(acknowledgedLogicalText.length);
1814
1801
  const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
1815
1802
  if (!remainingText && !hasMedia) return;
1816
1803
  return {
@@ -1826,15 +1813,7 @@ function createTeamsReplyStreamController(params) {
1826
1813
  } : payload;
1827
1814
  };
1828
1815
  const finalStreamActivity = (text) => ({
1829
- type: "message",
1830
- ...text ? { text } : {},
1831
- entities: [{
1832
- type: "https://schema.org/Message",
1833
- "@type": "Message",
1834
- "@context": "https://schema.org",
1835
- "@id": "",
1836
- additionalType: ["AIGeneratedContent"]
1837
- }],
1816
+ ...require_messenger.buildMSTeamsMessageActivity(text === void 0 ? void 0 : require_messenger.formatMSTeamsMarkdown(text, params.tableMode ?? "code")),
1838
1817
  channelData: params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {}
1839
1818
  });
1840
1819
  const deferredReplacementLogicalContent = () => {
@@ -1854,34 +1833,23 @@ function createTeamsReplyStreamController(params) {
1854
1833
  deferredReplacementEntries = [];
1855
1834
  return payloads;
1856
1835
  };
1857
- /**
1858
- * Render the current informative status line into the streaming card. Pulls
1859
- * the rotating "Thinking..." label from msteams config (or the plugin-sdk
1860
- * default) and prepends collected tool-progress lines when configured.
1861
- */
1862
- const renderInformativeUpdate = () => {
1863
- if (!stream || wasCanceled() || streamFinalizationPending) return;
1864
- const informativeText = (0, openclaw_plugin_sdk_channel_outbound.formatChannelProgressDraftText)({
1865
- entry: params.msteamsConfig,
1866
- lines: shouldStreamPreviewToolProgress ? progressLines : [],
1867
- seed: params.progressSeed,
1868
- bullet: "-",
1869
- narration: latestPlanExplanation,
1870
- plan: latestPlan
1871
- });
1872
- if (!informativeText || informativeText === lastInformativeText) return;
1873
- lastInformativeText = informativeText;
1874
- try {
1875
- stream.update(informativeText);
1876
- } catch (err) {
1877
- if (isStreamCancelledError(err)) {
1878
- canceledLocally = true;
1879
- return;
1836
+ const progressDraft = (0, openclaw_plugin_sdk_channel_outbound.createChannelProgressDraftCompositor)({
1837
+ entry: params.msteamsConfig,
1838
+ mode: streamMode,
1839
+ active: Boolean(stream) && streamMode === "progress",
1840
+ seed: params.progressSeed ?? "msteams",
1841
+ update: (text) => {
1842
+ if (!stream || wasCanceled() || streamFinalizationPending) return false;
1843
+ try {
1844
+ stream.update(text.replace(/^• /gmu, "- "));
1845
+ return true;
1846
+ } catch (err) {
1847
+ if (isStreamCancelledError(err)) canceledLocally = true;
1848
+ else params.log?.debug?.(`stream informative update failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
1849
+ return false;
1880
1850
  }
1881
- params.log?.debug?.(`stream informative update failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
1882
1851
  }
1883
- };
1884
- const progressDraftGate = (0, openclaw_plugin_sdk_channel_outbound.createChannelProgressDraftGate)({ onStart: renderInformativeUpdate });
1852
+ });
1885
1853
  return {
1886
1854
  async onReplyStart() {},
1887
1855
  onPartialReply(payload) {
@@ -1890,7 +1858,7 @@ function createTeamsReplyStreamController(params) {
1890
1858
  pendingFinalPayload = { text: payload.text };
1891
1859
  return;
1892
1860
  }
1893
- if (streamFinalizationPending) return;
1861
+ if (streamFinalizationPending || nativeDeliveryClaimed) return;
1894
1862
  const fullText = payload.text;
1895
1863
  let prefixLength = 0;
1896
1864
  while (prefixLength < emittedText.length && prefixLength < fullText.length && emittedText[prefixLength] === fullText[prefixLength]) prefixLength += 1;
@@ -1923,45 +1891,27 @@ function createTeamsReplyStreamController(params) {
1923
1891
  params.log?.warn?.(`msteams stream emit failed, falling back to block delivery: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
1924
1892
  }
1925
1893
  },
1926
- /**
1927
- * Note that the agent is working — bumps the progress-draft gate so the
1928
- * informative status starts (or refreshes) on the next render. Called
1929
- * from the reply-dispatcher's typing callbacks.
1930
- */
1931
1894
  async noteProgressWork(options) {
1932
- if (!stream || streamMode !== "progress") return;
1933
1895
  if (options?.toolName !== void 0 && !(0, openclaw_plugin_sdk_channel_outbound.isChannelProgressDraftWorkToolName)(options.toolName)) return;
1934
- const hadStarted = progressDraftGate.hasStarted;
1935
- const progressActive = await progressDraftGate.noteWork();
1936
- if ((hadStarted || progressActive) && progressDraftGate.hasStarted) renderInformativeUpdate();
1896
+ await progressDraft.noteActivity();
1937
1897
  },
1938
- /**
1939
- * Append a tool-progress line (e.g. a tool name being invoked) into the
1940
- * preview card's informative status. Only takes effect in "progress" mode
1941
- * with `streaming.previewToolProgress` enabled in config.
1942
- */
1943
1898
  async pushProgressLine(line, options) {
1944
- if (!stream || streamMode !== "progress") return;
1945
- if (options?.toolName !== void 0 && !(0, openclaw_plugin_sdk_channel_outbound.isChannelProgressDraftWorkToolName)(options.toolName)) return;
1946
- if (shouldStreamPreviewToolProgress) {
1947
- const normalized = (0, openclaw_plugin_sdk_channel_outbound.normalizeChannelProgressDraftLineIdentity)(line);
1948
- if (normalized) progressLines = (0, openclaw_plugin_sdk_channel_outbound.mergeChannelProgressDraftLine)(progressLines, typeof line === "object" && line !== void 0 ? line : normalized, { maxLines: (0, openclaw_plugin_sdk_channel_outbound.resolveChannelProgressDraftMaxLines)(params.msteamsConfig) });
1949
- }
1950
- const hadStarted = progressDraftGate.hasStarted;
1951
- const progressActive = await progressDraftGate.noteWork();
1952
- if ((hadStarted || progressActive) && progressDraftGate.hasStarted) renderInformativeUpdate();
1899
+ await progressDraft.pushToolProgress(line, options);
1900
+ },
1901
+ async pushReasoningProgress(text, options) {
1902
+ await progressDraft.pushReasoningProgress(text, options);
1903
+ },
1904
+ resetReasoningProgress: () => progressDraft.resetReasoningProgress(),
1905
+ async pushApprovalEvent(payload) {
1906
+ await progressDraft.pushApprovalEvent(payload);
1953
1907
  },
1954
1908
  async pushPlanProgress(steps, options) {
1955
- if (!stream || streamMode !== "progress" || streamFinalizationPending) return;
1956
- latestPlan = steps?.length ? steps.map((entry) => ({ ...entry })) : void 0;
1957
- latestPlanExplanation = options?.explanation?.replace(/\s+/g, " ").trim() || void 0;
1958
- const hadStarted = progressDraftGate.hasStarted;
1959
- await progressDraftGate.startNow();
1960
- if (hadStarted && progressDraftGate.hasStarted) renderInformativeUpdate();
1909
+ await progressDraft.pushPlanProgress(steps, options);
1961
1910
  },
1962
1911
  preparePayload(payload) {
1963
1912
  if (!stream) return payload;
1964
1913
  if (wasCanceled()) return;
1914
+ if (payload.text) progressDraft.markFinalReplyStarted();
1965
1915
  if (replacementSettlementPending) {
1966
1916
  if (!replacementFinalPending) {
1967
1917
  deferredReplacementEntries.push({
@@ -1984,9 +1934,13 @@ function createTeamsReplyStreamController(params) {
1984
1934
  });
1985
1935
  pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
1986
1936
  try {
1987
- replacementTextAwaitingAcknowledgement = payload.text;
1988
- stream.emit(finalStreamActivity(payload.text));
1989
- finalMetadataQueued = true;
1937
+ const activity = finalStreamActivity(payload.text);
1938
+ replacementTextAwaitingAcknowledgement = {
1939
+ text: activity.text,
1940
+ logicalText: payload.text
1941
+ };
1942
+ stream.emit(activity);
1943
+ queuedFinalActivity = activity;
1990
1944
  emittedText = payload.text;
1991
1945
  tokensEmitted = false;
1992
1946
  return;
@@ -2005,31 +1959,17 @@ function createTeamsReplyStreamController(params) {
2005
1959
  return;
2006
1960
  }
2007
1961
  }
2008
- if (tokensEmitted && !streamFailed) {
2009
- const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
2010
- pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
2011
- streamFinalizationPending = true;
2012
- tokensEmitted = false;
2013
- return hasMedia ? {
2014
- ...payload,
2015
- text: void 0
2016
- } : void 0;
2017
- }
2018
1962
  if (streamFailed) {
2019
1963
  const fallback = failedSegmentFallbackPrepared ? payload : fallbackPayloadAfterAcknowledgedText(payload);
2020
1964
  failedSegmentFallbackPrepared = true;
2021
1965
  pendingFinalPayload = void 0;
2022
1966
  return fallback;
2023
1967
  }
2024
- if (streamMode === "progress" && payload.text) try {
1968
+ if (streamMode === "progress" && payload.text && !nativeDispatchStarted) try {
2025
1969
  stream.emit(payload.text);
1970
+ emittedText = payload.text;
2026
1971
  nativeDispatchStarted = true;
2027
- pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
2028
- streamFinalizationPending = true;
2029
- return Boolean(payload.mediaUrl || payload.mediaUrls?.length) ? {
2030
- ...payload,
2031
- text: void 0
2032
- } : void 0;
1972
+ tokensEmitted = true;
2033
1973
  } catch (err) {
2034
1974
  if (isStreamCancelledError(err)) {
2035
1975
  canceledLocally = true;
@@ -2037,6 +1977,16 @@ function createTeamsReplyStreamController(params) {
2037
1977
  }
2038
1978
  params.log?.debug?.(`progress-mode finalize failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
2039
1979
  }
1980
+ if (tokensEmitted) {
1981
+ const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
1982
+ pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
1983
+ streamFinalizationPending = true;
1984
+ tokensEmitted = false;
1985
+ return hasMedia ? {
1986
+ ...payload,
1987
+ text: void 0
1988
+ } : void 0;
1989
+ }
2040
1990
  return payload;
2041
1991
  },
2042
1992
  claimNativeDelivery() {
@@ -2045,7 +1995,7 @@ function createTeamsReplyStreamController(params) {
2045
1995
  return true;
2046
1996
  },
2047
1997
  async finalize() {
2048
- progressDraftGate.cancel();
1998
+ progressDraft.markFinalReplyStarted();
2049
1999
  if (!stream || !nativeDispatchStarted) {
2050
2000
  releaseStreamChunkSubscription();
2051
2001
  return { visibleReplySent: false };
@@ -2065,15 +2015,34 @@ function createTeamsReplyStreamController(params) {
2065
2015
  kind: "replacement",
2066
2016
  payload: pendingFinalPayload
2067
2017
  });
2068
- replacementTextAwaitingAcknowledgement = pendingFinalPayload.text;
2018
+ const activity = finalStreamActivity(pendingFinalPayload.text);
2019
+ replacementTextAwaitingAcknowledgement = {
2020
+ text: activity.text,
2021
+ logicalText: pendingFinalPayload.text
2022
+ };
2069
2023
  logicalContent = deferredReplacementLogicalContent();
2070
- stream.emit(finalStreamActivity(pendingFinalPayload.text));
2071
- finalMetadataQueued = true;
2024
+ stream.emit(activity);
2025
+ queuedFinalActivity = activity;
2072
2026
  emittedText = pendingFinalPayload.text;
2073
2027
  }
2074
2028
  logicalContent ??= replacementSettlementPending ? deferredReplacementLogicalContent() : void 0;
2075
- const content = pendingFinalPayload?.text ?? (emittedText || void 0);
2076
- if (!finalMetadataQueued) stream.emit(finalStreamActivity());
2029
+ const logicalText = pendingFinalPayload?.text ?? (emittedText || void 0);
2030
+ const finalActivity = queuedFinalActivity ?? finalStreamActivity(logicalText);
2031
+ const content = finalActivity.text;
2032
+ logicalContent ??= content !== logicalText ? logicalText : void 0;
2033
+ if (!queuedFinalActivity) {
2034
+ if (content !== void 0 && content !== emittedText) {
2035
+ stream.clearText();
2036
+ replacementTextAwaitingAcknowledgement = {
2037
+ text: content,
2038
+ logicalText
2039
+ };
2040
+ stream.emit(finalActivity);
2041
+ } else stream.emit({
2042
+ ...finalActivity,
2043
+ text: void 0
2044
+ });
2045
+ }
2077
2046
  const result = await stream.close();
2078
2047
  streamFinalizationPending = false;
2079
2048
  if (!result) {
@@ -2126,7 +2095,8 @@ function createTeamsReplyStreamController(params) {
2126
2095
  ...postNativePayloads.length > 0 ? { postNativePayloads } : {}
2127
2096
  };
2128
2097
  } finally {
2129
- finalMetadataQueued = false;
2098
+ failedSegmentFallbackPrepared = true;
2099
+ queuedFinalActivity = void 0;
2130
2100
  replacementEmitFailed = false;
2131
2101
  replacementFinalPending = false;
2132
2102
  replacementSettlementPending = false;
@@ -2219,6 +2189,7 @@ function createMSTeamsReplyDispatcher(params) {
2219
2189
  const feedbackLoopEnabled = params.cfg.channels?.msteams?.feedbackEnabled !== false;
2220
2190
  const hookRunner = (0, openclaw_plugin_sdk_plugin_runtime.getGlobalHookRunner)();
2221
2191
  const streamController = createTeamsReplyStreamController({
2192
+ tableMode,
2222
2193
  allowProviderPreview: !((hookRunner?.hasHooks("reply_payload_sending") ?? false) || (hookRunner?.hasHooks("message_sending") ?? false)),
2223
2194
  conversationType,
2224
2195
  context: params.context,
@@ -2234,6 +2205,8 @@ function createMSTeamsReplyDispatcher(params) {
2234
2205
  const blockStreamingEnabled = blockStreamingResolved ?? false;
2235
2206
  const typingIndicatorEnabled = typeof msteamsCfg?.typingIndicator === "boolean" ? msteamsCfg.typingIndicator : true;
2236
2207
  const pendingDeliveries = [];
2208
+ let pendingSettlement;
2209
+ const findPendingNativeDelivery = () => pendingDeliveries.find((candidate) => candidate.native && !candidate.nativeSettled);
2237
2210
  const joinAcceptedContents = (contents) => contents.filter((content) => Boolean(content)).join("\n");
2238
2211
  const sendMessages = async (messages) => {
2239
2212
  return require_messenger.sendMSTeamsMessages({
@@ -2376,9 +2349,11 @@ function createMSTeamsReplyDispatcher(params) {
2376
2349
  const delivery = {
2377
2350
  observeMessageSent: true,
2378
2351
  deliver: async (payload) => {
2352
+ if (pendingSettlement) await pendingSettlement;
2379
2353
  const preparedPayload = streamController.preparePayload(payload);
2380
2354
  const native = streamController.claimNativeDelivery();
2381
- const messages = preparedPayload ? renderReplyPayload(preparedPayload) : [];
2355
+ if (preparedPayload && !native && findPendingNativeDelivery()) await settleDelivery();
2356
+ const messages = preparedPayload && !streamController.wasCanceled() ? renderReplyPayload(preparedPayload) : [];
2382
2357
  if (!native && messages.length === 0) return {
2383
2358
  visibleReplySent: false,
2384
2359
  suppression: { reason: "no_visible_result" }
@@ -2404,9 +2379,9 @@ function createMSTeamsReplyDispatcher(params) {
2404
2379
  });
2405
2380
  }
2406
2381
  };
2407
- const settleDelivery = async () => {
2382
+ const settleDelivery = () => pendingSettlement ??= Promise.resolve().then(async () => {
2408
2383
  await flushPendingMessages();
2409
- const nativeDelivery = pendingDeliveries.find((candidate) => candidate.native && !candidate.nativeSettled);
2384
+ const nativeDelivery = findPendingNativeDelivery();
2410
2385
  if (!nativeDelivery) {
2411
2386
  await streamController.finalize();
2412
2387
  return;
@@ -2440,25 +2415,18 @@ function createMSTeamsReplyDispatcher(params) {
2440
2415
  } catch (error) {
2441
2416
  params.log.warn?.("failed to record sent Teams message id", { error: require_channel_setup.formatUnknownError(error) });
2442
2417
  }
2443
- };
2444
- const previewToolProgressEnabled = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingPreviewToolProgress)(msteamsCfg, true, teamsStreamMode);
2445
- const suppressDefaultToolProgressMessages = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingSuppressDefaultToolProgressMessages)(msteamsCfg);
2446
- const shouldSuppressDefaultToolProgressMessages = streamController.hasStream() && teamsStreamMode === "progress" && suppressDefaultToolProgressMessages && previewToolProgressEnabled;
2418
+ }).finally(() => {
2419
+ pendingSettlement = void 0;
2420
+ });
2421
+ const shouldSuppressDefaultToolProgressMessages = streamController.hasStream() && teamsStreamMode === "progress";
2447
2422
  const progressCallbacks = streamController.hasStream() ? {
2448
2423
  onReasoningStream: async (payload) => {
2449
2424
  const text = typeof payload?.text === "string" ? payload.text : void 0;
2450
- if (!text) return false;
2451
- if (payload?.isReasoningSnapshot !== true) {
2452
- await streamController.pushProgressLine(text);
2453
- return false;
2454
- }
2455
- await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
2456
- event: "item",
2457
- itemId: "reasoning",
2458
- itemKind: "analysis",
2459
- title: "Reasoning",
2460
- progressText: text
2461
- }));
2425
+ await streamController.pushReasoningProgress(text, { snapshot: payload?.isReasoningSnapshot === true });
2426
+ return false;
2427
+ },
2428
+ onReasoningEnd: () => {
2429
+ streamController.resetReasoningProgress();
2462
2430
  return false;
2463
2431
  },
2464
2432
  onToolStart: async (payload) => {
@@ -2496,15 +2464,14 @@ function createMSTeamsReplyDispatcher(params) {
2496
2464
  return false;
2497
2465
  },
2498
2466
  onApprovalEvent: async (payload) => {
2499
- if (payload?.phase !== "requested") return false;
2500
- await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
2501
- event: "approval",
2502
- phase: payload.phase,
2467
+ await streamController.pushApprovalEvent({
2468
+ ...typeof payload?.phase === "string" ? { phase: payload.phase } : {},
2469
+ ...typeof payload?.approvalId === "string" ? { approvalId: payload.approvalId } : {},
2503
2470
  ...typeof payload?.title === "string" ? { title: payload.title } : {},
2504
2471
  ...typeof payload?.command === "string" ? { command: payload.command } : {},
2505
2472
  ...typeof payload?.reason === "string" ? { reason: payload.reason } : {},
2506
2473
  ...typeof payload?.message === "string" ? { message: payload.message } : {}
2507
- }));
2474
+ });
2508
2475
  return false;
2509
2476
  },
2510
2477
  onCommandOutput: async (payload) => {
@@ -2636,7 +2603,6 @@ async function dispatchMSTeamsInboundTurn(params) {
2636
2603
  allowNameMatching
2637
2604
  }).allowed
2638
2605
  }) : true;
2639
- const bodyForAgent = threadContext ? `[Thread history]\n${threadContext}\n[/Thread history]\n\n${agentBody}` : agentBody;
2640
2606
  const nativeChannelId = isChannel && teamAadGroupId ? `${teamAadGroupId}/${graphChannelId}` : void 0;
2641
2607
  const boundIngress = await resolveMSTeamsSenderAccess({
2642
2608
  cfg,
@@ -2655,13 +2621,25 @@ async function dispatchMSTeamsInboundTurn(params) {
2655
2621
  channelIngress: boundIngress.channelIngress,
2656
2622
  channel: "msteams",
2657
2623
  contextVisibility: contextVisibilityMode,
2658
- supplemental: { quote: quoteInfo ? {
2659
- id: quoteInfo.id ?? activity.replyToId ?? void 0,
2660
- body: quoteBodyFull ?? quoteInfo.body,
2661
- sender: quoteInfo.sender,
2662
- senderAllowed: quoteSenderAllowed,
2663
- isQuote: true
2664
- } : void 0 },
2624
+ supplemental: {
2625
+ channelStructuredContext: threadContext.length ? [{
2626
+ label: "Thread history",
2627
+ source: "msteams",
2628
+ type: "chat_window",
2629
+ sessionTranscriptMode: "preserve",
2630
+ payload: {
2631
+ order: "chronological",
2632
+ messages: threadContext
2633
+ }
2634
+ }] : void 0,
2635
+ quote: quoteInfo ? {
2636
+ id: quoteInfo.id ?? activity.replyToId ?? void 0,
2637
+ body: quoteBodyFull ?? quoteInfo.body,
2638
+ sender: quoteInfo.sender,
2639
+ senderAllowed: quoteSenderAllowed,
2640
+ isQuote: true
2641
+ } : void 0
2642
+ },
2665
2643
  media: await (0, openclaw_plugin_sdk_channel_inbound.toInboundMediaFactsWithMetadata)(inboundMedia),
2666
2644
  messageId: activity.id,
2667
2645
  timestamp: timestamp?.getTime() ?? Date.now(),
@@ -2692,7 +2670,7 @@ async function dispatchMSTeamsInboundTurn(params) {
2692
2670
  },
2693
2671
  message: {
2694
2672
  body: combinedBody,
2695
- bodyForAgent,
2673
+ bodyForAgent: agentBody,
2696
2674
  inboundHistory,
2697
2675
  rawBody,
2698
2676
  commandBody
@@ -2753,7 +2731,7 @@ async function dispatchMSTeamsInboundTurn(params) {
2753
2731
  id: activity.id ?? `${teamsFrom}:${Date.now()}`,
2754
2732
  timestamp: timestamp?.getTime(),
2755
2733
  rawText: rawBody,
2756
- textForAgent: bodyForAgent,
2734
+ textForAgent: agentBody,
2757
2735
  textForCommands: commandBody,
2758
2736
  raw: activity
2759
2737
  }),
@@ -3096,7 +3074,7 @@ async function resolveMSTeamsThreadContext(params) {
3096
3074
  } catch (err) {
3097
3075
  params.log.debug?.("failed to fetch full quoted message text", { error: require_channel_setup.formatUnknownError(err) });
3098
3076
  }
3099
- let threadContext;
3077
+ let threadContext = [];
3100
3078
  const threadParentId = params.routing.threadRootId;
3101
3079
  if (threadParentId && threadParentId !== activity.id && params.isChannel && teamAadGroupId) try {
3102
3080
  const graphToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
@@ -3147,7 +3125,7 @@ async function resolveMSTeamsThreadContext(params) {
3147
3125
  kind: "thread",
3148
3126
  isSenderAllowed: isThreadSenderAllowed
3149
3127
  });
3150
- threadContext = require_reaction_types.formatThreadContext(threadMessages, activity.id) || void 0;
3128
+ threadContext = require_reaction_types.buildThreadContext(threadMessages, activity.id);
3151
3129
  } catch (err) {
3152
3130
  params.log.debug?.("failed to fetch thread history", { error: require_channel_setup.formatUnknownError(err) });
3153
3131
  }
@@ -3175,8 +3153,9 @@ function createMSTeamsMessageHandler(deps) {
3175
3153
  });
3176
3154
  const historyLimit = Math.max(0, msteamsCfg?.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? openclaw_plugin_sdk_reply_history.DEFAULT_GROUP_HISTORY_LIMIT);
3177
3155
  const conversationHistories = /* @__PURE__ */ new Map();
3178
- const inboundDebounceMs = core.channel.debounce.resolveInboundDebounceMs({
3179
- cfg,
3156
+ const readConfig = (0, openclaw_plugin_sdk_runtime_config_snapshot.createRuntimeConfigReader)(cfg);
3157
+ const resolveDebounceMs = () => core.channel.debounce.resolveInboundDebounceMs({
3158
+ cfg: readConfig(),
3180
3159
  channel: "msteams"
3181
3160
  });
3182
3161
  const handleTeamsMessageNow = async (params) => {
@@ -3355,7 +3334,8 @@ function createMSTeamsMessageHandler(deps) {
3355
3334
  });
3356
3335
  };
3357
3336
  const inboundDebouncer = core.channel.debounce.createInboundDebouncer({
3358
- debounceMs: inboundDebounceMs,
3337
+ debounceMs: resolveDebounceMs(),
3338
+ resolveDebounceMs,
3359
3339
  buildKey: (entry) => {
3360
3340
  const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(entry.context.activity.conversation?.id ?? "");
3361
3341
  const senderId = entry.context.activity.from?.aadObjectId ?? entry.context.activity.from?.id ?? "";
@@ -1,5 +1,5 @@
1
1
  const require_runtime = require("./runtime-gZTAuFux.cjs");
2
- const require_polls = require("./polls-P86qBpeV.cjs");
2
+ const require_polls = require("./polls-BBdZPx_R.cjs");
3
3
  let node_crypto = require("node:crypto");
4
4
  //#region extensions/msteams/src/sso-token-store.ts
5
5
  /**
@@ -2,7 +2,23 @@
2
2
  "id": "msteams",
3
3
  "doctorContract": {
4
4
  "configRepair": true,
5
- "stateMigrations": true
5
+ "stateMigrations": [
6
+ {
7
+ "id": "msteams-conversations-json-to-plugin-state"
8
+ },
9
+ {
10
+ "id": "msteams-polls-json-to-plugin-state"
11
+ },
12
+ {
13
+ "id": "msteams-sso-tokens-json-to-plugin-state"
14
+ },
15
+ {
16
+ "id": "msteams-delegated-token-json-to-plugin-state"
17
+ },
18
+ {
19
+ "id": "msteams-feedback-learnings-json-to-plugin-state"
20
+ }
21
+ ]
6
22
  },
7
23
  "name": "Microsoft Teams",
8
24
  "description": "OpenClaw Microsoft Teams channel plugin for bot conversations.",
@@ -751,7 +767,7 @@
751
767
  },
752
768
  "streaming.progress.toolProgress": {
753
769
  "label": "MS Teams Progress Tool Lines",
754
- "help": "Show compact tool/progress lines in progress mode (default: true). Set false to keep only the title until final delivery."
770
+ "help": "Show individual tool activity in progress drafts (default: false). Quiet drafts retain plans, approval requests, failures, and authored progress text."
755
771
  },
756
772
  "streaming.progress.commandText": {
757
773
  "label": "MS Teams Progress Command Text",