@openclaw/msteams 2026.9.6 → 2026.9.8

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.
Files changed (33) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{adapter.runtime-CQWZeqB5.cjs → adapter.runtime-BiWeMKXE.cjs} +21 -29
  3. package/dist/.setup/{approval-card-CPNH_q3P.cjs → approval-card-DWPDItvq.cjs} +6 -3
  4. package/dist/.setup/{approval-handler.runtime-CUnanUdR.cjs → approval-handler.runtime-D1s7UR3U.cjs} +4 -4
  5. package/dist/.setup/bot-framework-service-url-BUhTe8F3.cjs +55 -0
  6. package/dist/.setup/{channel-BDp16XRR.cjs → channel-ByzS7QJ0.cjs} +286 -180
  7. package/dist/.setup/{channel.runtime-Cv5F8JCa.cjs → channel.runtime-Baz8XDFs.cjs} +43 -142
  8. package/dist/.setup/{channel.setup-BVDfgdCc.cjs → channel.setup-DL8lcWT_.cjs} +6 -6
  9. package/dist/.setup/{config-schema-D22Lr1Qk.cjs → config-schema-0KoBzb0t.cjs} +24 -38
  10. package/dist/.setup/{messenger-8RA1FUJa.cjs → conversation-store-state-B7JuyZTm.cjs} +693 -287
  11. package/dist/.setup/{src-BfOCwlHp.cjs → monitor-BJ7IyH4y.cjs} +422 -592
  12. package/dist/.setup/{oauth-C6m7dvHj.cjs → oauth-DQjOnJ4l.cjs} +1 -1
  13. package/dist/.setup/{reaction-types-C7RA7Wxt.cjs → reaction-types-CQ4oSUe7.cjs} +2 -6
  14. package/dist/.setup/{resolve-allowlist-CHzaaWiC.cjs → resolve-allowlist-Pl_NQFYH.cjs} +55 -312
  15. package/dist/.setup/{rolldown-runtime-VH7oDXx4.cjs → rolldown-runtime-BloeesfJ.cjs} +22 -0
  16. package/dist/.setup/{send-CYN_N7O1.cjs → send-VUM7Sryb.cjs} +43 -96
  17. package/dist/.setup/{setup-plugin-api-DpiaA8z1.cjs → setup-plugin-api-Dyf8kF-l.cjs} +1 -1
  18. package/dist/.setup/{thread-session-CXgHK-yi.cjs → thread-session--rw3BTal.cjs} +2 -5
  19. package/dist/api.cjs +2 -2
  20. package/dist/channel-config-api.cjs +1 -1
  21. package/dist/channel-plugin-api.cjs +1 -1
  22. package/dist/configured-state.cjs +0 -1
  23. package/dist/directory-contract-api.cjs +1 -1
  24. package/dist/doctor-contract-api.cjs +201 -371
  25. package/dist/qa-runner-api.cjs +1 -1
  26. package/dist/runtime-api.cjs +0 -6
  27. package/dist/setup-entry.cjs +1 -1
  28. package/dist/setup-plugin-api.cjs +1 -1
  29. package/openclaw.plugin.json +46 -5
  30. package/package.json +9 -9
  31. package/dist/.setup/polls-BJ4-4uZm.cjs +0 -668
  32. package/dist/.setup/secret-input-C65t6kjM.cjs +0 -1
  33. package/dist/.setup/sso-token-store-YYLHXgFH.cjs +0 -111
@@ -1,44 +1,40 @@
1
- const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
1
+ const require_rolldown_runtime = require("./rolldown-runtime-BloeesfJ.cjs");
2
2
  const require_runtime = require("./runtime-gZTAuFux.cjs");
3
3
  require("../runtime-api.cjs");
4
- const require_resolve_allowlist = require("./resolve-allowlist-CHzaaWiC.cjs");
5
- const require_channel_setup = require("./channel.setup-BVDfgdCc.cjs");
6
- const require_polls = require("./polls-BJ4-4uZm.cjs");
4
+ const require_resolve_allowlist = require("./resolve-allowlist-Pl_NQFYH.cjs");
5
+ const require_bot_framework_service_url = require("./bot-framework-service-url-BUhTe8F3.cjs");
6
+ const require_channel_setup = require("./channel.setup-DL8lcWT_.cjs");
7
7
  let openclaw_plugin_sdk_lazy_runtime = require("openclaw/plugin-sdk/lazy-runtime");
8
8
  let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
9
9
  let openclaw_plugin_sdk_fetch_runtime = require("openclaw/plugin-sdk/fetch-runtime");
10
10
  let openclaw_plugin_sdk_provider_http = require("openclaw/plugin-sdk/provider-http");
11
+ let openclaw_plugin_sdk_file_lock = require("openclaw/plugin-sdk/file-lock");
11
12
  let openclaw_plugin_sdk_reply_payload = require("openclaw/plugin-sdk/reply-payload");
12
13
  let openclaw_plugin_sdk_ssrf_runtime = require("openclaw/plugin-sdk/ssrf-runtime");
13
14
  let openclaw_plugin_sdk_text_chunking = require("openclaw/plugin-sdk/text-chunking");
14
15
  let node_dns_promises = require("node:dns/promises");
15
16
  let openclaw_plugin_sdk_ssrf_policy = require("openclaw/plugin-sdk/ssrf-policy");
17
+ let openclaw_plugin_sdk_number_runtime = require("openclaw/plugin-sdk/number-runtime");
16
18
  let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text-utility-runtime");
17
19
  let openclaw_plugin_sdk_error_runtime = require("openclaw/plugin-sdk/error-runtime");
18
20
  let openclaw_plugin_sdk_retry_runtime = require("openclaw/plugin-sdk/retry-runtime");
19
- let node_crypto = require("node:crypto");
20
- node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
21
21
  let node_path = require("node:path");
22
22
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
23
23
  let openclaw_plugin_sdk_runtime_env = require("openclaw/plugin-sdk/runtime-env");
24
24
  let openclaw_plugin_sdk_markdown_table_runtime = require("openclaw/plugin-sdk/markdown-table-runtime");
25
25
  let openclaw_plugin_sdk_blob_runtime = require("openclaw/plugin-sdk/blob-runtime");
26
+ let node_crypto = require("node:crypto");
27
+ node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
28
+ let openclaw_plugin_sdk_keyed_async_queue = require("openclaw/plugin-sdk/keyed-async-queue");
26
29
  let openclaw_plugin_sdk_global_singleton = require("openclaw/plugin-sdk/global-singleton");
27
30
  let openclaw_plugin_sdk_expect_runtime = require("openclaw/plugin-sdk/expect-runtime");
28
31
  let openclaw_plugin_sdk_reply_chunking = require("openclaw/plugin-sdk/reply-chunking");
29
32
  let openclaw_plugin_sdk_web_media = require("openclaw/plugin-sdk/web-media");
33
+ let openclaw_plugin_sdk_dedupe_runtime = require("openclaw/plugin-sdk/dedupe-runtime");
34
+ let openclaw_plugin_sdk_plugin_state_runtime = require("openclaw/plugin-sdk/plugin-state-runtime");
30
35
  let openclaw_plugin_sdk_media_runtime = require("openclaw/plugin-sdk/media-runtime");
31
36
  //#region extensions/msteams/src/file-consent.ts
32
37
  /**
33
- * FileConsentCard utilities for MS Teams large file uploads (>4MB) in personal chats.
34
- *
35
- * Teams requires user consent before the bot can upload large files. This module provides
36
- * utilities for:
37
- * - Building FileConsentCard attachments (to request upload permission)
38
- * - Building FileInfoCard attachments (to confirm upload completion)
39
- * - Parsing fileConsent/invoke activities
40
- */
41
- /**
42
38
  * Allowlist of domains that are valid targets for file consent uploads.
43
39
  * These are the Microsoft/SharePoint domains that Teams legitimately provides
44
40
  * as upload destinations in the FileConsentCard flow.
@@ -58,11 +54,6 @@ const CONSENT_UPLOAD_HOST_ALLOWLIST = [
58
54
  "graph.microsoft.cn"
59
55
  ];
60
56
  /**
61
- * Returns true if the given IPv4 or IPv6 address is private, internal, or
62
- * special-use and must never be reached via consent uploads.
63
- */
64
- const isPrivateOrReservedIP = openclaw_plugin_sdk_ssrf_policy.isPrivateIpAddress;
65
- /**
66
57
  * Validate that a consent upload URL is safe to PUT to.
67
58
  * Checks:
68
59
  * 1. Protocol is HTTPS
@@ -89,7 +80,7 @@ async function validateConsentUploadUrl(url, opts) {
89
80
  } catch {
90
81
  throw new Error(`Failed to resolve consent upload URL hostname "${hostname}"`);
91
82
  }
92
- for (const entry of resolved) if (isPrivateOrReservedIP(entry.address)) throw new Error(`Consent upload URL resolves to a private/reserved IP (${entry.address})`);
83
+ for (const entry of resolved) if ((0, openclaw_plugin_sdk_ssrf_policy.isPrivateIpAddress)(entry.address)) throw new Error(`Consent upload URL resolves to a private/reserved IP (${entry.address})`);
93
84
  }
94
85
  /**
95
86
  * Build a FileConsentCard attachment for requesting upload permission.
@@ -164,6 +155,47 @@ async function uploadToConsentUrl(params) {
164
155
  if (!res.ok) throw new Error(`File upload to consent URL failed: ${res.status} ${res.statusText}`);
165
156
  }
166
157
  //#endregion
158
+ //#region extensions/msteams/src/sqlite-state.ts
159
+ function resolveStateDirOverride(options) {
160
+ if (!options) return;
161
+ if (options.stateDir) return options.stateDir;
162
+ if (options.storePath) return node_path.default.dirname(options.storePath);
163
+ if (options.homedir) return require_runtime.getMSTeamsRuntime().state.resolveStateDir(options.env ?? process.env, options.homedir);
164
+ return options.env?.OPENCLAW_STATE_DIR?.trim() || void 0;
165
+ }
166
+ function resolveMSTeamsSqliteStateEnv(options) {
167
+ const stateDir = resolveStateDirOverride(options);
168
+ if (!stateDir) return options?.env;
169
+ return {
170
+ ...options?.env ?? process.env,
171
+ OPENCLAW_STATE_DIR: stateDir
172
+ };
173
+ }
174
+ function toPluginJsonValue(value) {
175
+ const serialized = JSON.stringify(value);
176
+ return JSON.parse(serialized);
177
+ }
178
+ function resolveMSTeamsSqliteStateDir(options) {
179
+ return resolveStateDirOverride(options) ?? require_runtime.getMSTeamsRuntime().state.resolveStateDir(options?.env ?? process.env, options?.homedir);
180
+ }
181
+ const sqliteMutationLocks = new openclaw_plugin_sdk_keyed_async_queue.KeyedAsyncQueue();
182
+ const MSTEAMS_MUTATION_LOCK_OPTIONS = {
183
+ retries: {
184
+ retries: 10,
185
+ factor: 2,
186
+ minTimeout: 100,
187
+ maxTimeout: 1e4,
188
+ randomize: true
189
+ },
190
+ stale: 3e4
191
+ };
192
+ async function withMSTeamsSqliteMutationLock(options, mutationKey, fn) {
193
+ const scopedMutationKey = node_path.default.join(resolveMSTeamsSqliteStateDir(options), mutationKey);
194
+ return await sqliteMutationLocks.enqueue(scopedMutationKey, async () => {
195
+ return await (0, openclaw_plugin_sdk_file_lock.withFileLock)(scopedMutationKey, MSTEAMS_MUTATION_LOCK_OPTIONS, fn);
196
+ });
197
+ }
198
+ //#endregion
167
199
  //#region extensions/msteams/src/pending-uploads-fs.ts
168
200
  /** TTL for persisted pending uploads (matches in-memory store). */
169
201
  const PENDING_UPLOAD_TTL_MS$1 = 3e5;
@@ -180,14 +212,14 @@ function createMetaStore(options) {
180
212
  return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
181
213
  namespace: PENDING_UPLOAD_META_NAMESPACE,
182
214
  maxEntries: PENDING_UPLOAD_META_MAX_ENTRIES,
183
- env: require_polls.resolveMSTeamsSqliteStateEnv(options)
215
+ env: resolveMSTeamsSqliteStateEnv(options)
184
216
  });
185
217
  }
186
218
  function createChunkStore(options) {
187
219
  return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
188
220
  namespace: PENDING_UPLOAD_CHUNKS_NAMESPACE,
189
221
  maxEntries: MAX_PENDING_UPLOAD_CHUNK_ROWS,
190
- env: require_polls.resolveMSTeamsSqliteStateEnv(options)
222
+ env: resolveMSTeamsSqliteStateEnv(options)
191
223
  });
192
224
  }
193
225
  function buildUploadKey(id) {
@@ -217,22 +249,21 @@ async function deleteUploadRows(id, metaStore, chunkStore) {
217
249
  const chunkCount = existing.chunkCount;
218
250
  for (let index = 0; index < chunkCount; index += 1) await chunkStore.delete(buildChunkKey(id, index));
219
251
  }
220
- async function registerUploadRows(record, metaStore, chunkStore, ttlMs, overwrite) {
221
- const buffer = Buffer.from(record.bufferBase64, "base64");
252
+ async function registerUploadRows(record, metaStore, chunkStore, ttlMs) {
253
+ const buffer = Buffer.from(record.buffer);
222
254
  const chunkCount = Math.max(1, Math.ceil(buffer.byteLength / RAW_CHUNK_BYTES));
223
255
  if (chunkCount > MAX_CHUNKS_PER_UPLOAD) throw new Error(`Microsoft Teams pending upload ${record.id} exceeds SQLite chunk limit (${chunkCount}/${MAX_CHUNKS_PER_UPLOAD})`);
224
- if (overwrite) await deleteUploadRows(record.id, metaStore, chunkStore);
225
- else if (await metaStore.lookup(buildMetaKey(record.id))) return;
256
+ await deleteUploadRows(record.id, metaStore, chunkStore);
226
257
  await pruneUploadStore(metaStore, chunkStore, ttlMs, chunkCount);
227
258
  for (let index = 0; index < chunkCount; index += 1) {
228
259
  const chunk = buffer.subarray(index * RAW_CHUNK_BYTES, (index + 1) * RAW_CHUNK_BYTES);
229
- await chunkStore.register(buildChunkKey(record.id, index), require_polls.toPluginJsonValue({
260
+ await chunkStore.register(buildChunkKey(record.id, index), toPluginJsonValue({
230
261
  id: record.id,
231
262
  index,
232
263
  dataBase64: chunk.toString("base64")
233
264
  }));
234
265
  }
235
- await metaStore.register(buildMetaKey(record.id), require_polls.toPluginJsonValue({
266
+ await metaStore.register(buildMetaKey(record.id), toPluginJsonValue({
236
267
  id: record.id,
237
268
  filename: record.filename,
238
269
  contentType: record.contentType,
@@ -244,7 +275,7 @@ async function registerUploadRows(record, metaStore, chunkStore, ttlMs, overwrit
244
275
  }));
245
276
  }
246
277
  async function withPendingUploadLock(options, run) {
247
- return await require_polls.withMSTeamsSqliteMutationLock(options, PENDING_UPLOAD_MUTATION_KEY, run);
278
+ return await withMSTeamsSqliteMutationLock(options, PENDING_UPLOAD_MUTATION_KEY, run);
248
279
  }
249
280
  async function readUploadRows(id, metaStore, chunkStore) {
250
281
  const meta = await metaStore.lookup(buildMetaKey(id));
@@ -295,19 +326,16 @@ async function storePendingUploadFs(upload, options) {
295
326
  await withPendingUploadLock(options, async () => {
296
327
  await registerUploadRows({
297
328
  id: upload.id,
298
- bufferBase64: upload.buffer.toString("base64"),
329
+ buffer: upload.buffer,
299
330
  filename: upload.filename,
300
331
  contentType: upload.contentType,
301
332
  conversationId: upload.conversationId,
302
333
  consentCardActivityId: upload.consentCardActivityId,
303
334
  createdAt: Date.now()
304
- }, metaStore, chunkStore, ttlMs, true);
335
+ }, metaStore, chunkStore, ttlMs);
305
336
  await pruneUploadStore(metaStore, chunkStore, ttlMs);
306
337
  });
307
338
  }
308
- /**
309
- * Retrieve a persisted pending upload. Expired entries are treated as absent.
310
- */
311
339
  async function getPendingUploadFs(id, options) {
312
340
  if (!id) return;
313
341
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
@@ -319,10 +347,6 @@ async function getPendingUploadFs(id, options) {
319
347
  }
320
348
  return upload;
321
349
  }
322
- /**
323
- * Remove a persisted pending upload (after successful upload or decline).
324
- * No-op if the entry is already gone.
325
- */
326
350
  async function removePendingUploadFs(id, options) {
327
351
  if (!id) return;
328
352
  const metaStore = createMetaStore(options);
@@ -331,17 +355,13 @@ async function removePendingUploadFs(id, options) {
331
355
  await deleteUploadRows(id, metaStore, chunkStore);
332
356
  });
333
357
  }
334
- /**
335
- * Set the consent card activity ID on a persisted entry. Called after the
336
- * FileConsentCard activity is sent and we know its message id.
337
- */
338
358
  async function setPendingUploadActivityIdFs(id, activityId, options) {
339
359
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
340
360
  const metaStore = createMetaStore(options);
341
361
  await withPendingUploadLock(options, async () => {
342
362
  const record = await metaStore.lookup(buildMetaKey(id));
343
363
  if (!record || Date.now() - record.createdAt > ttlMs) return;
344
- await metaStore.register(buildMetaKey(id), require_polls.toPluginJsonValue({
364
+ await metaStore.register(buildMetaKey(id), toPluginJsonValue({
345
365
  ...record,
346
366
  consentCardActivityId: activityId
347
367
  }));
@@ -365,7 +385,6 @@ const { pendingUploads, pendingUploadTimers } = (0, openclaw_plugin_sdk_global_s
365
385
  state.pendingUploadTimers.clear();
366
386
  state.pendingUploads.clear();
367
387
  });
368
- /** TTL for pending uploads: 5 minutes */
369
388
  const PENDING_UPLOAD_TTL_MS = 3e5;
370
389
  /**
371
390
  * Store a file pending user consent.
@@ -386,21 +405,12 @@ function storePendingUpload(upload) {
386
405
  pendingUploadTimers.set(id, timer);
387
406
  return id;
388
407
  }
389
- /**
390
- * Retrieve a pending upload by ID.
391
- * Returns undefined if not found or expired.
392
- */
393
408
  function getPendingUpload(id) {
394
409
  if (!id) return;
395
410
  const entry = pendingUploads.get(id);
396
411
  if (!entry) return;
397
412
  if (Date.now() - entry.createdAt > PENDING_UPLOAD_TTL_MS) {
398
- pendingUploads.delete(id);
399
- const timer = pendingUploadTimers.get(id);
400
- if (timer !== void 0) {
401
- clearTimeout(timer);
402
- pendingUploadTimers.delete(id);
403
- }
413
+ removePendingUpload(id);
404
414
  return;
405
415
  }
406
416
  return entry;
@@ -419,10 +429,6 @@ function removePendingUpload(id) {
419
429
  }
420
430
  }
421
431
  }
422
- /**
423
- * Set the consent card activity ID on an existing pending upload.
424
- * Called after the FileConsentCard is sent and we know its activity ID.
425
- */
426
432
  function setPendingUploadActivityId(uploadId, activityId) {
427
433
  const entry = pendingUploads.get(uploadId);
428
434
  if (entry) entry.consentCardActivityId = activityId;
@@ -769,14 +775,15 @@ function formatMSTeamsMarkdown(markdown, tableMode) {
769
775
  const index = escapedMarkdown.push(escaped) - 1;
770
776
  return `${tokenPrefix}e${index}${TOKEN_END}`;
771
777
  });
772
- const ir = prefixMSTeamsBlockquotes(protectMSTeamsCode((0, openclaw_plugin_sdk_text_chunking.markdownToIR)(protectedMarkdown, {
778
+ const parsed = (0, openclaw_plugin_sdk_text_chunking.markdownToIR)(protectedMarkdown, {
773
779
  autolink: false,
774
780
  enableSpoilers: true,
775
781
  enableTaskLists: true,
776
782
  headingStyle: "rich",
777
783
  linkify: false,
778
784
  blockquotePrefix: ""
779
- }), tokenPrefix, codeRegions, [
785
+ });
786
+ const protectedValues = [
780
787
  {
781
788
  prefix: `${tokenPrefix}e`,
782
789
  values: escapedMarkdown
@@ -797,8 +804,9 @@ function formatMSTeamsMarkdown(markdown, tableMode) {
797
804
  prefix: `${tokenPrefix}h`,
798
805
  values: entities
799
806
  }
800
- ]));
801
- let restored = restoreTokens((0, openclaw_plugin_sdk_text_chunking.renderMarkdownWithMarkers)(ir, {
807
+ ];
808
+ const ir = prefixMSTeamsBlockquotes(protectMSTeamsCode(parsed, tokenPrefix, codeRegions, protectedValues));
809
+ const rendered = (0, openclaw_plugin_sdk_text_chunking.renderMarkdownWithMarkers)(ir, {
802
810
  styleMarkers: MSTEAMS_MARKERS,
803
811
  escapeText: (text) => text,
804
812
  buildLink: (link) => ({
@@ -807,12 +815,8 @@ function formatMSTeamsMarkdown(markdown, tableMode) {
807
815
  open: "[",
808
816
  close: `](${serializeMarkdownDestination(link.href)})`
809
817
  })
810
- }, MSTEAMS_FORMAT_CAPABILITIES), `${tokenPrefix}c`, codeRegions);
811
- restored = restoreTokens(restored, `${tokenPrefix}e`, escapedMarkdown);
812
- restored = restoreTokens(restored, `${tokenPrefix}t`, rawTables);
813
- restored = restoreTokens(restored, `${tokenPrefix}m`, mentions);
814
- restored = restoreTokens(restored, `${tokenPrefix}i`, images);
815
- return restoreTokens(restored, `${tokenPrefix}h`, entities);
818
+ }, MSTEAMS_FORMAT_CAPABILITIES);
819
+ return protectedValues.reduce((text, { prefix, values }) => restoreTokens(text, prefix, values), restoreTokens(rendered, `${tokenPrefix}c`, codeRegions));
816
820
  }
817
821
  //#endregion
818
822
  //#region extensions/msteams/src/graph-chat.ts
@@ -821,43 +825,18 @@ function buildTeamsFileInfoCard(file) {
821
825
  const uniqueId = rawETag.replace(/^["']|["']$/g, "").replace(/[{}]/g, "").split(",")[0] ?? rawETag;
822
826
  const lastDot = file.name.lastIndexOf(".");
823
827
  const fileType = lastDot >= 0 ? (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(file.name.slice(lastDot + 1)) : "";
824
- return {
825
- contentType: "application/vnd.microsoft.teams.card.file.info",
828
+ return buildFileInfoCard({
829
+ filename: file.name,
826
830
  contentUrl: file.webDavUrl,
827
- name: file.name,
828
- content: {
829
- uniqueId,
830
- fileType
831
- }
832
- };
831
+ uniqueId,
832
+ fileType
833
+ });
833
834
  }
834
835
  //#endregion
835
836
  //#region extensions/msteams/src/graph-upload.ts
836
- /**
837
- * SharePoint upload utilities for MS Teams file sending.
838
- *
839
- * For group chats and channels, files are uploaded to SharePoint and shared via a link.
840
- * This module provides utilities for:
841
- * - Uploading files to SharePoint (group/channel scope)
842
- * - Creating sharing links (organization-wide or per-user)
843
- * - Getting chat members for per-user sharing
844
- */
845
837
  const GRAPH_ROOT = "https://graph.microsoft.com/v1.0";
846
838
  const GRAPH_BETA = "https://graph.microsoft.com/beta";
847
839
  const GRAPH_SCOPE = "https://graph.microsoft.com";
848
- async function requestSharePoint(url, init, params) {
849
- const { response, release } = await (0, openclaw_plugin_sdk_ssrf_runtime.fetchWithSsrFGuard)({
850
- url,
851
- init,
852
- fetchImpl: params.fetchFn,
853
- mode: "trusted_env_proxy",
854
- beforeRequest: () => require_resolve_allowlist.assertMSTeamsSendHandoff(params),
855
- maxRedirects: 20,
856
- allowCrossOriginUnsafeRedirectReplay: true,
857
- auditContext: "msteams.graph-upload"
858
- });
859
- return (0, openclaw_plugin_sdk_fetch_runtime.responseWithRelease)(response, release);
860
- }
861
840
  function requireMSTeamsSharePointSiteId(siteId) {
862
841
  const normalized = siteId?.trim();
863
842
  if (!normalized) throw new Error("channels.msteams.sharePointSiteId is required to send files to group chats or channels");
@@ -866,43 +845,55 @@ function requireMSTeamsSharePointSiteId(siteId) {
866
845
  const SHAREPOINT_REQUEST_TIMEOUT_LABEL = "MS Teams SharePoint request";
867
846
  const SHAREPOINT_UPLOAD_TIMEOUT_LABEL = "MS Teams SharePoint upload";
868
847
  const GRAPH_TOKEN_TIMEOUT_LABEL = "MS Teams Graph token acquisition";
869
- async function getGraphAccessToken(tokenProvider) {
870
- return await require_resolve_allowlist.withMSTeamsRequestDeadline({
871
- label: GRAPH_TOKEN_TIMEOUT_LABEL,
872
- work: async () => await tokenProvider.getAccessToken(GRAPH_SCOPE)
873
- });
874
- }
875
- /**
876
- * Upload a file to a SharePoint site.
877
- * This is used for group chats and channels where /me/drive doesn't work for bots.
878
- *
879
- * @param params.siteId - SharePoint site ID (e.g., "contoso.sharepoint.com,guid1,guid2")
880
- */
881
- async function uploadToSharePoint(params) {
882
- const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`;
883
- const uploadUrl = `${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content?@microsoft.graph.conflictBehavior=rename`;
884
- const timeoutMs = require_resolve_allowlist.resolveMSTeamsSharePointUploadTimeoutMs(params.buffer.length);
885
- const data = await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
886
- label: SHAREPOINT_UPLOAD_TIMEOUT_LABEL,
887
- timeoutMs,
848
+ async function requestSharePointJson(params, request) {
849
+ return await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
850
+ label: request.timeoutMs !== void 0 ? SHAREPOINT_UPLOAD_TIMEOUT_LABEL : SHAREPOINT_REQUEST_TIMEOUT_LABEL,
851
+ timeoutMs: request.timeoutMs,
888
852
  work: async (signal) => {
889
853
  require_resolve_allowlist.assertMSTeamsSendHandoff(params);
890
- const token = await getGraphAccessToken(params.tokenProvider);
854
+ const token = await require_resolve_allowlist.withMSTeamsRequestDeadline({
855
+ label: GRAPH_TOKEN_TIMEOUT_LABEL,
856
+ work: () => params.tokenProvider.getAccessToken(GRAPH_SCOPE)
857
+ });
891
858
  require_resolve_allowlist.assertMSTeamsSendHandoff(params);
892
- const res = await requestSharePoint(uploadUrl, {
893
- method: "PUT",
894
- headers: {
895
- "User-Agent": require_resolve_allowlist.buildUserAgent(),
896
- Authorization: `Bearer ${token}`,
897
- "Content-Type": params.contentType ?? "application/octet-stream"
859
+ const init = request.init?.();
860
+ const { response, release } = await (0, openclaw_plugin_sdk_ssrf_runtime.fetchWithSsrFGuard)({
861
+ url: request.url,
862
+ init: {
863
+ ...init,
864
+ headers: {
865
+ "User-Agent": require_resolve_allowlist.buildUserAgent(),
866
+ Authorization: `Bearer ${token}`,
867
+ ...init?.headers
868
+ },
869
+ signal
898
870
  },
899
- body: new Blob([(0, openclaw_plugin_sdk_blob_runtime.bufferToBlobPart)(params.buffer)]),
900
- signal
901
- }, params);
902
- if (!res.ok) throw await require_resolve_allowlist.createMSTeamsHttpError(res, "SharePoint upload failed");
903
- return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.uploadSharePointFile", { chunkTimeoutMs: timeoutMs });
871
+ fetchImpl: params.fetchFn,
872
+ mode: "trusted_env_proxy",
873
+ beforeRequest: () => require_resolve_allowlist.assertMSTeamsSendHandoff(params),
874
+ maxRedirects: 20,
875
+ allowCrossOriginUnsafeRedirectReplay: true,
876
+ auditContext: "msteams.graph-upload"
877
+ });
878
+ const res = (0, openclaw_plugin_sdk_fetch_runtime.responseWithRelease)(response, release);
879
+ if (!res.ok) throw await (0, openclaw_plugin_sdk_provider_http.createProviderHttpError)(res, typeof request.error === "string" ? request.error : request.error(res));
880
+ return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, request.label, { chunkTimeoutMs: request.timeoutMs });
904
881
  }
905
882
  });
883
+ }
884
+ async function uploadToSharePoint(params) {
885
+ const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`;
886
+ const data = await requestSharePointJson(params, {
887
+ url: `${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content?@microsoft.graph.conflictBehavior=rename`,
888
+ timeoutMs: require_resolve_allowlist.resolveMSTeamsSharePointUploadTimeoutMs(params.buffer.length),
889
+ init: () => ({
890
+ method: "PUT",
891
+ headers: { "Content-Type": params.contentType ?? "application/octet-stream" },
892
+ body: new Blob([(0, openclaw_plugin_sdk_blob_runtime.bufferToBlobPart)(params.buffer)])
893
+ }),
894
+ error: "SharePoint upload failed",
895
+ label: "msteams.graph-upload.uploadSharePointFile"
896
+ });
906
897
  if (!data.id || !data.webUrl || !data.name) throw new Error("SharePoint upload response missing required fields");
907
898
  return {
908
899
  id: data.id,
@@ -910,30 +901,11 @@ async function uploadToSharePoint(params) {
910
901
  name: data.name
911
902
  };
912
903
  }
913
- /**
914
- * Get driveItem properties needed for native Teams file card attachments.
915
- * This fetches the eTag and webDavUrl which are required for "reference" type attachments.
916
- *
917
- * @param params.siteId - SharePoint site ID
918
- * @param params.itemId - The driveItem ID (returned from upload)
919
- */
920
904
  async function getDriveItemProperties(params) {
921
- const data = await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
922
- label: SHAREPOINT_REQUEST_TIMEOUT_LABEL,
923
- work: async (signal) => {
924
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
925
- const token = await getGraphAccessToken(params.tokenProvider);
926
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
927
- const res = await requestSharePoint(`${GRAPH_ROOT}/sites/${params.siteId}/drive/items/${params.itemId}?$select=eTag,webDavUrl,name`, {
928
- headers: {
929
- "User-Agent": require_resolve_allowlist.buildUserAgent(),
930
- Authorization: `Bearer ${token}`
931
- },
932
- signal
933
- }, params);
934
- if (!res.ok) throw await require_resolve_allowlist.createMSTeamsHttpError(res, "Get driveItem properties failed");
935
- return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.getDriveItemProperties");
936
- }
905
+ const data = await requestSharePointJson(params, {
906
+ url: `${GRAPH_ROOT}/sites/${params.siteId}/drive/items/${params.itemId}?$select=eTag,webDavUrl,name`,
907
+ error: "Get driveItem properties failed",
908
+ label: "msteams.graph-upload.getDriveItemProperties"
937
909
  });
938
910
  if (!data.eTag || !data.webDavUrl || !data.name) throw new Error("DriveItem response missing required properties (eTag, webDavUrl, or name)");
939
911
  return {
@@ -942,31 +914,12 @@ async function getDriveItemProperties(params) {
942
914
  name: data.name
943
915
  };
944
916
  }
945
- /**
946
- * Get members of a Teams chat for per-user sharing.
947
- * Used to create sharing links scoped to only the chat participants.
948
- */
949
- async function getChatMembers(params) {
950
- return await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
951
- label: SHAREPOINT_REQUEST_TIMEOUT_LABEL,
952
- work: async (signal) => {
953
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
954
- const token = await getGraphAccessToken(params.tokenProvider);
955
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
956
- const res = await requestSharePoint(`${GRAPH_ROOT}/chats/${params.chatId}/members`, {
957
- headers: {
958
- "User-Agent": require_resolve_allowlist.buildUserAgent(),
959
- Authorization: `Bearer ${token}`
960
- },
961
- signal
962
- }, params);
963
- if (!res.ok) {
964
- const message = res.status === 403 ? "Get chat members failed; verify Graph chat-member permissions and tenant access policies" : "Get chat members failed";
965
- throw await require_resolve_allowlist.createMSTeamsHttpError(res, message);
966
- }
967
- return ((await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.getChatMembers")).value ?? []).map((member) => ({ aadObjectId: member.userId ?? "" })).filter((member) => member.aadObjectId);
968
- }
969
- });
917
+ async function getChatMemberIds(params) {
918
+ return ((await requestSharePointJson(params, {
919
+ url: `${GRAPH_ROOT}/chats/${params.chatId}/members`,
920
+ error: (res) => res.status === 403 ? "Get chat members failed; verify Graph chat-member permissions and tenant access policies" : "Get chat members failed",
921
+ label: "msteams.graph-upload.getChatMembers"
922
+ })).value ?? []).flatMap((member) => member.userId ? [member.userId] : []);
970
923
  }
971
924
  /**
972
925
  * Create a sharing link for a SharePoint drive item.
@@ -981,63 +934,40 @@ async function createSharePointSharingLink(params) {
981
934
  scope: scope === "users" ? "users" : "organization"
982
935
  };
983
936
  if (scope === "users" && params.recipientObjectIds?.length) body.recipients = params.recipientObjectIds.map((id) => ({ objectId: id }));
984
- const data = await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
985
- label: SHAREPOINT_REQUEST_TIMEOUT_LABEL,
986
- work: async (signal) => {
987
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
988
- const token = await getGraphAccessToken(params.tokenProvider);
989
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
990
- const res = await requestSharePoint(`${apiRoot}/sites/${params.siteId}/drive/items/${params.itemId}/createLink`, {
991
- method: "POST",
992
- headers: {
993
- "User-Agent": require_resolve_allowlist.buildUserAgent(),
994
- Authorization: `Bearer ${token}`,
995
- "Content-Type": "application/json"
996
- },
997
- body: JSON.stringify(body),
998
- signal
999
- }, params);
1000
- if (!res.ok) throw await require_resolve_allowlist.createMSTeamsHttpError(res, "Create SharePoint sharing link failed");
1001
- return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.createSharePointSharingLink");
1002
- }
937
+ const data = await requestSharePointJson(params, {
938
+ url: `${apiRoot}/sites/${params.siteId}/drive/items/${params.itemId}/createLink`,
939
+ init: () => ({
940
+ method: "POST",
941
+ headers: { "Content-Type": "application/json" },
942
+ body: JSON.stringify(body)
943
+ }),
944
+ error: "Create SharePoint sharing link failed",
945
+ label: "msteams.graph-upload.createSharePointSharingLink"
1003
946
  });
1004
947
  if (!data.link?.webUrl) throw new Error("Create SharePoint sharing link response missing webUrl");
1005
- return { webUrl: data.link.webUrl };
948
+ return data.link.webUrl;
1006
949
  }
1007
950
  /**
1008
951
  * Upload a file to SharePoint and create a sharing link.
1009
952
  *
1010
953
  * For group chats, this creates a per-user sharing link scoped to chat members.
1011
954
  * For channels, this creates an organization-wide sharing link.
1012
- *
1013
- * @param params.siteId - SharePoint site ID
1014
- * @param params.chatId - Optional chat ID for per-user sharing (group chats)
1015
- * @param params.usePerUserSharing - Whether to use per-user sharing (requires beta API + chat-member read access)
1016
955
  */
1017
956
  async function uploadAndShareSharePoint(params) {
1018
- const uploaded = await uploadToSharePoint({
1019
- buffer: params.buffer,
1020
- filename: params.filename,
1021
- contentType: params.contentType,
1022
- tokenProvider: params.tokenProvider,
1023
- siteId: params.siteId,
1024
- fetchFn: params.fetchFn,
1025
- assertDirectAdapterHandoff: params.assertDirectAdapterHandoff
1026
- });
957
+ const uploaded = await uploadToSharePoint(params);
1027
958
  let scope = "organization";
1028
959
  let recipientObjectIds;
1029
960
  if (params.usePerUserSharing && params.chatId) {
1030
- const members = await getChatMembers({
961
+ recipientObjectIds = await getChatMemberIds({
1031
962
  chatId: params.chatId,
1032
963
  tokenProvider: params.tokenProvider,
1033
964
  fetchFn: params.fetchFn,
1034
965
  assertDirectAdapterHandoff: params.assertDirectAdapterHandoff
1035
966
  });
1036
- if (members.length === 0) throw new Error("MS Teams chat member lookup returned no recipients");
967
+ if (recipientObjectIds.length === 0) throw new Error("MS Teams chat member lookup returned no recipients");
1037
968
  scope = "users";
1038
- recipientObjectIds = members.map((member) => member.aadObjectId);
1039
969
  }
1040
- const shareLink = await createSharePointSharingLink({
970
+ const shareUrl = await createSharePointSharingLink({
1041
971
  siteId: params.siteId,
1042
972
  itemId: uploaded.id,
1043
973
  tokenProvider: params.tokenProvider,
@@ -1049,16 +979,13 @@ async function uploadAndShareSharePoint(params) {
1049
979
  return {
1050
980
  itemId: uploaded.id,
1051
981
  webUrl: uploaded.webUrl,
1052
- shareUrl: shareLink.webUrl,
982
+ shareUrl,
1053
983
  name: uploaded.name
1054
984
  };
1055
985
  }
1056
986
  //#endregion
1057
987
  //#region extensions/msteams/src/media-helpers.ts
1058
988
  /**
1059
- * MIME type detection and filename extraction for MSTeams media attachments.
1060
- */
1061
- /**
1062
989
  * Detect MIME type from URL extension or data URL.
1063
990
  * Uses shared MIME detection for consistency with core handling.
1064
991
  */
@@ -1097,25 +1024,16 @@ async function extractFilename(url) {
1097
1024
  return (0, openclaw_plugin_sdk_media_runtime.extractOriginalFilename)(url);
1098
1025
  }
1099
1026
  }
1100
- /**
1101
- * Check if a URL refers to a local file path.
1102
- */
1103
1027
  function isLocalPath(url) {
1104
- if (/^file:\/\//iu.test(url) || url.startsWith("/") || url.startsWith("~")) return true;
1105
- if (url.startsWith("\\") && !url.startsWith("\\\\")) return true;
1106
- if (/^[a-zA-Z]:[\\/]/.test(url)) return true;
1107
- if (url.startsWith("\\\\")) return true;
1108
- return false;
1028
+ return /^file:\/\//iu.test(url) || url.startsWith("/") || url.startsWith("~") || url.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(url);
1109
1029
  }
1110
1030
  /**
1111
1031
  * Extract the message ID from a Bot Framework response.
1112
1032
  */
1113
1033
  function extractMessageId(response) {
1114
- if (!response || typeof response !== "object") return null;
1115
- if (!("id" in response)) return null;
1034
+ if (!response || typeof response !== "object" || !("id" in response)) return null;
1116
1035
  const { id } = response;
1117
- if (typeof id !== "string" || !id) return null;
1118
- return id;
1036
+ return typeof id === "string" && id ? id : null;
1119
1037
  }
1120
1038
  //#endregion
1121
1039
  //#region extensions/msteams/src/mentions.ts
@@ -1183,6 +1101,15 @@ function buildMSTeamsMessageActivity(text) {
1183
1101
  entities: [...parsed.entities, AI_GENERATED_ENTITY]
1184
1102
  };
1185
1103
  }
1104
+ function buildMSTeamsAdaptiveCardActivity(card) {
1105
+ return {
1106
+ type: "message",
1107
+ attachments: [{
1108
+ contentType: "application/vnd.microsoft.card.adaptive",
1109
+ content: card
1110
+ }]
1111
+ };
1112
+ }
1186
1113
  //#endregion
1187
1114
  //#region extensions/msteams/src/revoked-context.ts
1188
1115
  async function withRevokedProxyFallback(params) {
@@ -1195,6 +1122,36 @@ async function withRevokedProxyFallback(params) {
1195
1122
  }
1196
1123
  }
1197
1124
  //#endregion
1125
+ //#region extensions/msteams/src/sent-message-cache.ts
1126
+ const sentMessages = (0, openclaw_plugin_sdk_dedupe_runtime.createPersistentDedupeCache)({
1127
+ globalKey: Symbol.for("openclaw.msteamsSentMessages"),
1128
+ ttlMs: 864e5,
1129
+ maxSize: 2e4,
1130
+ persistent: {
1131
+ namespace: "msteams.sent-messages",
1132
+ maxEntries: 1e3,
1133
+ openStore: (options) => require_runtime.getOptionalMSTeamsRuntime()?.state.openKeyedStore(options),
1134
+ logError: (0, openclaw_plugin_sdk_plugin_state_runtime.createPluginStateErrorReporter)(require_runtime.getOptionalMSTeamsRuntime, "msteams", "sent-message-state", "Microsoft Teams persistent sent-message state failed"),
1135
+ readTimestamp: (record) => record.sentAt
1136
+ }
1137
+ });
1138
+ function makeKey(conversationId, messageId, botId) {
1139
+ return botId ? JSON.stringify([
1140
+ botId,
1141
+ conversationId,
1142
+ messageId
1143
+ ]) : `${conversationId}:${messageId}`;
1144
+ }
1145
+ function recordMSTeamsSentMessage(conversationId, messageId, botId) {
1146
+ if (!conversationId || !messageId || messageId === "unknown") return;
1147
+ const sentAt = Date.now();
1148
+ sentMessages.register(makeKey(conversationId, messageId, botId), { sentAt }, { at: sentAt });
1149
+ }
1150
+ async function wasMSTeamsMessageSentWithPersistence(params) {
1151
+ if (!params.conversationId || !params.messageId) return false;
1152
+ return await sentMessages.lookup(makeKey(params.conversationId, params.messageId, params.botId));
1153
+ }
1154
+ //#endregion
1198
1155
  //#region extensions/msteams/src/sdk-proactive.ts
1199
1156
  const loadMSTeamsApiModule = (0, openclaw_plugin_sdk_lazy_runtime.createLazyRuntimeModule)(() => import("@microsoft/teams.api"));
1200
1157
  async function quoteMSTeamsActivity(activity, messageId) {
@@ -1204,12 +1161,12 @@ async function quoteMSTeamsActivity(activity, messageId) {
1204
1161
  return MessageActivityInput.from(activity).prependQuote(messageId);
1205
1162
  }
1206
1163
  function resolveThreadedConversationId(conversationId, threadActivityId) {
1207
- if (!threadActivityId) return conversationId.split(";")[0] ?? conversationId;
1208
- return `${conversationId.split(";")[0] ?? conversationId};messageid=${threadActivityId}`;
1164
+ const baseId = conversationId.split(";")[0] ?? conversationId;
1165
+ return threadActivityId ? `${baseId};messageid=${threadActivityId}` : baseId;
1209
1166
  }
1210
1167
  function normalizeRequiredServiceUrl(ref) {
1211
1168
  if (!ref.serviceUrl) throw new Error("Invalid stored reference: missing serviceUrl");
1212
- return require_resolve_allowlist.normalizeBotFrameworkServiceUrl(ref.serviceUrl);
1169
+ return require_bot_framework_service_url.normalizeBotFrameworkServiceUrl(ref.serviceUrl);
1213
1170
  }
1214
1171
  function buildSdkConversationReference(source, options) {
1215
1172
  const bot = source.agent ?? source.bot ?? void 0;
@@ -1244,13 +1201,10 @@ function buildSdkConversationReference(source, options) {
1244
1201
  ...source.aadObjectId ? { aadObjectId: source.aadObjectId } : {}
1245
1202
  };
1246
1203
  }
1247
- function getStructuralApiClient(app) {
1248
- return app.api;
1249
- }
1250
1204
  function sameServiceUrl(left, right) {
1251
1205
  if (!left) return false;
1252
1206
  try {
1253
- return require_resolve_allowlist.normalizeBotFrameworkServiceUrl(left) === right;
1207
+ return require_bot_framework_service_url.normalizeBotFrameworkServiceUrl(left) === right;
1254
1208
  } catch {
1255
1209
  return false;
1256
1210
  }
@@ -1262,7 +1216,7 @@ function stringifyReferenceFallbackActivity(activity) {
1262
1216
  return "";
1263
1217
  }
1264
1218
  async function getApiClientForReference(app, ref) {
1265
- const api = getStructuralApiClient(app);
1219
+ const api = app.api;
1266
1220
  if (sameServiceUrl(api.serviceUrl, ref.serviceUrl)) return api;
1267
1221
  const appInternals = app;
1268
1222
  const httpClient = appInternals.api?.http ?? appInternals.client;
@@ -1271,12 +1225,12 @@ async function getApiClientForReference(app, ref) {
1271
1225
  return new Client(ref.serviceUrl, httpClient);
1272
1226
  }
1273
1227
  function mergeReferenceIntoActivity(activity, ref) {
1274
- const source = activity && typeof activity === "object" && !Array.isArray(activity) ? activity : {
1228
+ const source = (0, openclaw_plugin_sdk_string_coerce_runtime.asOptionalRecord)(activity) ?? {
1275
1229
  type: "message",
1276
1230
  text: stringifyReferenceFallbackActivity(activity)
1277
1231
  };
1278
- const existingChannelData = source.channelData && typeof source.channelData === "object" && !Array.isArray(source.channelData) ? source.channelData : void 0;
1279
- const existingTenant = existingChannelData?.tenant && typeof existingChannelData.tenant === "object" && !Array.isArray(existingChannelData.tenant) ? existingChannelData.tenant : void 0;
1232
+ const existingChannelData = (0, openclaw_plugin_sdk_string_coerce_runtime.asOptionalRecord)(source.channelData);
1233
+ const existingTenant = (0, openclaw_plugin_sdk_string_coerce_runtime.asOptionalRecord)(existingChannelData?.tenant);
1280
1234
  let channelData = existingChannelData ? { ...existingChannelData } : void 0;
1281
1235
  if (ref.tenantId) {
1282
1236
  channelData ??= {};
@@ -1315,6 +1269,8 @@ async function sendMSTeamsActivityWithReference(app, source, activity, options)
1315
1269
  };
1316
1270
  }
1317
1271
  const res = isTargeted && activities.createTargeted ? await activities.createTargeted(activityWithRef) : await activities.create(activityWithRef);
1272
+ const conversationId = ref.conversation.id.split(";")[0] ?? ref.conversation.id;
1273
+ if (res.id && activityWithRef.type === "message" && ref.conversation.conversationType === "channel" && ref.conversation.id === conversationId) recordMSTeamsSentMessage(conversationId, res.id, ref.bot.id);
1318
1274
  return {
1319
1275
  ...activityWithRef,
1320
1276
  ...res
@@ -1341,9 +1297,6 @@ const MSTEAMS_MAX_MEDIA_BYTES = 104857600;
1341
1297
  * Files >= 4MB use consent flow; smaller images can use inline base64.
1342
1298
  */
1343
1299
  const FILE_CONSENT_THRESHOLD_BYTES = 4194304;
1344
- function normalizeConversationId(rawId) {
1345
- return rawId.split(";")[0] ?? rawId;
1346
- }
1347
1300
  function buildConversationReference(ref) {
1348
1301
  const conversationId = ref.conversation?.id?.trim();
1349
1302
  if (!conversationId) throw new Error("Invalid stored reference: missing conversation.id");
@@ -1361,7 +1314,7 @@ function buildConversationReference(ref) {
1361
1314
  } : user,
1362
1315
  agent,
1363
1316
  conversation: {
1364
- id: normalizeConversationId(conversationId),
1317
+ id: require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId),
1365
1318
  conversationType: ref.conversation?.conversationType,
1366
1319
  tenantId
1367
1320
  },
@@ -1374,17 +1327,11 @@ function buildConversationReference(ref) {
1374
1327
  }
1375
1328
  function pushTextMessages(out, text, opts) {
1376
1329
  if (!text) return;
1377
- if (opts.chunkText) {
1378
- for (const chunk of require_runtime.getMSTeamsRuntime().channel.text.chunkMarkdownTextWithMode(text, opts.chunkLimit, opts.chunkMode)) {
1379
- const trimmed = chunk.trim();
1380
- if (!trimmed || (0, openclaw_plugin_sdk_reply_chunking.isSilentReplyText)(trimmed, openclaw_plugin_sdk_reply_chunking.SILENT_REPLY_TOKEN)) continue;
1381
- out.push({ text: trimmed });
1382
- }
1383
- return;
1330
+ const chunks = opts.chunkText ? require_runtime.getMSTeamsRuntime().channel.text.chunkMarkdownTextWithMode(text, opts.chunkLimit, opts.chunkMode) : [text];
1331
+ for (const chunk of chunks) {
1332
+ const trimmed = chunk.trim();
1333
+ if (trimmed && !(0, openclaw_plugin_sdk_reply_chunking.isSilentReplyText)(trimmed, openclaw_plugin_sdk_reply_chunking.SILENT_REPLY_TOKEN)) out.push({ text: trimmed });
1384
1334
  }
1385
- const trimmed = text.trim();
1386
- if (!trimmed || (0, openclaw_plugin_sdk_reply_chunking.isSilentReplyText)(trimmed, openclaw_plugin_sdk_reply_chunking.SILENT_REPLY_TOKEN)) return;
1387
- out.push({ text: trimmed });
1388
1335
  }
1389
1336
  function clampMs(value, maxMs) {
1390
1337
  if (!Number.isFinite(value) || value < 0) return 0;
@@ -1399,9 +1346,9 @@ function resolveRetryOptions(retry) {
1399
1346
  };
1400
1347
  return {
1401
1348
  enabled: true,
1402
- maxAttempts: Math.max(1, retry?.maxAttempts ?? 3),
1403
- baseDelayMs: Math.max(0, retry?.baseDelayMs ?? 250),
1404
- maxDelayMs: Math.max(0, retry?.maxDelayMs ?? 1e4)
1349
+ maxAttempts: Math.max(1, retry.maxAttempts ?? 3),
1350
+ baseDelayMs: Math.max(0, retry.baseDelayMs ?? 250),
1351
+ maxDelayMs: Math.max(0, retry.maxDelayMs ?? 1e4)
1405
1352
  };
1406
1353
  }
1407
1354
  function computeRetryDelayMs(attempt, classification, opts) {
@@ -1421,37 +1368,20 @@ function renderReplyPayloadsToMessages(replies, options) {
1421
1368
  for (const payload of replies) {
1422
1369
  const reply = (0, openclaw_plugin_sdk_reply_payload.resolveSendableOutboundReplyParts)(payload, { text: formatMSTeamsMarkdown(payload.text ?? "", tableMode) });
1423
1370
  if (!reply.hasContent) continue;
1424
- if (!reply.hasMedia) {
1425
- pushTextMessages(out, reply.text, {
1426
- chunkText,
1427
- chunkLimit,
1428
- chunkMode
1371
+ const [firstMedia, ...remainingMedia] = reply.mediaUrls;
1372
+ if (mediaMode === "inline" && firstMedia) {
1373
+ out.push({
1374
+ text: reply.text || void 0,
1375
+ mediaUrl: firstMedia
1429
1376
  });
1430
- continue;
1431
- }
1432
- if (mediaMode === "inline") {
1433
- const firstMedia = reply.mediaUrls[0];
1434
- if (firstMedia) {
1435
- out.push({
1436
- text: reply.text || void 0,
1437
- mediaUrl: firstMedia
1438
- });
1439
- for (let i = 1; i < reply.mediaUrls.length; i++) if (reply.mediaUrls[i]) out.push({ mediaUrl: reply.mediaUrls[i] });
1440
- } else pushTextMessages(out, reply.text, {
1377
+ out.push(...remainingMedia.map((mediaUrl) => ({ mediaUrl })));
1378
+ } else {
1379
+ pushTextMessages(out, reply.text, {
1441
1380
  chunkText,
1442
1381
  chunkLimit,
1443
1382
  chunkMode
1444
1383
  });
1445
- continue;
1446
- }
1447
- pushTextMessages(out, reply.text, {
1448
- chunkText,
1449
- chunkLimit,
1450
- chunkMode
1451
- });
1452
- for (const mediaUrl of reply.mediaUrls) {
1453
- if (!mediaUrl) continue;
1454
- out.push({ mediaUrl });
1384
+ out.push(...reply.mediaUrls.map((mediaUrl) => ({ mediaUrl })));
1455
1385
  }
1456
1386
  }
1457
1387
  return out;
@@ -1614,9 +1544,8 @@ async function sendMSTeamsMessages(params) {
1614
1544
  fellBack: false
1615
1545
  }),
1616
1546
  onRevoked: async () => {
1617
- const remaining = messages.slice(idx);
1618
1547
  return {
1619
- ids: remaining.length > 0 ? await sendProactively(remaining, idx, resolvedThreadId) : [],
1548
+ ids: await sendProactively(messages.slice(idx), idx, resolvedThreadId),
1620
1549
  fellBack: true
1621
1550
  };
1622
1551
  }
@@ -1629,6 +1558,423 @@ async function sendMSTeamsMessages(params) {
1629
1558
  return await sendProactively(messages, 0);
1630
1559
  }
1631
1560
  //#endregion
1561
+ //#region extensions/msteams/src/polls.ts
1562
+ const MSTEAMS_POLLS_NAMESPACE = "polls";
1563
+ const MSTEAMS_POLL_VOTE_BUCKETS_NAMESPACE = "poll-vote-buckets";
1564
+ const MSTEAMS_MAX_POLLS = 1e3;
1565
+ const MSTEAMS_SQLITE_MAX_POLL_ROWS = 2e3;
1566
+ const MSTEAMS_POLL_VOTE_BUCKET_COUNT = 32;
1567
+ const MSTEAMS_MAX_POLL_VOTE_BUCKET_ROWS = 1001 * MSTEAMS_POLL_VOTE_BUCKET_COUNT;
1568
+ const MSTEAMS_POLL_TTL_MS = 2592e6;
1569
+ function normalizeChoiceValue(value) {
1570
+ if (typeof value === "string") {
1571
+ const trimmed = value.trim();
1572
+ return trimmed ? trimmed : null;
1573
+ }
1574
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
1575
+ return null;
1576
+ }
1577
+ function extractSelections(value) {
1578
+ if (Array.isArray(value)) return value.map(normalizeChoiceValue).filter((entry) => Boolean(entry));
1579
+ const normalized = normalizeChoiceValue(value);
1580
+ if (!normalized) return [];
1581
+ if (normalized.includes(",")) return (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeStringEntries)(normalized.split(","));
1582
+ return [normalized];
1583
+ }
1584
+ function readNestedValue(value, keys) {
1585
+ let current = value;
1586
+ for (const key of keys) {
1587
+ if (!(0, openclaw_plugin_sdk_string_coerce_runtime.isRecord)(current)) return;
1588
+ current = current[key];
1589
+ }
1590
+ return current;
1591
+ }
1592
+ function readNestedString(value, keys) {
1593
+ return (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(readNestedValue(value, keys));
1594
+ }
1595
+ function extractMSTeamsPollVote(activity) {
1596
+ const value = activity?.value;
1597
+ if (!value || !(0, openclaw_plugin_sdk_string_coerce_runtime.isRecord)(value)) return null;
1598
+ const pollId = readNestedString(value, ["openclawPollId"]) ?? readNestedString(value, ["pollId"]) ?? readNestedString(value, ["openclaw", "pollId"]) ?? readNestedString(value, [
1599
+ "openclaw",
1600
+ "poll",
1601
+ "id"
1602
+ ]) ?? readNestedString(value, ["data", "openclawPollId"]) ?? readNestedString(value, ["data", "pollId"]) ?? readNestedString(value, [
1603
+ "data",
1604
+ "openclaw",
1605
+ "pollId"
1606
+ ]) ?? readNestedString(value, [
1607
+ "action",
1608
+ "data",
1609
+ "openclawPollId"
1610
+ ]) ?? readNestedString(value, [
1611
+ "action",
1612
+ "data",
1613
+ "pollId"
1614
+ ]);
1615
+ if (!pollId) return null;
1616
+ const selections = [
1617
+ value.choices,
1618
+ readNestedValue(value, ["data", "choices"]),
1619
+ readNestedValue(value, [
1620
+ "action",
1621
+ "data",
1622
+ "choices"
1623
+ ])
1624
+ ].map(extractSelections).find((choices) => choices.length > 0) ?? [];
1625
+ if (selections.length === 0) return null;
1626
+ return {
1627
+ pollId,
1628
+ selections
1629
+ };
1630
+ }
1631
+ function buildMSTeamsPollCard(params) {
1632
+ const pollId = params.pollId ?? node_crypto.default.randomUUID();
1633
+ const maxSelections = typeof params.maxSelections === "number" && params.maxSelections > 1 ? Math.floor(params.maxSelections) : 1;
1634
+ const cappedMaxSelections = Math.min(Math.max(1, maxSelections), params.options.length);
1635
+ const choices = params.options.map((option, index) => ({
1636
+ title: option,
1637
+ value: String(index)
1638
+ }));
1639
+ const hint = cappedMaxSelections > 1 ? `Select up to ${cappedMaxSelections} option${cappedMaxSelections === 1 ? "" : "s"}.` : "Select one option.";
1640
+ const card = {
1641
+ type: "AdaptiveCard",
1642
+ version: "1.5",
1643
+ body: [
1644
+ {
1645
+ type: "TextBlock",
1646
+ text: params.question,
1647
+ wrap: true,
1648
+ weight: "Bolder",
1649
+ size: "Medium"
1650
+ },
1651
+ {
1652
+ type: "Input.ChoiceSet",
1653
+ id: "choices",
1654
+ isMultiSelect: cappedMaxSelections > 1,
1655
+ style: "expanded",
1656
+ choices
1657
+ },
1658
+ {
1659
+ type: "TextBlock",
1660
+ text: hint,
1661
+ wrap: true,
1662
+ isSubtle: true,
1663
+ spacing: "Small"
1664
+ }
1665
+ ],
1666
+ actions: [{
1667
+ type: "Action.Execute",
1668
+ title: "Vote",
1669
+ verb: "openclaw.poll.vote",
1670
+ data: {
1671
+ openclawPollId: pollId,
1672
+ pollId
1673
+ }
1674
+ }]
1675
+ };
1676
+ const fallbackLines = [`Poll: ${params.question}`, ...params.options.map((option, index) => `${index + 1}. ${option}`)];
1677
+ return {
1678
+ pollId,
1679
+ question: params.question,
1680
+ options: params.options,
1681
+ maxSelections: cappedMaxSelections,
1682
+ card,
1683
+ fallbackText: fallbackLines.join("\n")
1684
+ };
1685
+ }
1686
+ function createPollStateStore(params) {
1687
+ return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
1688
+ namespace: MSTEAMS_POLLS_NAMESPACE,
1689
+ maxEntries: MSTEAMS_SQLITE_MAX_POLL_ROWS,
1690
+ env: resolveMSTeamsSqliteStateEnv(params)
1691
+ });
1692
+ }
1693
+ function createPollVoteBucketStateStore(params) {
1694
+ return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
1695
+ namespace: MSTEAMS_POLL_VOTE_BUCKETS_NAMESPACE,
1696
+ maxEntries: MSTEAMS_MAX_POLL_VOTE_BUCKET_ROWS,
1697
+ env: resolveMSTeamsSqliteStateEnv(params)
1698
+ });
1699
+ }
1700
+ function isPollExpired(poll) {
1701
+ const cutoff = Date.now() - MSTEAMS_POLL_TTL_MS;
1702
+ return !(((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(poll.updatedAt ?? poll.createdAt) ?? 0) >= cutoff);
1703
+ }
1704
+ function normalizeMSTeamsPollSelections(poll, selections) {
1705
+ const maxSelections = Math.max(1, poll.maxSelections);
1706
+ const mapped = selections.map((entry) => (0, openclaw_plugin_sdk_number_runtime.parseStrictNonNegativeInteger)(entry)).filter((value) => value !== void 0).filter((value) => value >= 0 && value < poll.options.length).map((value) => String(value));
1707
+ return (0, openclaw_plugin_sdk_string_coerce_runtime.uniqueStrings)(mapped).slice(0, maxSelections);
1708
+ }
1709
+ function hashMSTeamsPollVote(pollId, voterId) {
1710
+ return node_crypto.default.createHash("sha256").update(pollId).update("\0").update(voterId).digest("hex");
1711
+ }
1712
+ function buildMSTeamsPollStateKey(pollId) {
1713
+ return node_crypto.default.createHash("sha256").update(pollId).digest("hex");
1714
+ }
1715
+ function selectMSTeamsPollVoteBucket(pollId, voterId) {
1716
+ const bucket = Number.parseInt(hashMSTeamsPollVote(pollId, voterId).slice(0, 8), 16);
1717
+ return String(bucket % MSTEAMS_POLL_VOTE_BUCKET_COUNT).padStart(4, "0");
1718
+ }
1719
+ function buildMSTeamsPollVoteBucketKey(pollId, bucket) {
1720
+ return `${buildMSTeamsPollStateKey(pollId)}:${bucket}`;
1721
+ }
1722
+ function createMSTeamsPollStoreState(params) {
1723
+ const pollStore = createPollStateStore(params);
1724
+ const voteBucketStore = createPollVoteBucketStateStore(params);
1725
+ const readPollVotes = async (pollId) => {
1726
+ const votes = {};
1727
+ for (const row of await voteBucketStore.entries()) if (row.value.pollId === pollId) Object.assign(votes, row.value.votes);
1728
+ return votes;
1729
+ };
1730
+ const deletePollVotes = async (pollId, cachedRows) => {
1731
+ for (const row of cachedRows?.values() ?? await voteBucketStore.entries()) if (row.value.pollId === pollId) {
1732
+ await voteBucketStore.delete(row.key);
1733
+ cachedRows?.delete(row.key);
1734
+ }
1735
+ };
1736
+ const registerPollVotes = async (pollId, votes, updatedAt) => {
1737
+ const buckets = /* @__PURE__ */ new Map();
1738
+ for (const [voterId, selections] of Object.entries(votes)) {
1739
+ const bucket = selectMSTeamsPollVoteBucket(pollId, voterId);
1740
+ const bucketVotes = buckets.get(bucket) ?? {};
1741
+ bucketVotes[voterId] = selections;
1742
+ buckets.set(bucket, bucketVotes);
1743
+ }
1744
+ for (const [bucket, bucketVotes] of buckets) {
1745
+ const key = buildMSTeamsPollVoteBucketKey(pollId, bucket);
1746
+ const existing = await voteBucketStore.lookup(key);
1747
+ await voteBucketStore.register(key, toPluginJsonValue({
1748
+ pollId,
1749
+ bucket,
1750
+ votes: {
1751
+ ...bucketVotes,
1752
+ ...existing?.votes
1753
+ },
1754
+ updatedAt
1755
+ }));
1756
+ }
1757
+ };
1758
+ const registerPollVote = async (pollId, voterId, selections, updatedAt) => {
1759
+ const bucket = selectMSTeamsPollVoteBucket(pollId, voterId);
1760
+ const key = buildMSTeamsPollVoteBucketKey(pollId, bucket);
1761
+ const existing = await voteBucketStore.lookup(key);
1762
+ await voteBucketStore.register(key, toPluginJsonValue({
1763
+ pollId,
1764
+ bucket,
1765
+ votes: {
1766
+ ...existing?.votes,
1767
+ [voterId]: selections
1768
+ },
1769
+ updatedAt
1770
+ }));
1771
+ };
1772
+ const reconstructPoll = async (metadata) => {
1773
+ return {
1774
+ ...metadata,
1775
+ votes: await readPollVotes(metadata.id)
1776
+ };
1777
+ };
1778
+ const prunePollStoreToLimit = async () => {
1779
+ let voteRows;
1780
+ const deletePrunedPollVotes = async (pollId) => {
1781
+ voteRows ??= new Map((await voteBucketStore.entries()).map((row) => [row.key, row]));
1782
+ await deletePollVotes(pollId, voteRows);
1783
+ };
1784
+ const rows = [];
1785
+ for (const row of await pollStore.entries()) {
1786
+ if (isPollExpired(row.value)) {
1787
+ await pollStore.delete(row.key);
1788
+ await deletePrunedPollVotes(row.value.id);
1789
+ continue;
1790
+ }
1791
+ rows.push(row);
1792
+ }
1793
+ if (rows.length <= MSTEAMS_MAX_POLLS) return;
1794
+ const sorted = rows.toSorted((a, b) => {
1795
+ return ((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(a.value.updatedAt ?? a.value.createdAt) ?? 0) - ((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(b.value.updatedAt ?? b.value.createdAt) ?? 0) || a.key.localeCompare(b.key);
1796
+ });
1797
+ for (const row of sorted.slice(0, rows.length - MSTEAMS_MAX_POLLS)) {
1798
+ await pollStore.delete(row.key);
1799
+ await deletePrunedPollVotes(row.value.id);
1800
+ }
1801
+ };
1802
+ const createPoll = async (poll) => {
1803
+ await withMSTeamsSqliteMutationLock(params, MSTEAMS_POLLS_NAMESPACE, async () => {
1804
+ const { votes, ...metadata } = poll;
1805
+ await pollStore.register(buildMSTeamsPollStateKey(poll.id), toPluginJsonValue(metadata));
1806
+ await deletePollVotes(poll.id);
1807
+ await registerPollVotes(poll.id, votes, poll.updatedAt ?? poll.createdAt);
1808
+ await prunePollStoreToLimit();
1809
+ });
1810
+ };
1811
+ const getPoll = async (pollId) => {
1812
+ const poll = await pollStore.lookup(buildMSTeamsPollStateKey(pollId));
1813
+ if (!poll) return null;
1814
+ if (isPollExpired(poll)) return null;
1815
+ return await reconstructPoll(poll);
1816
+ };
1817
+ const recordVote = async (vote) => {
1818
+ return await withMSTeamsSqliteMutationLock(params, MSTEAMS_POLLS_NAMESPACE, async () => {
1819
+ const pollKey = buildMSTeamsPollStateKey(vote.pollId);
1820
+ const poll = await pollStore.lookup(pollKey);
1821
+ if (!poll) return null;
1822
+ if (isPollExpired(poll)) {
1823
+ await pollStore.delete(pollKey);
1824
+ await deletePollVotes(vote.pollId);
1825
+ return null;
1826
+ }
1827
+ const normalized = normalizeMSTeamsPollSelections(await reconstructPoll(poll), vote.selections);
1828
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1829
+ poll.updatedAt = updatedAt;
1830
+ await pollStore.register(pollKey, toPluginJsonValue(poll));
1831
+ await registerPollVote(vote.pollId, vote.voterId, normalized, updatedAt);
1832
+ await prunePollStoreToLimit();
1833
+ return await reconstructPoll(poll);
1834
+ });
1835
+ };
1836
+ return {
1837
+ createPoll,
1838
+ getPoll,
1839
+ recordVote
1840
+ };
1841
+ }
1842
+ //#endregion
1843
+ //#region extensions/msteams/src/conversation-store-helpers.ts
1844
+ function toConversationStoreEntries(entries) {
1845
+ return Array.from(entries, ([conversationId, reference]) => ({
1846
+ conversationId,
1847
+ reference
1848
+ }));
1849
+ }
1850
+ function mergeStoredConversationReference(existing, incoming, nowIso) {
1851
+ return {
1852
+ ...existing?.timezone && !incoming.timezone ? { timezone: existing.timezone } : {},
1853
+ ...existing?.tenantId && !incoming.tenantId ? { tenantId: existing.tenantId } : {},
1854
+ ...existing?.aadObjectId && !incoming.aadObjectId ? { aadObjectId: existing.aadObjectId } : {},
1855
+ ...incoming,
1856
+ lastSeenAt: nowIso
1857
+ };
1858
+ }
1859
+ function findPreferredDmConversationByUserId(entries, id) {
1860
+ const target = id.trim();
1861
+ if (!target) return null;
1862
+ const personalMatches = [];
1863
+ const unknownTypeMatches = [];
1864
+ for (const entry of entries) {
1865
+ if (entry.reference.user?.aadObjectId !== target && entry.reference.user?.id !== target) continue;
1866
+ const convType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(entry.reference.conversation?.conversationType ?? "");
1867
+ if (convType === "personal") personalMatches.push(entry);
1868
+ else if (convType === "channel" || convType === "groupchat") {} else unknownTypeMatches.push(entry);
1869
+ }
1870
+ const candidates = personalMatches.length > 0 ? personalMatches : unknownTypeMatches;
1871
+ if (candidates.length === 0) return null;
1872
+ if (candidates.length > 1) candidates.sort((a, b) => ((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(b.reference.lastSeenAt) ?? 0) - ((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(a.reference.lastSeenAt) ?? 0));
1873
+ return candidates[0] ?? null;
1874
+ }
1875
+ //#endregion
1876
+ //#region extensions/msteams/src/conversation-store-state.ts
1877
+ const MSTEAMS_CONVERSATIONS_NAMESPACE = "conversations";
1878
+ const MSTEAMS_MAX_CONVERSATIONS = 1e3;
1879
+ const MSTEAMS_SQLITE_MAX_CONVERSATION_ROWS = 2e3;
1880
+ const MSTEAMS_CONVERSATION_TTL_MS = 31536e6;
1881
+ const CONVERSATION_MUTATION_KEY = "conversations";
1882
+ function createConversationStateStore(params) {
1883
+ return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
1884
+ namespace: MSTEAMS_CONVERSATIONS_NAMESPACE,
1885
+ maxEntries: MSTEAMS_SQLITE_MAX_CONVERSATION_ROWS,
1886
+ env: resolveMSTeamsSqliteStateEnv(params)
1887
+ });
1888
+ }
1889
+ function buildMSTeamsConversationStateKey(conversationId) {
1890
+ return node_crypto.default.createHash("sha256").update(conversationId).digest("hex");
1891
+ }
1892
+ function prepareMSTeamsConversationReferenceForStorage(conversationId, reference) {
1893
+ return {
1894
+ ...reference,
1895
+ conversation: {
1896
+ ...reference.conversation,
1897
+ id: conversationId
1898
+ }
1899
+ };
1900
+ }
1901
+ function getStoredConversationId(reference) {
1902
+ const rawId = reference.conversation?.id;
1903
+ return rawId ? require_resolve_allowlist.normalizeMSTeamsConversationId(rawId) : null;
1904
+ }
1905
+ function createMSTeamsConversationStoreState(params) {
1906
+ const ttlMs = params?.ttlMs ?? MSTEAMS_CONVERSATION_TTL_MS;
1907
+ const conversationStore = createConversationStateStore(params);
1908
+ const isExpired = (reference) => {
1909
+ const lastSeenAt = (0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(reference.lastSeenAt);
1910
+ return lastSeenAt != null && Date.now() - lastSeenAt > ttlMs;
1911
+ };
1912
+ const lookupStored = async (conversationId) => {
1913
+ const normalizedId = require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId);
1914
+ const value = await conversationStore.lookup(buildMSTeamsConversationStateKey(normalizedId));
1915
+ if (!value) return null;
1916
+ if (isExpired(value)) return null;
1917
+ return value;
1918
+ };
1919
+ const entries = async () => {
1920
+ const rows = await conversationStore.entries();
1921
+ const kept = [];
1922
+ for (const row of rows) {
1923
+ if (isExpired(row.value)) continue;
1924
+ const conversationId = getStoredConversationId(row.value);
1925
+ if (conversationId) kept.push([conversationId, row.value]);
1926
+ }
1927
+ return kept;
1928
+ };
1929
+ const register = async (conversationId, reference) => {
1930
+ const normalizedId = require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId);
1931
+ await conversationStore.register(buildMSTeamsConversationStateKey(normalizedId), toPluginJsonValue(prepareMSTeamsConversationReferenceForStorage(normalizedId, reference)));
1932
+ const rows = [];
1933
+ for (const row of await conversationStore.entries()) {
1934
+ if (isExpired(row.value)) {
1935
+ await conversationStore.delete(row.key);
1936
+ continue;
1937
+ }
1938
+ rows.push(row);
1939
+ }
1940
+ if (rows.length <= MSTEAMS_MAX_CONVERSATIONS) return;
1941
+ const sorted = rows.toSorted((a, b) => {
1942
+ const aTs = (0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(a.value.lastSeenAt) ?? 0;
1943
+ const bTs = (0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(b.value.lastSeenAt) ?? 0;
1944
+ const aId = getStoredConversationId(a.value) ?? a.key;
1945
+ const bId = getStoredConversationId(b.value) ?? b.key;
1946
+ return aTs - bTs || aId.localeCompare(bId);
1947
+ });
1948
+ for (const row of sorted.slice(0, rows.length - MSTEAMS_MAX_CONVERSATIONS)) await conversationStore.delete(row.key);
1949
+ };
1950
+ const list = async () => {
1951
+ return toConversationStoreEntries(await entries());
1952
+ };
1953
+ const findPreferredDmByUserId = async (id) => {
1954
+ return findPreferredDmConversationByUserId(await list(), id);
1955
+ };
1956
+ const upsert = async (conversationId, reference) => {
1957
+ const normalizedId = require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId);
1958
+ await withMSTeamsSqliteMutationLock(params, CONVERSATION_MUTATION_KEY, async () => {
1959
+ const existing = await lookupStored(normalizedId);
1960
+ await register(normalizedId, mergeStoredConversationReference(existing ?? void 0, reference, (/* @__PURE__ */ new Date()).toISOString()));
1961
+ });
1962
+ };
1963
+ const remove = async (conversationId) => {
1964
+ const normalizedId = require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId);
1965
+ return await withMSTeamsSqliteMutationLock(params, CONVERSATION_MUTATION_KEY, async () => {
1966
+ return await conversationStore.delete(buildMSTeamsConversationStateKey(normalizedId));
1967
+ });
1968
+ };
1969
+ return {
1970
+ upsert,
1971
+ get: lookupStored,
1972
+ list,
1973
+ remove,
1974
+ findPreferredDmByUserId
1975
+ };
1976
+ }
1977
+ //#endregion
1632
1978
  Object.defineProperty(exports, "buildConversationReference", {
1633
1979
  enumerable: true,
1634
1980
  get: function() {
@@ -1641,18 +1987,42 @@ Object.defineProperty(exports, "buildFileInfoCard", {
1641
1987
  return buildFileInfoCard;
1642
1988
  }
1643
1989
  });
1990
+ Object.defineProperty(exports, "buildMSTeamsAdaptiveCardActivity", {
1991
+ enumerable: true,
1992
+ get: function() {
1993
+ return buildMSTeamsAdaptiveCardActivity;
1994
+ }
1995
+ });
1644
1996
  Object.defineProperty(exports, "buildMSTeamsMessageActivity", {
1645
1997
  enumerable: true,
1646
1998
  get: function() {
1647
1999
  return buildMSTeamsMessageActivity;
1648
2000
  }
1649
2001
  });
2002
+ Object.defineProperty(exports, "buildMSTeamsPollCard", {
2003
+ enumerable: true,
2004
+ get: function() {
2005
+ return buildMSTeamsPollCard;
2006
+ }
2007
+ });
1650
2008
  Object.defineProperty(exports, "buildTeamsFileInfoCard", {
1651
2009
  enumerable: true,
1652
2010
  get: function() {
1653
2011
  return buildTeamsFileInfoCard;
1654
2012
  }
1655
2013
  });
2014
+ Object.defineProperty(exports, "createMSTeamsConversationStoreState", {
2015
+ enumerable: true,
2016
+ get: function() {
2017
+ return createMSTeamsConversationStoreState;
2018
+ }
2019
+ });
2020
+ Object.defineProperty(exports, "createMSTeamsPollStoreState", {
2021
+ enumerable: true,
2022
+ get: function() {
2023
+ return createMSTeamsPollStoreState;
2024
+ }
2025
+ });
1656
2026
  Object.defineProperty(exports, "deleteMSTeamsActivityWithReference", {
1657
2027
  enumerable: true,
1658
2028
  get: function() {
@@ -1665,6 +2035,12 @@ Object.defineProperty(exports, "extractFilename", {
1665
2035
  return extractFilename;
1666
2036
  }
1667
2037
  });
2038
+ Object.defineProperty(exports, "extractMSTeamsPollVote", {
2039
+ enumerable: true,
2040
+ get: function() {
2041
+ return extractMSTeamsPollVote;
2042
+ }
2043
+ });
1668
2044
  Object.defineProperty(exports, "extractMessageId", {
1669
2045
  enumerable: true,
1670
2046
  get: function() {
@@ -1707,6 +2083,12 @@ Object.defineProperty(exports, "prepareFileConsentActivityFs", {
1707
2083
  return prepareFileConsentActivityFs;
1708
2084
  }
1709
2085
  });
2086
+ Object.defineProperty(exports, "recordMSTeamsSentMessage", {
2087
+ enumerable: true,
2088
+ get: function() {
2089
+ return recordMSTeamsSentMessage;
2090
+ }
2091
+ });
1710
2092
  Object.defineProperty(exports, "removePendingUpload", {
1711
2093
  enumerable: true,
1712
2094
  get: function() {
@@ -1737,6 +2119,12 @@ Object.defineProperty(exports, "requiresFileConsent", {
1737
2119
  return requiresFileConsent;
1738
2120
  }
1739
2121
  });
2122
+ Object.defineProperty(exports, "resolveMSTeamsSqliteStateEnv", {
2123
+ enumerable: true,
2124
+ get: function() {
2125
+ return resolveMSTeamsSqliteStateEnv;
2126
+ }
2127
+ });
1740
2128
  Object.defineProperty(exports, "sendMSTeamsActivityWithReference", {
1741
2129
  enumerable: true,
1742
2130
  get: function() {
@@ -1761,6 +2149,12 @@ Object.defineProperty(exports, "setPendingUploadActivityIdFs", {
1761
2149
  return setPendingUploadActivityIdFs;
1762
2150
  }
1763
2151
  });
2152
+ Object.defineProperty(exports, "toPluginJsonValue", {
2153
+ enumerable: true,
2154
+ get: function() {
2155
+ return toPluginJsonValue;
2156
+ }
2157
+ });
1764
2158
  Object.defineProperty(exports, "updateMSTeamsActivityWithReference", {
1765
2159
  enumerable: true,
1766
2160
  get: function() {
@@ -1779,6 +2173,18 @@ Object.defineProperty(exports, "uploadToConsentUrl", {
1779
2173
  return uploadToConsentUrl;
1780
2174
  }
1781
2175
  });
2176
+ Object.defineProperty(exports, "wasMSTeamsMessageSentWithPersistence", {
2177
+ enumerable: true,
2178
+ get: function() {
2179
+ return wasMSTeamsMessageSentWithPersistence;
2180
+ }
2181
+ });
2182
+ Object.defineProperty(exports, "withMSTeamsSqliteMutationLock", {
2183
+ enumerable: true,
2184
+ get: function() {
2185
+ return withMSTeamsSqliteMutationLock;
2186
+ }
2187
+ });
1782
2188
  Object.defineProperty(exports, "withRevokedProxyFallback", {
1783
2189
  enumerable: true,
1784
2190
  get: function() {