@openclaw/msteams 2026.7.2-beta.1 → 2026.7.2-beta.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.
@@ -1,30 +1,28 @@
1
- const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
1
+ const require_delegated_state = require("./delegated-state-DOSCHaAB.cjs");
2
2
  const require_runtime = require("./runtime-gZTAuFux.cjs");
3
3
  require("./runtime-api.cjs");
4
- const require_resolve_allowlist = require("./resolve-allowlist-CyQ57MfH.cjs");
5
- const require_channel = require("./channel-BSyPdpGN.cjs");
6
- const require_setup_surface = require("./setup-surface-BDTPN3dX.cjs");
7
- const require_polls = require("./polls-BhoI4VaN.cjs");
8
- const require_sso_token_store = require("./sso-token-store-CktCz88k.cjs");
9
- const require_messenger = require("./messenger-DtS9gqkV.cjs");
4
+ const require_resolve_allowlist = require("./resolve-allowlist-Dk-1CqBm.cjs");
5
+ const require_channel = require("./channel-oNBYNcJT.cjs");
6
+ const require_setup_surface = require("./setup-surface-BcRVoUY8.cjs");
7
+ const require_polls = require("./polls-CWExq2xZ.cjs");
8
+ const require_sso_token_store = require("./sso-token-store-OSg229re.cjs");
9
+ const require_messenger = require("./messenger-Do4oje4J.cjs");
10
10
  let openclaw_plugin_sdk_allow_from = require("openclaw/plugin-sdk/allow-from");
11
11
  let openclaw_plugin_sdk_channel_outbound = require("openclaw/plugin-sdk/channel-outbound");
12
12
  let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
13
13
  let openclaw_plugin_sdk_media_runtime = require("openclaw/plugin-sdk/media-runtime");
14
- let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
15
14
  let openclaw_plugin_sdk_ssrf_runtime = require("openclaw/plugin-sdk/ssrf-runtime");
16
15
  let openclaw_plugin_sdk_provider_http = require("openclaw/plugin-sdk/provider-http");
17
16
  let openclaw_plugin_sdk_number_runtime = require("openclaw/plugin-sdk/number-runtime");
18
17
  let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text-utility-runtime");
19
- let node_path = require("node:path");
20
- node_path = require_rolldown_runtime.__toESM(node_path, 1);
21
- let openclaw_plugin_sdk_security_runtime = require("openclaw/plugin-sdk/security-runtime");
22
18
  let openclaw_plugin_sdk_routing = require("openclaw/plugin-sdk/routing");
23
19
  let node_crypto = require("node:crypto");
24
- node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
20
+ node_crypto = require_delegated_state.__toESM(node_crypto, 1);
21
+ let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
25
22
  let openclaw_plugin_sdk_channel_ingress_runtime = require("openclaw/plugin-sdk/channel-ingress-runtime");
26
23
  let openclaw_plugin_sdk_context_visibility_runtime = require("openclaw/plugin-sdk/context-visibility-runtime");
27
24
  let openclaw_plugin_sdk_reply_history = require("openclaw/plugin-sdk/reply-history");
25
+ let openclaw_plugin_sdk_agent_runtime = require("openclaw/plugin-sdk/agent-runtime");
28
26
  let openclaw_plugin_sdk_dedupe_runtime = require("openclaw/plugin-sdk/dedupe-runtime");
29
27
  let openclaw_plugin_sdk_collection_runtime = require("openclaw/plugin-sdk/collection-runtime");
30
28
  let openclaw_plugin_sdk_channel_pairing = require("openclaw/plugin-sdk/channel-pairing");
@@ -33,100 +31,24 @@ let openclaw_plugin_sdk_runtime_group_policy = require("openclaw/plugin-sdk/runt
33
31
  let openclaw_plugin_sdk_dangerous_name_runtime = require("openclaw/plugin-sdk/dangerous-name-runtime");
34
32
  let openclaw_plugin_sdk_reply_payload = require("openclaw/plugin-sdk/reply-payload");
35
33
  let openclaw_plugin_sdk_webhook_ingress = require("openclaw/plugin-sdk/webhook-ingress");
36
- //#region extensions/msteams/src/feedback-reflection-prompt.ts
37
- /** Max chars of the thumbed-down response to include in the reflection prompt. */
38
- const MAX_RESPONSE_CHARS = 500;
39
- function buildReflectionPrompt(params) {
40
- const parts = ["A user indicated your previous response wasn't helpful."];
41
- if (params.thumbedDownResponse) {
42
- const truncated = params.thumbedDownResponse.length > MAX_RESPONSE_CHARS ? `${(0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(params.thumbedDownResponse, MAX_RESPONSE_CHARS)}...` : params.thumbedDownResponse;
43
- parts.push(`\nYour response was:\n> ${truncated}`);
44
- }
45
- if (params.userComment) parts.push(`\nUser's comment: "${params.userComment}"`);
46
- parts.push("\nBriefly reflect: what could you improve? Consider tone, length, accuracy, relevance, and specificity. Reply with a single JSON object only, no markdown or prose, using this exact shape:\n{\"learning\":\"...\",\"followUp\":false,\"userMessage\":\"\"}\n- learning: a short internal adjustment note (1-2 sentences) for your future behavior in this conversation.\n- followUp: true only if the user needs a direct follow-up message.\n- userMessage: only the exact user-facing message to send; empty string when followUp is false.");
47
- return parts.join("\n");
48
- }
49
- function parseBooleanLike(value) {
50
- if (typeof value === "boolean") return value;
51
- if (typeof value === "string") {
52
- const normalized = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(value);
53
- if (normalized === "true" || normalized === "yes") return true;
54
- if (normalized === "false" || normalized === "no") return false;
55
- }
56
- }
57
- function parseStructuredReflectionValue(value) {
58
- if (value == null || typeof value !== "object" || Array.isArray(value)) return null;
59
- const candidate = value;
60
- const learning = typeof candidate.learning === "string" ? candidate.learning.trim() : void 0;
61
- if (!learning) return null;
62
- return {
63
- learning,
64
- followUp: parseBooleanLike(candidate.followUp) ?? false,
65
- userMessage: typeof candidate.userMessage === "string" && candidate.userMessage.trim() ? candidate.userMessage.trim() : void 0
66
- };
67
- }
68
- function parseReflectionResponse(text) {
69
- const trimmed = text.trim();
70
- if (!trimmed) return null;
71
- const candidates = [trimmed, ...trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i)?.slice(1, 2) ?? []];
72
- for (const candidateText of candidates) {
73
- const candidate = candidateText.trim();
74
- if (!candidate) continue;
75
- try {
76
- const parsed = parseStructuredReflectionValue(JSON.parse(candidate));
77
- if (parsed) return parsed;
78
- } catch {}
79
- }
80
- return {
81
- learning: trimmed,
82
- followUp: false
83
- };
84
- }
85
- /** Tracks last reflection time per session to enforce cooldown. */
86
- const lastReflectionBySession = /* @__PURE__ */ new Map();
87
- /** Maximum cooldown entries before pruning expired ones. */
88
- const MAX_COOLDOWN_ENTRIES = 500;
34
+ //#region extensions/msteams/src/feedback-reflection-store.ts
89
35
  const LEARNINGS_NAMESPACE = "feedback-learnings";
90
36
  const MAX_LEARNING_ENTRIES = 1e4;
91
37
  function learningStoreKey(storePath, sessionKey) {
92
38
  return node_crypto.default.createHash("sha256").update(`${storePath}\0${sessionKey}`, "utf8").digest("hex");
93
39
  }
94
- function openLearningStore() {
95
- return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
40
+ async function storeSessionLearning(params) {
41
+ const store = require_runtime.getMSTeamsRuntime().state.openKeyedStore({
96
42
  namespace: LEARNINGS_NAMESPACE,
97
43
  maxEntries: MAX_LEARNING_ENTRIES
98
44
  });
99
- }
100
- /** Prune expired cooldown entries to prevent unbounded memory growth. */
101
- function pruneExpiredCooldowns(cooldownMs) {
102
- if (lastReflectionBySession.size <= MAX_COOLDOWN_ENTRIES) return;
103
- const now = Date.now();
104
- for (const [key, time] of lastReflectionBySession) if (now - time >= cooldownMs) lastReflectionBySession.delete(key);
105
- }
106
- /** Check if a reflection is allowed (cooldown not active). */
107
- function isReflectionAllowed(sessionKey, cooldownMs) {
108
- const cooldown = cooldownMs ?? 3e5;
109
- const lastTime = lastReflectionBySession.get(sessionKey);
110
- if (lastTime == null) return true;
111
- return Date.now() - lastTime >= cooldown;
112
- }
113
- /** Record that a reflection was run for a session. */
114
- function recordReflectionTime(sessionKey, cooldownMs) {
115
- lastReflectionBySession.set(sessionKey, Date.now());
116
- pruneExpiredCooldowns(cooldownMs ?? 3e5);
117
- }
118
- /** Store a learning derived from feedback reflection. */
119
- async function storeSessionLearning(params) {
120
- const store = openLearningStore();
121
45
  const key = learningStoreKey(params.storePath, params.sessionKey);
122
- let learnings = (await store.lookup(key))?.learnings ?? [];
123
- learnings.push(params.learning);
124
- if (learnings.length > 10) learnings = learnings.slice(-10);
125
- await store.register(key, {
46
+ if (!store.update) throw new Error("plugin state atomic update is unavailable");
47
+ await store.update(key, (existing) => ({
126
48
  sessionKey: params.sessionKey,
127
- learnings,
49
+ learnings: [...existing?.learnings ?? [], params.learning].slice(-10),
128
50
  updatedAt: Date.now()
129
- });
51
+ }));
130
52
  }
131
53
  //#endregion
132
54
  //#region extensions/msteams/src/feedback-reflection.ts
@@ -143,144 +65,68 @@ function buildFeedbackEvent(params) {
143
65
  conversationId: params.conversationId
144
66
  };
145
67
  }
146
- function buildReflectionContext(params) {
147
- const core = require_runtime.getMSTeamsRuntime();
148
- const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(params.cfg);
149
- const body = core.channel.reply.formatAgentEnvelope({
150
- channel: "Teams",
151
- from: "system",
152
- body: params.reflectionPrompt,
153
- envelope: envelopeOptions
154
- });
155
- return { ctxPayload: core.channel.reply.finalizeInboundContext({
156
- Body: body,
157
- BodyForAgent: params.reflectionPrompt,
158
- RawBody: params.reflectionPrompt,
159
- CommandBody: params.reflectionPrompt,
160
- From: `msteams:system:${params.conversationId}`,
161
- To: `conversation:${params.conversationId}`,
162
- SessionKey: params.sessionKey,
163
- ChatType: "direct",
164
- SenderName: "system",
165
- SenderId: "system",
166
- Provider: "msteams",
167
- Surface: "msteams",
168
- Timestamp: Date.now(),
169
- WasMentioned: true,
170
- CommandAuthorized: false,
171
- OriginatingChannel: "msteams",
172
- OriginatingTo: `conversation:${params.conversationId}`
173
- }) };
174
- }
175
- function createReflectionCaptureDispatcher(params) {
176
- const core = require_runtime.getMSTeamsRuntime();
177
- let response = "";
178
- const { dispatcher, replyOptions } = core.channel.reply.createReplyDispatcherWithTyping({
179
- deliver: async (payload) => {
180
- if (payload.text) response += (response ? "\n" : "") + payload.text;
181
- },
182
- typingCallbacks: {
183
- onReplyStart: async () => {},
184
- onIdle: () => {},
185
- onCleanup: () => {}
186
- },
187
- humanDelay: core.channel.reply.resolveHumanDelayConfig(params.cfg, params.agentId),
188
- onError: (err) => {
189
- params.log.debug?.("reflection reply error", { error: require_setup_surface.formatUnknownError(err) });
190
- }
191
- });
192
- return {
193
- dispatcher,
194
- replyOptions,
195
- readResponse: () => response
196
- };
197
- }
198
- async function sendReflectionFollowUp(params) {
199
- const baseRef = require_messenger.buildConversationReference(params.conversationRef);
200
- await require_messenger.sendMSTeamsActivityWithReference(params.app, baseRef, {
201
- type: "message",
202
- text: params.userMessage
203
- }, { serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(params.cfg.channels?.msteams) });
204
- }
205
68
  /**
206
69
  * Run a background reflection after negative feedback.
207
70
  * This is designed to be called fire-and-forget (don't await in the invoke handler).
208
71
  */
209
72
  async function runFeedbackReflection(params) {
210
73
  const { cfg, log, sessionKey } = params;
211
- const cooldownMs = cfg.channels?.msteams?.feedbackReflectionCooldownMs ?? 3e5;
212
- if (!isReflectionAllowed(sessionKey, cooldownMs)) {
213
- log.debug?.("skipping reflection (cooldown active)", { sessionKey });
214
- return;
215
- }
216
- const reflectionPrompt = buildReflectionPrompt({
217
- thumbedDownResponse: params.thumbedDownResponse,
218
- userComment: params.userComment
219
- });
220
- const storePath = require_runtime.getMSTeamsRuntime().channel.session.resolveStorePath(cfg.session?.store, { agentId: params.agentId });
221
- const { ctxPayload } = buildReflectionContext({
222
- cfg,
223
- conversationId: params.conversationId,
224
- sessionKey: params.sessionKey,
225
- reflectionPrompt
226
- });
227
- const capture = createReflectionCaptureDispatcher({
228
- cfg,
229
- agentId: params.agentId,
230
- log
231
- });
74
+ const cooldownMs = cfg.channels?.msteams?.feedbackReflectionCooldownMs ?? openclaw_plugin_sdk_channel_inbound.DEFAULT_CHANNEL_FEEDBACK_REFLECTION_COOLDOWN_MS;
75
+ let reflection;
232
76
  try {
233
- await (0, openclaw_plugin_sdk_channel_inbound.dispatchReplyFromConfigWithSettledDispatcher)({
234
- ctxPayload,
77
+ reflection = await (0, openclaw_plugin_sdk_channel_inbound.runChannelFeedbackReflection)({
235
78
  cfg,
236
- dispatcher: capture.dispatcher,
237
- onSettled: () => {},
238
- replyOptions: capture.replyOptions
79
+ channel: "msteams",
80
+ channelLabel: "Teams",
81
+ agentId: params.agentId,
82
+ sessionKey,
83
+ conversationId: params.conversationId,
84
+ conversationKind: params.conversationKind,
85
+ thumbedDownResponse: params.thumbedDownResponse,
86
+ userComment: params.userComment,
87
+ cooldownMs,
88
+ onRecordError: (err) => log.debug?.("reflection session record failed", { error: require_setup_surface.formatUnknownError(err) }),
89
+ onDispatchError: (err) => log.debug?.("reflection reply error", { error: require_setup_surface.formatUnknownError(err) })
239
90
  });
240
91
  } catch (err) {
241
92
  log.error("reflection dispatch failed", { error: require_setup_surface.formatUnknownError(err) });
242
93
  return;
243
94
  }
244
- const reflectionResponse = capture.readResponse().trim();
245
- if (!reflectionResponse) {
246
- log.debug?.("reflection produced no output");
95
+ if (reflection.status === "cooldown") {
96
+ log.debug?.("skipping reflection (cooldown active)", { sessionKey });
247
97
  return;
248
98
  }
249
- const parsedReflection = parseReflectionResponse(reflectionResponse);
250
- if (!parsedReflection) {
251
- log.debug?.("reflection produced no structured output");
99
+ if (reflection.status === "empty") {
100
+ log.debug?.("reflection produced no output");
252
101
  return;
253
102
  }
254
- recordReflectionTime(sessionKey, cooldownMs);
255
103
  log.info("reflection complete", {
256
104
  sessionKey,
257
- responseLength: reflectionResponse.length,
258
- followUp: parsedReflection.followUp
105
+ responseLength: reflection.responseLength,
106
+ followUp: reflection.followUp
259
107
  });
260
108
  try {
261
109
  await storeSessionLearning({
262
- storePath,
263
- sessionKey: params.sessionKey,
264
- learning: parsedReflection.learning
110
+ storePath: reflection.storePath,
111
+ sessionKey,
112
+ learning: reflection.learning
265
113
  });
266
114
  } catch (err) {
267
115
  log.debug?.("failed to store reflection learning", { error: require_setup_surface.formatUnknownError(err) });
268
116
  }
269
117
  const conversationType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(params.conversationRef.conversation?.conversationType);
270
- if (!(conversationType === "personal" && parsedReflection.followUp && Boolean(parsedReflection.userMessage))) {
271
- if (parsedReflection.followUp && conversationType !== "personal") log.debug?.("skipping reflection follow-up outside direct message", {
118
+ if (!(conversationType === "personal" && reflection.followUp && Boolean(reflection.userMessage))) {
119
+ if (reflection.followUp && conversationType !== "personal") log.debug?.("skipping reflection follow-up outside direct message", {
272
120
  sessionKey,
273
121
  conversationType
274
122
  });
275
123
  return;
276
124
  }
277
125
  try {
278
- await sendReflectionFollowUp({
279
- cfg,
280
- app: params.app,
281
- conversationRef: params.conversationRef,
282
- userMessage: parsedReflection.userMessage
283
- });
126
+ await require_messenger.sendMSTeamsActivityWithReference(params.app, require_messenger.buildConversationReference(params.conversationRef), {
127
+ type: "message",
128
+ text: reflection.userMessage
129
+ }, { serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(cfg.channels?.msteams) });
284
130
  log.info("sent reflection follow-up", { sessionKey });
285
131
  } catch (err) {
286
132
  log.debug?.("failed to send reflection follow-up", { error: require_setup_surface.formatUnknownError(err) });
@@ -1461,6 +1307,8 @@ function createTeamsReplyStreamController(params) {
1461
1307
  let streamFailed = false;
1462
1308
  let lastInformativeText = "";
1463
1309
  let progressLines = [];
1310
+ let latestPlan;
1311
+ let latestPlanExplanation;
1464
1312
  let pendingFinalPayload;
1465
1313
  let emittedText = "";
1466
1314
  const wasCanceled = () => canceledLocally || Boolean(stream?.canceled);
@@ -1482,7 +1330,9 @@ function createTeamsReplyStreamController(params) {
1482
1330
  entry: params.msteamsConfig,
1483
1331
  lines: shouldStreamPreviewToolProgress ? progressLines : [],
1484
1332
  seed: params.progressSeed,
1485
- bullet: "-"
1333
+ bullet: "-",
1334
+ narration: latestPlanExplanation,
1335
+ plan: latestPlan
1486
1336
  });
1487
1337
  if (!informativeText || informativeText === lastInformativeText) return;
1488
1338
  lastInformativeText = informativeText;
@@ -1554,6 +1404,14 @@ function createTeamsReplyStreamController(params) {
1554
1404
  const progressActive = await progressDraftGate.noteWork();
1555
1405
  if ((hadStarted || progressActive) && progressDraftGate.hasStarted) renderInformativeUpdate();
1556
1406
  },
1407
+ async pushPlanProgress(steps, options) {
1408
+ if (!stream || streamMode !== "progress" || streamFinalizationPending) return;
1409
+ latestPlan = steps?.length ? steps.map((entry) => ({ ...entry })) : void 0;
1410
+ latestPlanExplanation = options?.explanation?.replace(/\s+/g, " ").trim() || void 0;
1411
+ const hadStarted = progressDraftGate.hasStarted;
1412
+ await progressDraftGate.startNow();
1413
+ if (hadStarted && progressDraftGate.hasStarted) renderInformativeUpdate();
1414
+ },
1557
1415
  preparePayload(payload) {
1558
1416
  if (!stream) return payload;
1559
1417
  if (wasCanceled()) return;
@@ -1803,14 +1661,16 @@ function createMSTeamsReplyDispatcher(params) {
1803
1661
  }
1804
1662
  if (ids.length > 0) params.onSentMessageIds?.(ids);
1805
1663
  };
1806
- const { dispatcher, replyOptions, markDispatchIdle: baseMarkDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
1664
+ const dispatcherOptions = {
1807
1665
  ...replyPipeline,
1808
- humanDelay: core.channel.reply.resolveHumanDelayConfig(params.cfg, params.agentId),
1666
+ humanDelay: (0, openclaw_plugin_sdk_agent_runtime.resolveHumanDelayConfig)(params.cfg, params.agentId),
1809
1667
  onReplyStart: async () => {
1810
1668
  await streamController.onReplyStart();
1811
1669
  if (typingIndicatorEnabled) await typingCallbacks?.onReplyStart?.();
1812
1670
  },
1813
- typingCallbacks,
1671
+ typingCallbacks
1672
+ };
1673
+ const delivery = {
1814
1674
  deliver: async (payload) => {
1815
1675
  const preparedPayload = streamController.preparePayload(payload);
1816
1676
  if (!preparedPayload) return;
@@ -1829,8 +1689,8 @@ function createMSTeamsReplyDispatcher(params) {
1829
1689
  hint
1830
1690
  });
1831
1691
  }
1832
- });
1833
- const markDispatchIdle = () => {
1692
+ };
1693
+ const settleDelivery = () => {
1834
1694
  return flushPendingMessages().catch((err) => {
1835
1695
  const errMsg = require_setup_surface.formatUnknownError(err);
1836
1696
  const classification = require_setup_surface.classifyMSTeamsSendError(err);
@@ -1849,8 +1709,6 @@ function createMSTeamsReplyDispatcher(params) {
1849
1709
  queueReplyPayload(fallbackPayload);
1850
1710
  await flushPendingMessages();
1851
1711
  }
1852
- }).finally(() => {
1853
- baseMarkDispatchIdle();
1854
1712
  });
1855
1713
  };
1856
1714
  const previewToolProgressEnabled = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingPreviewToolProgress)(msteamsCfg);
@@ -1901,13 +1759,7 @@ function createMSTeamsReplyDispatcher(params) {
1901
1759
  },
1902
1760
  onPlanUpdate: async (payload) => {
1903
1761
  if (payload?.phase !== "update") return;
1904
- await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
1905
- event: "plan",
1906
- phase: payload.phase,
1907
- ...typeof payload?.title === "string" ? { title: payload.title } : {},
1908
- ...typeof payload?.explanation === "string" ? { explanation: payload.explanation } : {},
1909
- ...Array.isArray(payload?.steps) && payload.steps.every((s) => typeof s === "string") ? { steps: payload.steps } : {}
1910
- }));
1762
+ await streamController.pushPlanProgress((0, openclaw_plugin_sdk_channel_outbound.normalizeAgentPlanSteps)(payload.steps), { explanation: typeof payload.explanation === "string" ? payload.explanation : void 0 });
1911
1763
  },
1912
1764
  onApprovalEvent: async (payload) => {
1913
1765
  if (payload?.phase !== "requested") return;
@@ -1950,16 +1802,18 @@ function createMSTeamsReplyDispatcher(params) {
1950
1802
  }
1951
1803
  } : {};
1952
1804
  return {
1953
- dispatcher,
1805
+ dispatcherOptions: {
1806
+ ...dispatcherOptions,
1807
+ onSettled: settleDelivery
1808
+ },
1809
+ delivery,
1954
1810
  replyOptions: {
1955
- ...replyOptions,
1956
1811
  ...streamController.hasStream() ? { onPartialReply: (payload) => streamController.onPartialReply(payload) } : {},
1957
1812
  ...progressCallbacks,
1958
1813
  ...shouldSuppressDefaultToolProgressMessages ? { suppressDefaultToolProgressMessages: true } : {},
1959
1814
  disableBlockStreaming: blockStreamingResolved == null ? void 0 : !blockStreamingResolved,
1960
1815
  onModelSelected
1961
- },
1962
- markDispatchIdle
1816
+ }
1963
1817
  };
1964
1818
  }
1965
1819
  //#endregion
@@ -2575,12 +2429,17 @@ function createMSTeamsMessageHandler(deps) {
2575
2429
  const replies = repliesResult.status === "fulfilled" ? repliesResult.value : [];
2576
2430
  if (parentResult.status === "rejected") log.debug?.("failed to fetch parent message", { error: require_setup_surface.formatUnknownError(parentResult.reason) });
2577
2431
  if (repliesResult.status === "rejected") log.debug?.("failed to fetch thread replies", { error: require_setup_surface.formatUnknownError(repliesResult.reason) });
2578
- const isThreadSenderAllowed = (msg) => groupPolicy === "allowlist" ? require_channel.resolveMSTeamsAllowlistMatch({
2432
+ const isThreadSenderAllowed = (msg) => (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
2433
+ isGroup: isChannel,
2434
+ groupPolicy,
2579
2435
  allowFrom: effectiveGroupAllowFrom,
2580
- senderId: msg.from?.user?.id ?? "",
2581
- senderName: msg.from?.user?.displayName,
2582
- allowNameMatching
2583
- }).allowed : true;
2436
+ isSenderAllowed: (allowFrom) => require_channel.resolveMSTeamsAllowlistMatch({
2437
+ allowFrom,
2438
+ senderId: msg.from?.user?.id ?? "",
2439
+ senderName: msg.from?.user?.displayName,
2440
+ allowNameMatching
2441
+ }).allowed
2442
+ });
2584
2443
  const parentSummary = summarizeParentMessage(parentMsg);
2585
2444
  const visibleParentMessages = parentMsg ? (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
2586
2445
  items: [parentMsg],
@@ -2611,17 +2470,14 @@ function createMSTeamsMessageHandler(deps) {
2611
2470
  }
2612
2471
  quoteSenderName ??= quoteInfo?.sender;
2613
2472
  const envelopeFrom = isDirectMessage ? senderName : conversationType;
2614
- const { storePath, envelopeOptions, previousTimestamp } = (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSessionEnvelopeContext)({
2473
+ const buildEnvelope = (0, openclaw_plugin_sdk_channel_inbound.createChannelInboundEnvelopeBuilder)({
2615
2474
  cfg,
2616
- agentId: route.agentId,
2617
- sessionKey: route.sessionKey
2475
+ route
2618
2476
  });
2619
- let combinedBody = core.channel.reply.formatAgentEnvelope({
2477
+ let combinedBody = buildEnvelope({
2620
2478
  channel: "Teams",
2621
2479
  from: envelopeFrom,
2622
2480
  timestamp,
2623
- previousTimestamp,
2624
- envelope: envelopeOptions,
2625
2481
  body: agentBody
2626
2482
  });
2627
2483
  const isRoomish = !isDirectMessage;
@@ -2630,12 +2486,12 @@ function createMSTeamsMessageHandler(deps) {
2630
2486
  historyKey,
2631
2487
  limit: historyLimit,
2632
2488
  currentMessage: combinedBody,
2633
- formatEntry: (entry) => core.channel.reply.formatAgentEnvelope({
2489
+ formatEntry: (entry) => buildEnvelope({
2634
2490
  channel: "Teams",
2635
2491
  from: conversationType,
2636
2492
  timestamp: entry.timestamp,
2637
- body: `${entry.sender}: ${entry.body}${entry.messageId ? ` [id:${entry.messageId}]` : ""}`,
2638
- envelope: envelopeOptions
2493
+ previousTimestamp: null,
2494
+ body: `${entry.sender}: ${entry.body}${entry.messageId ? ` [id:${entry.messageId}]` : ""}`
2639
2495
  })
2640
2496
  });
2641
2497
  const inboundHistory = isRoomish && historyKey && historyLimit > 0 ? (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildInboundHistory({
@@ -2643,17 +2499,21 @@ function createMSTeamsMessageHandler(deps) {
2643
2499
  limit: historyLimit
2644
2500
  }) : void 0;
2645
2501
  const commandBody = text.trim();
2646
- const quoteSenderAllowed = quoteInfo && quoteInfo.sender ? !isChannel || groupPolicy !== "allowlist" ? true : require_channel.resolveMSTeamsAllowlistMatch({
2502
+ const quoteSenderAllowed = quoteInfo && quoteInfo.sender ? (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
2503
+ isGroup: !isDirectMessage,
2504
+ groupPolicy,
2647
2505
  allowFrom: effectiveGroupAllowFrom,
2648
- senderId: quoteSenderId ?? "",
2649
- senderName: quoteSenderName,
2650
- allowNameMatching
2651
- }).allowed : true;
2506
+ isSenderAllowed: (allowFrom) => require_channel.resolveMSTeamsAllowlistMatch({
2507
+ allowFrom,
2508
+ senderId: quoteSenderId ?? "",
2509
+ senderName: quoteSenderName,
2510
+ allowNameMatching
2511
+ }).allowed
2512
+ }) : true;
2652
2513
  const bodyForAgent = threadContext ? `[Thread history]\n${threadContext}\n[/Thread history]\n\n${agentBody}` : agentBody;
2653
2514
  const nativeChannelId = isChannel && teamAadGroupId ? `${teamAadGroupId}/${graphChannelId}` : void 0;
2654
2515
  const ctxPayload = (0, openclaw_plugin_sdk_channel_inbound.buildChannelInboundEventContext)({
2655
2516
  channel: "msteams",
2656
- finalize: core.channel.reply.finalizeInboundContext,
2657
2517
  contextVisibility: contextVisibilityMode,
2658
2518
  supplemental: { quote: quoteInfo ? {
2659
2519
  id: quoteInfo.id ?? activity.replyToId ?? void 0,
@@ -2678,6 +2538,7 @@ function createMSTeamsMessageHandler(deps) {
2678
2538
  },
2679
2539
  route: {
2680
2540
  agentId: route.agentId,
2541
+ dmScope: route.dmScope,
2681
2542
  accountId: route.accountId,
2682
2543
  routeSessionKey: route.sessionKey
2683
2544
  },
@@ -2698,7 +2559,7 @@ function createMSTeamsMessageHandler(deps) {
2698
2559
  canDetectMention: !isDirectMessage,
2699
2560
  wasMentioned: isDirectMessage || mentionDecision.effectiveWasMentioned
2700
2561
  },
2701
- commands: { authorized: commandAuthorized }
2562
+ commands: { authorized: commandAuthorized === true }
2702
2563
  },
2703
2564
  extra: {
2704
2565
  GroupSubject: !isDirectMessage ? conversationType : void 0,
@@ -2708,7 +2569,7 @@ function createMSTeamsMessageHandler(deps) {
2708
2569
  });
2709
2570
  logVerboseMessage(`msteams inbound: from=${ctxPayload.From} preview="${preview}"`);
2710
2571
  const sharePointSiteId = msteamsCfg?.sharePointSiteId;
2711
- const { dispatcher, replyOptions, markDispatchIdle } = createMSTeamsReplyDispatcher({
2572
+ const { dispatcherOptions, delivery, replyOptions } = createMSTeamsReplyDispatcher({
2712
2573
  cfg,
2713
2574
  agentId: route.agentId,
2714
2575
  sessionKey: route.sessionKey,
@@ -2728,10 +2589,16 @@ function createMSTeamsMessageHandler(deps) {
2728
2589
  sharePointSiteId
2729
2590
  });
2730
2591
  const senderTimezone = (activity.entities?.find((e) => e.type === "clientInfo"))?.timezone || conversationRef.timezone;
2731
- const configOverride = senderTimezone && !cfg.agents?.defaults?.userTimezone ? { agents: { defaults: {
2732
- ...cfg.agents?.defaults,
2733
- userTimezone: senderTimezone
2734
- } } } : void 0;
2592
+ const turnConfig = senderTimezone && !cfg.agents?.defaults?.userTimezone ? {
2593
+ ...cfg,
2594
+ agents: {
2595
+ ...cfg.agents,
2596
+ defaults: {
2597
+ ...cfg.agents?.defaults,
2598
+ userTimezone: senderTimezone
2599
+ }
2600
+ }
2601
+ } : cfg;
2735
2602
  log.info("dispatching to agent", { sessionKey: route.sessionKey });
2736
2603
  try {
2737
2604
  const turnResult = await core.channel.inbound.run({
@@ -2748,12 +2615,14 @@ function createMSTeamsMessageHandler(deps) {
2748
2615
  raw: activity
2749
2616
  }),
2750
2617
  resolveTurn: () => ({
2618
+ cfg: turnConfig,
2751
2619
  channel: "msteams",
2752
2620
  accountId: route.accountId,
2753
- routeSessionKey: route.sessionKey,
2754
- storePath,
2621
+ route: {
2622
+ agentId: route.agentId,
2623
+ sessionKey: route.sessionKey
2624
+ },
2755
2625
  ctxPayload,
2756
- recordInboundSession: core.channel.session.recordInboundSession,
2757
2626
  record: { onRecordError: (err) => {
2758
2627
  logVerboseMessage(`msteams: failed updating session meta: ${require_setup_surface.formatUnknownError(err)}`);
2759
2628
  } },
@@ -2763,18 +2632,12 @@ function createMSTeamsMessageHandler(deps) {
2763
2632
  historyMap: conversationHistories,
2764
2633
  limit: historyLimit
2765
2634
  },
2766
- onPreDispatchFailure: () => core.channel.reply.settleReplyDispatcher({
2767
- dispatcher,
2768
- onSettled: () => markDispatchIdle()
2769
- }),
2770
- runDispatch: () => (0, openclaw_plugin_sdk_channel_inbound.dispatchReplyFromConfigWithSettledDispatcher)({
2771
- cfg,
2772
- ctxPayload,
2773
- dispatcher,
2774
- onSettled: () => markDispatchIdle(),
2775
- replyOptions,
2776
- configOverride
2777
- })
2635
+ dispatcherOptions,
2636
+ delivery,
2637
+ replyOptions: {
2638
+ ...replyOptions,
2639
+ ...params.turnAdoptionLifecycle ? (0, openclaw_plugin_sdk_channel_outbound.bindIngressLifecycleToReplyOptions)(params.turnAdoptionLifecycle) : {}
2640
+ }
2778
2641
  })
2779
2642
  }
2780
2643
  });
@@ -2792,11 +2655,47 @@ function createMSTeamsMessageHandler(deps) {
2792
2655
  } catch (err) {
2793
2656
  log.error("dispatch failed", { error: require_setup_surface.formatUnknownError(err) });
2794
2657
  runtime.error(`msteams dispatch failed: ${require_setup_surface.formatUnknownError(err)}`);
2658
+ if (params.turnAdoptionLifecycle) throw err;
2795
2659
  try {
2796
2660
  await context.sendActivity("⚠️ Something went wrong. Please try again.");
2797
2661
  } catch {}
2798
2662
  }
2799
2663
  };
2664
+ function buildFlushIngressLifecycle(entries) {
2665
+ const lifecycles = entries.map((entry) => entry.turnAdoptionLifecycle).filter((lifecycle) => lifecycle !== void 0);
2666
+ const [firstLifecycle] = lifecycles;
2667
+ if (!firstLifecycle) return {
2668
+ lifecycle: void 0,
2669
+ settle: async () => {}
2670
+ };
2671
+ let handedOff = false;
2672
+ const adoptAll = async () => {
2673
+ for (const lifecycle of lifecycles) await lifecycle.onAdopted();
2674
+ };
2675
+ return {
2676
+ lifecycle: {
2677
+ abortSignal: lifecycles.length === 1 ? firstLifecycle.abortSignal : AbortSignal.any(lifecycles.map((lifecycle) => lifecycle.abortSignal)),
2678
+ onAdopted: async () => {
2679
+ handedOff = true;
2680
+ await adoptAll();
2681
+ },
2682
+ onDeferred: () => {
2683
+ handedOff = true;
2684
+ for (const lifecycle of lifecycles) lifecycle.onDeferred();
2685
+ },
2686
+ onAdoptionFinalizing: () => {
2687
+ for (const lifecycle of lifecycles) lifecycle.onAdoptionFinalizing();
2688
+ },
2689
+ onAbandoned: async () => {
2690
+ handedOff = true;
2691
+ for (const lifecycle of lifecycles) await lifecycle.onAbandoned();
2692
+ }
2693
+ },
2694
+ settle: async () => {
2695
+ if (!handedOff) await adoptAll();
2696
+ }
2697
+ };
2698
+ }
2800
2699
  const inboundDebouncer = core.channel.debounce.createInboundDebouncer({
2801
2700
  debounceMs: inboundDebounceMs,
2802
2701
  buildKey: (entry) => {
@@ -2813,29 +2712,40 @@ function createMSTeamsMessageHandler(deps) {
2813
2712
  onFlush: async (entries) => {
2814
2713
  const last = entries.at(-1);
2815
2714
  if (!last) return;
2816
- if (entries.length === 1) {
2817
- await handleTeamsMessageNow(last);
2818
- return;
2715
+ const { lifecycle, settle } = buildFlushIngressLifecycle(entries);
2716
+ try {
2717
+ if (entries.length === 1) await handleTeamsMessageNow(lifecycle ? {
2718
+ ...last,
2719
+ turnAdoptionLifecycle: lifecycle
2720
+ } : last);
2721
+ else {
2722
+ const combinedText = entries.map((entry) => entry.text).filter(Boolean).join("\n");
2723
+ if (combinedText.trim()) {
2724
+ const combinedRawText = entries.map((entry) => entry.rawText).filter(Boolean).join("\n");
2725
+ const wasMentioned = entries.some((entry) => entry.wasMentioned);
2726
+ const implicitMentionKinds = entries.flatMap((entry) => entry.implicitMentionKinds);
2727
+ await handleTeamsMessageNow({
2728
+ context: last.context,
2729
+ rawText: combinedRawText,
2730
+ text: combinedText,
2731
+ attachments: [],
2732
+ wasMentioned,
2733
+ implicitMentionKinds,
2734
+ ...lifecycle ? { turnAdoptionLifecycle: lifecycle } : {}
2735
+ });
2736
+ }
2737
+ }
2738
+ await settle();
2739
+ } catch (err) {
2740
+ await lifecycle?.onAbandoned();
2741
+ throw err;
2819
2742
  }
2820
- const combinedText = entries.map((entry) => entry.text).filter(Boolean).join("\n");
2821
- if (!combinedText.trim()) return;
2822
- const combinedRawText = entries.map((entry) => entry.rawText).filter(Boolean).join("\n");
2823
- const wasMentioned = entries.some((entry) => entry.wasMentioned);
2824
- const implicitMentionKinds = entries.flatMap((entry) => entry.implicitMentionKinds);
2825
- await handleTeamsMessageNow({
2826
- context: last.context,
2827
- rawText: combinedRawText,
2828
- text: combinedText,
2829
- attachments: [],
2830
- wasMentioned,
2831
- implicitMentionKinds
2832
- });
2833
2743
  },
2834
2744
  onError: (err) => {
2835
2745
  runtime.error(`msteams debounce flush failed: ${require_setup_surface.formatUnknownError(err)}`);
2836
2746
  }
2837
2747
  });
2838
- return async function handleTeamsMessage(context) {
2748
+ return async function handleTeamsMessage(context, turnAdoptionLifecycle) {
2839
2749
  const activity = context.activity;
2840
2750
  const attachments = Array.isArray(activity.attachments) ? activity.attachments : [];
2841
2751
  const rawText = activity.text?.trim() ?? "";
@@ -2855,8 +2765,10 @@ function createMSTeamsMessageHandler(deps) {
2855
2765
  text,
2856
2766
  attachments,
2857
2767
  wasMentioned,
2858
- implicitMentionKinds
2768
+ implicitMentionKinds,
2769
+ turnAdoptionLifecycle
2859
2770
  });
2771
+ if (turnAdoptionLifecycle) return { kind: "deferred" };
2860
2772
  };
2861
2773
  }
2862
2774
  //#endregion
@@ -3053,29 +2965,37 @@ function registerMSTeamsHandlers(handler, deps) {
3053
2965
  const handleTeamsMessage = createMSTeamsMessageHandler(deps);
3054
2966
  const handleReaction = createMSTeamsReactionHandler(deps);
3055
2967
  const originalRun = handler.run;
3056
- if (originalRun) handler.run = async (context) => {
2968
+ if (originalRun) handler.run = async (context, turnAdoptionLifecycle) => {
3057
2969
  const ctx = context;
3058
2970
  if (ctx.activity?.type === "invoke" && ctx.activity?.name === "adaptiveCard/action") {
3059
2971
  const text = serializeMSTeamsAdaptiveCardActionValue(ctx.activity?.value);
3060
- if (text) await handleTeamsMessage({
2972
+ if (text) return await handleTeamsMessage({
3061
2973
  ...ctx,
3062
2974
  activity: {
3063
2975
  ...ctx.activity,
3064
2976
  type: "message",
3065
2977
  text
3066
2978
  }
3067
- });
2979
+ }, turnAdoptionLifecycle);
3068
2980
  return;
3069
2981
  }
3070
- return originalRun.call(handler, context);
2982
+ return originalRun.call(handler, context, turnAdoptionLifecycle);
3071
2983
  };
3072
- handler.onMessage(async (context, next) => {
2984
+ handler.onMessage(async (context, next, turnAdoptionLifecycle) => {
2985
+ let nextRan = false;
2986
+ const runNext = async () => {
2987
+ nextRan = true;
2988
+ await next();
2989
+ };
3073
2990
  try {
3074
- await handleTeamsMessage(context);
2991
+ const result = await handleTeamsMessage(context, turnAdoptionLifecycle);
2992
+ await runNext();
2993
+ return result;
3075
2994
  } catch (err) {
2995
+ if (turnAdoptionLifecycle) throw err;
3076
2996
  deps.runtime.error(`msteams handler failed: ${require_setup_surface.formatUnknownError(err)}`);
3077
2997
  }
3078
- await next();
2998
+ if (!nextRan) await runNext();
3079
2999
  });
3080
3000
  handler.onMembersAdded(async (context, next) => {
3081
3001
  const ctx = context;
@@ -3176,8 +3096,7 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
3176
3096
  const convType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(activity.conversation?.conversationType);
3177
3097
  const isDirectMessage = convType === "personal" || !convType && !activity.conversation?.isGroup;
3178
3098
  const isChannel = convType === "channel";
3179
- const core = require_runtime.getMSTeamsRuntime();
3180
- const route = core.channel.routing.resolveAgentRoute({
3099
+ const route = require_runtime.getMSTeamsRuntime().channel.routing.resolveAgentRoute({
3181
3100
  cfg: deps.cfg,
3182
3101
  channel: "msteams",
3183
3102
  peer: {
@@ -3206,13 +3125,12 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
3206
3125
  hasComment: Boolean(userComment)
3207
3126
  });
3208
3127
  try {
3209
- const storePath = core.channel.session.resolveStorePath(deps.cfg.session?.store, { agentId: route.agentId });
3210
- const safeKey = route.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
3211
- await (0, openclaw_plugin_sdk_security_runtime.appendRegularFile)({
3212
- filePath: node_path.default.join(storePath, `${safeKey}.jsonl`),
3213
- content: `${JSON.stringify(feedbackEvent)}\n`,
3214
- rejectSymlinkParents: true
3215
- }).catch(() => {});
3128
+ await (0, openclaw_plugin_sdk_channel_inbound.recordChannelFeedbackEvent)({
3129
+ cfg: deps.cfg,
3130
+ agentId: route.agentId,
3131
+ sessionKey: route.sessionKey,
3132
+ event: feedbackEvent
3133
+ });
3216
3134
  } catch {}
3217
3135
  const conversationRef = {
3218
3136
  activityId: activity.id,
@@ -3237,12 +3155,11 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
3237
3155
  if (isNegative && msteamsCfg?.feedbackReflection !== false) runFeedbackReflection({
3238
3156
  cfg: deps.cfg,
3239
3157
  app: deps.app,
3240
- appId: deps.appId,
3241
3158
  conversationRef,
3242
3159
  sessionKey: route.sessionKey,
3243
3160
  agentId: route.agentId,
3244
3161
  conversationId,
3245
- feedbackMessageId: messageId,
3162
+ conversationKind: isDirectMessage ? "direct" : isChannel ? "channel" : "group",
3246
3163
  userComment,
3247
3164
  log: deps.log
3248
3165
  }).catch((err) => {
@@ -3360,6 +3277,175 @@ async function runMSTeamsFileConsentInvokeHandler(context, log) {
3360
3277
  }
3361
3278
  }
3362
3279
  //#endregion
3280
+ //#region extensions/msteams/src/msteams-ingress.ts
3281
+ const MSTEAMS_INGRESS_VERSION = 1;
3282
+ const MSTEAMS_INGRESS_DRAIN_INTERVAL_MS = 500;
3283
+ const MSTEAMS_INGRESS_MAX_CONCURRENT_DELIVERIES = 8;
3284
+ const MSTEAMS_INGRESS_SCAN_LIMIT = 100;
3285
+ const MSTEAMS_INGRESS_TOMBSTONE_TTL_MS = 720 * 60 * 6e4;
3286
+ const MSTEAMS_INGRESS_COMPLETED_MAX_ENTRIES = 2e4;
3287
+ const MSTEAMS_INGRESS_FAILED_MAX_ENTRIES = 4096;
3288
+ var MSTeamsIngressPayloadError = class extends Error {
3289
+ constructor(reason, message, options) {
3290
+ super(message, options);
3291
+ this.reason = reason;
3292
+ this.name = "MSTeamsIngressPayloadError";
3293
+ }
3294
+ };
3295
+ function nonEmptyString(value) {
3296
+ return typeof value === "string" && value.trim() ? value.trim() : null;
3297
+ }
3298
+ function isDispatchableActivity(activity) {
3299
+ return activity.type === "message" || activity.type === "invoke" && activity.name === "adaptiveCard/action";
3300
+ }
3301
+ function inspectMSTeamsIngressActivity(activity) {
3302
+ if (!isDispatchableActivity(activity)) return null;
3303
+ const eventId = nonEmptyString(activity.id);
3304
+ if (!eventId) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams dispatchable activity is missing activity.id.");
3305
+ const laneKey = nonEmptyString(activity.conversation?.id);
3306
+ if (!laneKey) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams dispatchable activity is missing conversation.id.");
3307
+ return {
3308
+ eventId,
3309
+ laneKey
3310
+ };
3311
+ }
3312
+ function parseClaimedActivity(payload, claimedId) {
3313
+ if (payload.version !== MSTEAMS_INGRESS_VERSION || typeof payload.rawActivity !== "string" || !Number.isFinite(payload.receivedAt)) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams ingress payload is invalid.");
3314
+ let activity;
3315
+ try {
3316
+ activity = JSON.parse(payload.rawActivity);
3317
+ } catch (error) {
3318
+ throw new MSTeamsIngressPayloadError("invalid-json", "Microsoft Teams ingress activity JSON is invalid.", { cause: error });
3319
+ }
3320
+ if (!activity || typeof activity !== "object" || Array.isArray(activity)) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams ingress activity must be an object.");
3321
+ const parsed = activity;
3322
+ const facts = inspectMSTeamsIngressActivity(parsed);
3323
+ if (!facts) throw new MSTeamsIngressPayloadError("unsupported-activity", "Microsoft Teams ingress row is not an agent-turn activity.");
3324
+ if (facts.eventId !== claimedId) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams activity id changed after durable admission.");
3325
+ return parsed;
3326
+ }
3327
+ function errorText(error) {
3328
+ return error instanceof Error ? error.message : String(error);
3329
+ }
3330
+ function createMSTeamsIngress(options) {
3331
+ const queue = options.queue ?? require_runtime.getMSTeamsRuntime().state.openChannelIngressQueue({ accountId: options.accountId });
3332
+ const shutdown = new AbortController();
3333
+ const liveContexts = /* @__PURE__ */ new Map();
3334
+ const activeDeliveries = /* @__PURE__ */ new Set();
3335
+ const drain = (0, openclaw_plugin_sdk_channel_outbound.createChannelIngressDrain)({
3336
+ queue,
3337
+ abortSignal: shutdown.signal,
3338
+ orderBy: "received",
3339
+ scanLimit: MSTEAMS_INGRESS_SCAN_LIMIT,
3340
+ startLimit: MSTEAMS_INGRESS_MAX_CONCURRENT_DELIVERIES,
3341
+ retryPolicy: {
3342
+ maxAttempts: openclaw_plugin_sdk_channel_outbound.DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
3343
+ deadLetterMinAgeMs: openclaw_plugin_sdk_channel_outbound.DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS
3344
+ },
3345
+ resolveNonRetryableFailure: (error) => {
3346
+ if (error instanceof MSTeamsIngressPayloadError) return {
3347
+ reason: error.reason,
3348
+ message: error.message
3349
+ };
3350
+ return require_setup_surface.classifyMSTeamsSendError(error).kind === "auth" ? {
3351
+ reason: "authentication-failed",
3352
+ message: errorText(error)
3353
+ } : null;
3354
+ },
3355
+ onLog: (message) => options.runtime.error?.(`msteams: ${message}`),
3356
+ dispatchClaimedEvent: async (claimed, lifecycle) => {
3357
+ const activity = parseClaimedActivity(claimed.payload, claimed.id);
3358
+ const liveContext = liveContexts.get(claimed.id);
3359
+ liveContexts.delete(claimed.id);
3360
+ const delivery = Promise.resolve(options.dispatch(activity, lifecycle, liveContext));
3361
+ activeDeliveries.add(delivery);
3362
+ try {
3363
+ return await delivery;
3364
+ } finally {
3365
+ activeDeliveries.delete(delivery);
3366
+ }
3367
+ }
3368
+ });
3369
+ let running = false;
3370
+ let stopped = false;
3371
+ let drainRequested = false;
3372
+ let drainTask;
3373
+ let drainTimer;
3374
+ const requestDrain = () => {
3375
+ if (!running || shutdown.signal.aborted) return;
3376
+ drainRequested = true;
3377
+ if (drainTask) return;
3378
+ drainTask = (async () => {
3379
+ while (drainRequested && !shutdown.signal.aborted) {
3380
+ if (!running) break;
3381
+ drainRequested = false;
3382
+ await drain.drainOnce({ shouldStop: () => !running || shutdown.signal.aborted || activeDeliveries.size >= MSTEAMS_INGRESS_MAX_CONCURRENT_DELIVERIES });
3383
+ }
3384
+ })().catch((error) => {
3385
+ options.runtime.error?.(`msteams ingress drain failed: ${errorText(error)}`);
3386
+ }).finally(() => {
3387
+ drainTask = void 0;
3388
+ if (running && drainRequested && !shutdown.signal.aborted) requestDrain();
3389
+ });
3390
+ };
3391
+ return {
3392
+ accept: async (activity, liveContext) => {
3393
+ const facts = inspectMSTeamsIngressActivity(activity);
3394
+ if (!facts) return;
3395
+ await queue.prune({
3396
+ completedTtlMs: MSTEAMS_INGRESS_TOMBSTONE_TTL_MS,
3397
+ completedMaxEntries: MSTEAMS_INGRESS_COMPLETED_MAX_ENTRIES,
3398
+ failedTtlMs: MSTEAMS_INGRESS_TOMBSTONE_TTL_MS,
3399
+ failedMaxEntries: MSTEAMS_INGRESS_FAILED_MAX_ENTRIES
3400
+ });
3401
+ const receivedAt = Date.now();
3402
+ const installedLiveContext = Boolean(liveContext) && !liveContexts.has(facts.eventId);
3403
+ if (liveContext && installedLiveContext) liveContexts.set(facts.eventId, liveContext);
3404
+ const uninstallLiveContext = () => {
3405
+ if (installedLiveContext && liveContexts.get(facts.eventId) === liveContext) liveContexts.delete(facts.eventId);
3406
+ };
3407
+ let result;
3408
+ try {
3409
+ result = await queue.enqueue(facts.eventId, {
3410
+ version: MSTEAMS_INGRESS_VERSION,
3411
+ receivedAt,
3412
+ rawActivity: JSON.stringify(activity)
3413
+ }, {
3414
+ receivedAt,
3415
+ laneKey: facts.laneKey
3416
+ });
3417
+ } catch (error) {
3418
+ uninstallLiveContext();
3419
+ throw error;
3420
+ }
3421
+ if (!(result.kind === "accepted" || result.kind === "pending")) uninstallLiveContext();
3422
+ requestDrain();
3423
+ },
3424
+ start: () => {
3425
+ if (running || stopped) return;
3426
+ running = true;
3427
+ requestDrain();
3428
+ drainTimer = setInterval(requestDrain, MSTEAMS_INGRESS_DRAIN_INTERVAL_MS);
3429
+ drainTimer.unref?.();
3430
+ },
3431
+ stop: async () => {
3432
+ if (stopped) return;
3433
+ stopped = true;
3434
+ running = false;
3435
+ if (drainTimer) {
3436
+ clearInterval(drainTimer);
3437
+ drainTimer = void 0;
3438
+ }
3439
+ await drainTask;
3440
+ await Promise.allSettled(activeDeliveries);
3441
+ await drain.waitForIdle();
3442
+ shutdown.abort();
3443
+ drain.dispose();
3444
+ liveContexts.clear();
3445
+ }
3446
+ };
3447
+ }
3448
+ //#endregion
3363
3449
  //#region extensions/msteams/src/webhook-timeouts.ts
3364
3450
  const MSTEAMS_WEBHOOK_INACTIVITY_TIMEOUT_MS = 3e4;
3365
3451
  const MSTEAMS_WEBHOOK_REQUEST_TIMEOUT_MS = 3e4;
@@ -3542,6 +3628,15 @@ async function monitorMSTeamsProvider(opts) {
3542
3628
  log
3543
3629
  };
3544
3630
  registerMSTeamsHandlers(handler, handlerDeps);
3631
+ const ingress = createMSTeamsIngress({
3632
+ accountId: appId,
3633
+ runtime,
3634
+ dispatch: async (activity, lifecycle, liveContext) => {
3635
+ if (liveContext) liveContext.activity = activity;
3636
+ const context = liveContext ?? createMSTeamsReplayContext(activity, app, require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(msteamsCfg));
3637
+ return await handler.run(context, lifecycle);
3638
+ }
3639
+ });
3545
3640
  app.on("card.action", async (ctx) => {
3546
3641
  const adaptedCtx = adaptSdkContext(ctx, app);
3547
3642
  try {
@@ -3618,9 +3713,7 @@ async function monitorMSTeamsProvider(opts) {
3618
3713
  };
3619
3714
  }
3620
3715
  }
3621
- handler.run(adaptedCtx).catch((err) => {
3622
- log.error("msteams card.action dispatch failed", { error: require_setup_surface.formatUnknownError(err) });
3623
- });
3716
+ await ingress.accept(activity, adaptedCtx);
3624
3717
  return {
3625
3718
  statusCode: 200,
3626
3719
  type: "application/vnd.microsoft.activity.message",
@@ -3702,24 +3795,30 @@ async function monitorMSTeamsProvider(opts) {
3702
3795
  if (!await runMSTeamsFeedbackInvokeHandler(adaptSdkContext(ctx, app), handlerDeps)) await ctx.next?.call(ctx);
3703
3796
  });
3704
3797
  app.on("activity", async (ctx) => {
3798
+ const adaptedCtx = adaptSdkContext(ctx, app);
3799
+ const activity = adaptedCtx.activity;
3800
+ if (activity?.type === "invoke") {
3801
+ if (activity?.name === "adaptiveCard/action") return;
3802
+ if (activity?.name === "fileConsent/invoke") return;
3803
+ if (activity?.name === "signin/tokenExchange" || activity?.name === "signin/verifyState") return;
3804
+ }
3805
+ if (activity?.type === "message") {
3806
+ await ingress.accept(activity, adaptedCtx);
3807
+ return;
3808
+ }
3705
3809
  try {
3706
- const adaptedCtx = adaptSdkContext(ctx, app);
3707
- const activity = adaptedCtx.activity;
3708
- if (activity?.type === "invoke") {
3709
- if (activity?.name === "adaptiveCard/action") return;
3710
- if (activity?.name === "fileConsent/invoke") return;
3711
- if (activity?.name === "signin/tokenExchange" || activity?.name === "signin/verifyState") return;
3712
- }
3713
3810
  await handler.run(adaptedCtx);
3714
3811
  } catch (err) {
3715
- log.error("msteams webhook failed", { error: require_setup_surface.formatUnknownError(err) });
3812
+ log.error("msteams non-turn activity failed", { error: require_setup_surface.formatUnknownError(err) });
3716
3813
  }
3717
3814
  });
3718
3815
  await app.initialize();
3816
+ ingress.start();
3719
3817
  const httpServer = await new Promise((resolve, reject) => {
3720
3818
  const server = expressApp.listen(port, (err) => err ? reject(err) : resolve(server));
3721
- }).catch((err) => {
3819
+ }).catch(async (err) => {
3722
3820
  log.error("msteams server error", { error: require_setup_surface.formatUnknownError(err) });
3821
+ await ingress.stop();
3723
3822
  throw err;
3724
3823
  });
3725
3824
  log.info(`msteams provider started on port ${port}`);
@@ -3729,12 +3828,13 @@ async function monitorMSTeamsProvider(opts) {
3729
3828
  });
3730
3829
  const shutdown = async () => {
3731
3830
  log.info("shutting down msteams provider");
3732
- return new Promise((resolve) => {
3831
+ await new Promise((resolve) => {
3733
3832
  httpServer.close((err) => {
3734
3833
  if (err) log.debug?.("msteams server close error", { error: require_setup_surface.formatUnknownError(err) });
3735
3834
  resolve();
3736
3835
  });
3737
3836
  });
3837
+ await ingress.stop();
3738
3838
  };
3739
3839
  await (0, openclaw_plugin_sdk_channel_outbound.keepHttpServerTaskAlive)({
3740
3840
  server: httpServer,
@@ -3772,11 +3872,14 @@ function buildActivityHandler() {
3772
3872
  reactionsRemovedHandlers.push(cb);
3773
3873
  return handler;
3774
3874
  },
3775
- async run(context) {
3875
+ async run(context, turnAdoptionLifecycle) {
3776
3876
  const ctx = context;
3777
3877
  const activityType = ctx?.activity?.type;
3778
3878
  const noop = async () => {};
3779
- if (activityType === "message") for (const h of messageHandlers) await h(context, noop);
3879
+ if (activityType === "message") for (const h of messageHandlers) {
3880
+ const result = await h(context, noop, turnAdoptionLifecycle);
3881
+ if (result) return result;
3882
+ }
3780
3883
  else if (activityType === "conversationUpdate") for (const h of membersAddedHandlers) await h(context, noop);
3781
3884
  else if (activityType === "messageReaction") {
3782
3885
  const activity = ctx?.activity;
@@ -3787,6 +3890,47 @@ function buildActivityHandler() {
3787
3890
  };
3788
3891
  return handler;
3789
3892
  }
3893
+ function createMSTeamsReplayContext(activity, app, serviceUrlBoundary) {
3894
+ const rawConversationId = activity.conversation?.id ?? "";
3895
+ const conversationId = require_channel.normalizeMSTeamsConversationId(rawConversationId);
3896
+ const conversationType = activity.conversation?.conversationType ?? "personal";
3897
+ const threadActivityId = conversationType.toLowerCase() === "channel" ? require_channel.extractMSTeamsConversationMessageId(rawConversationId) ?? activity.replyToId : void 0;
3898
+ const tenantId = activity.channelData?.tenant?.id ?? activity.conversation?.tenantId;
3899
+ const reference = {
3900
+ activityId: activity.id,
3901
+ user: activity.from,
3902
+ agent: activity.recipient,
3903
+ conversation: {
3904
+ id: conversationId,
3905
+ conversationType,
3906
+ ...tenantId ? { tenantId } : {}
3907
+ },
3908
+ channelId: activity.channelId,
3909
+ serviceUrl: activity.serviceUrl,
3910
+ locale: activity.locale,
3911
+ ...tenantId ? { tenantId } : {},
3912
+ ...activity.from?.aadObjectId ? { aadObjectId: activity.from.aadObjectId } : {}
3913
+ };
3914
+ const proactiveOptions = {
3915
+ ...threadActivityId ? { threadActivityId } : {},
3916
+ serviceUrlBoundary
3917
+ };
3918
+ const sendActivity = (outbound) => require_messenger.sendMSTeamsActivityWithReference(app, reference, outbound, proactiveOptions);
3919
+ return {
3920
+ activity,
3921
+ sendActivity,
3922
+ sendActivities: async (activities) => {
3923
+ const results = [];
3924
+ for (const outbound of activities) results.push(await sendActivity(outbound));
3925
+ return results;
3926
+ },
3927
+ updateActivity: async (outbound) => await require_messenger.updateMSTeamsActivityWithReference(app, reference, typeof outbound.id === "string" ? outbound.id : "", outbound, proactiveOptions),
3928
+ deleteActivity: async (activityId) => {
3929
+ await require_messenger.deleteMSTeamsActivityWithReference(app, reference, activityId, proactiveOptions);
3930
+ },
3931
+ getTeamDetails: (teamId) => app.api.teams.getById(teamId)
3932
+ };
3933
+ }
3790
3934
  /**
3791
3935
  * Adapt a new @microsoft/teams.apps SDK context to the MSTeamsTurnContext interface
3792
3936
  * our handlers expect. The new SDK uses reply()/send() instead of sendActivity().