@openclaw/msteams 2026.9.5 → 2026.9.7

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-ChuVK6k_.cjs → adapter.runtime-BiWeMKXE.cjs} +24 -30
  3. package/dist/.setup/{approval-card-CPNH_q3P.cjs → approval-card-DWPDItvq.cjs} +6 -3
  4. package/dist/.setup/{approval-handler.runtime-DDxGPJDz.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-Dzpbbht8.cjs → channel-ByzS7QJ0.cjs} +338 -220
  7. package/dist/.setup/{channel.runtime-TE49QvFv.cjs → channel.runtime-Baz8XDFs.cjs} +47 -144
  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-BR1l4zw-.cjs → conversation-store-state-B7JuyZTm.cjs} +697 -297
  11. package/dist/.setup/{src-CpKfpP1m.cjs → monitor-BJ7IyH4y.cjs} +440 -663
  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-D8ATH4QI.cjs → send-VUM7Sryb.cjs} +47 -97
  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 +10 -10
  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;
@@ -479,18 +485,16 @@ function prepareFileConsentActivity(params) {
479
485
  */
480
486
  async function prepareFileConsentActivityFs(params) {
481
487
  const { media, conversationId, description } = params;
482
- const uploadId = storePendingUpload({
488
+ const upload = {
483
489
  buffer: media.buffer,
484
490
  filename: media.filename,
485
491
  contentType: media.contentType,
486
492
  conversationId
487
- });
493
+ };
494
+ const uploadId = storePendingUpload(upload);
488
495
  await storePendingUploadFs({
489
496
  id: uploadId,
490
- buffer: media.buffer,
491
- filename: media.filename,
492
- contentType: media.contentType,
493
- conversationId
497
+ ...upload
494
498
  });
495
499
  return {
496
500
  activity: buildConsentActivity({
@@ -501,10 +505,6 @@ async function prepareFileConsentActivityFs(params) {
501
505
  uploadId
502
506
  };
503
507
  }
504
- /**
505
- * Check if a file requires FileConsentCard flow.
506
- * True for: personal chat AND (large file OR non-image)
507
- */
508
508
  function requiresFileConsent(params) {
509
509
  const isPersonal = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(params.conversationType) === "personal";
510
510
  const isImage = params.contentType?.startsWith("image/") ?? false;
@@ -775,14 +775,15 @@ function formatMSTeamsMarkdown(markdown, tableMode) {
775
775
  const index = escapedMarkdown.push(escaped) - 1;
776
776
  return `${tokenPrefix}e${index}${TOKEN_END}`;
777
777
  });
778
- const ir = prefixMSTeamsBlockquotes(protectMSTeamsCode((0, openclaw_plugin_sdk_text_chunking.markdownToIR)(protectedMarkdown, {
778
+ const parsed = (0, openclaw_plugin_sdk_text_chunking.markdownToIR)(protectedMarkdown, {
779
779
  autolink: false,
780
780
  enableSpoilers: true,
781
781
  enableTaskLists: true,
782
782
  headingStyle: "rich",
783
783
  linkify: false,
784
784
  blockquotePrefix: ""
785
- }), tokenPrefix, codeRegions, [
785
+ });
786
+ const protectedValues = [
786
787
  {
787
788
  prefix: `${tokenPrefix}e`,
788
789
  values: escapedMarkdown
@@ -803,8 +804,9 @@ function formatMSTeamsMarkdown(markdown, tableMode) {
803
804
  prefix: `${tokenPrefix}h`,
804
805
  values: entities
805
806
  }
806
- ]));
807
- 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, {
808
810
  styleMarkers: MSTEAMS_MARKERS,
809
811
  escapeText: (text) => text,
810
812
  buildLink: (link) => ({
@@ -813,12 +815,8 @@ function formatMSTeamsMarkdown(markdown, tableMode) {
813
815
  open: "[",
814
816
  close: `](${serializeMarkdownDestination(link.href)})`
815
817
  })
816
- }, MSTEAMS_FORMAT_CAPABILITIES), `${tokenPrefix}c`, codeRegions);
817
- restored = restoreTokens(restored, `${tokenPrefix}e`, escapedMarkdown);
818
- restored = restoreTokens(restored, `${tokenPrefix}t`, rawTables);
819
- restored = restoreTokens(restored, `${tokenPrefix}m`, mentions);
820
- restored = restoreTokens(restored, `${tokenPrefix}i`, images);
821
- 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));
822
820
  }
823
821
  //#endregion
824
822
  //#region extensions/msteams/src/graph-chat.ts
@@ -827,43 +825,18 @@ function buildTeamsFileInfoCard(file) {
827
825
  const uniqueId = rawETag.replace(/^["']|["']$/g, "").replace(/[{}]/g, "").split(",")[0] ?? rawETag;
828
826
  const lastDot = file.name.lastIndexOf(".");
829
827
  const fileType = lastDot >= 0 ? (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(file.name.slice(lastDot + 1)) : "";
830
- return {
831
- contentType: "application/vnd.microsoft.teams.card.file.info",
828
+ return buildFileInfoCard({
829
+ filename: file.name,
832
830
  contentUrl: file.webDavUrl,
833
- name: file.name,
834
- content: {
835
- uniqueId,
836
- fileType
837
- }
838
- };
831
+ uniqueId,
832
+ fileType
833
+ });
839
834
  }
840
835
  //#endregion
841
836
  //#region extensions/msteams/src/graph-upload.ts
842
- /**
843
- * SharePoint upload utilities for MS Teams file sending.
844
- *
845
- * For group chats and channels, files are uploaded to SharePoint and shared via a link.
846
- * This module provides utilities for:
847
- * - Uploading files to SharePoint (group/channel scope)
848
- * - Creating sharing links (organization-wide or per-user)
849
- * - Getting chat members for per-user sharing
850
- */
851
837
  const GRAPH_ROOT = "https://graph.microsoft.com/v1.0";
852
838
  const GRAPH_BETA = "https://graph.microsoft.com/beta";
853
839
  const GRAPH_SCOPE = "https://graph.microsoft.com";
854
- async function requestSharePoint(url, init, params) {
855
- const { response, release } = await (0, openclaw_plugin_sdk_ssrf_runtime.fetchWithSsrFGuard)({
856
- url,
857
- init,
858
- fetchImpl: params.fetchFn,
859
- mode: "trusted_env_proxy",
860
- beforeRequest: () => require_resolve_allowlist.assertMSTeamsSendHandoff(params),
861
- maxRedirects: 20,
862
- allowCrossOriginUnsafeRedirectReplay: true,
863
- auditContext: "msteams.graph-upload"
864
- });
865
- return (0, openclaw_plugin_sdk_fetch_runtime.responseWithRelease)(response, release);
866
- }
867
840
  function requireMSTeamsSharePointSiteId(siteId) {
868
841
  const normalized = siteId?.trim();
869
842
  if (!normalized) throw new Error("channels.msteams.sharePointSiteId is required to send files to group chats or channels");
@@ -872,43 +845,55 @@ function requireMSTeamsSharePointSiteId(siteId) {
872
845
  const SHAREPOINT_REQUEST_TIMEOUT_LABEL = "MS Teams SharePoint request";
873
846
  const SHAREPOINT_UPLOAD_TIMEOUT_LABEL = "MS Teams SharePoint upload";
874
847
  const GRAPH_TOKEN_TIMEOUT_LABEL = "MS Teams Graph token acquisition";
875
- async function getGraphAccessToken(tokenProvider) {
876
- return await require_resolve_allowlist.withMSTeamsRequestDeadline({
877
- label: GRAPH_TOKEN_TIMEOUT_LABEL,
878
- work: async () => await tokenProvider.getAccessToken(GRAPH_SCOPE)
879
- });
880
- }
881
- /**
882
- * Upload a file to a SharePoint site.
883
- * This is used for group chats and channels where /me/drive doesn't work for bots.
884
- *
885
- * @param params.siteId - SharePoint site ID (e.g., "contoso.sharepoint.com,guid1,guid2")
886
- */
887
- async function uploadToSharePoint(params) {
888
- const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`;
889
- const uploadUrl = `${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content?@microsoft.graph.conflictBehavior=rename`;
890
- const timeoutMs = require_resolve_allowlist.resolveMSTeamsSharePointUploadTimeoutMs(params.buffer.length);
891
- const data = await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
892
- label: SHAREPOINT_UPLOAD_TIMEOUT_LABEL,
893
- 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,
894
852
  work: async (signal) => {
895
853
  require_resolve_allowlist.assertMSTeamsSendHandoff(params);
896
- 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
+ });
897
858
  require_resolve_allowlist.assertMSTeamsSendHandoff(params);
898
- const res = await requestSharePoint(uploadUrl, {
899
- method: "PUT",
900
- headers: {
901
- "User-Agent": require_resolve_allowlist.buildUserAgent(),
902
- Authorization: `Bearer ${token}`,
903
- "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
904
870
  },
905
- body: new Blob([(0, openclaw_plugin_sdk_blob_runtime.bufferToBlobPart)(params.buffer)]),
906
- signal
907
- }, params);
908
- if (!res.ok) throw await require_resolve_allowlist.createMSTeamsHttpError(res, "SharePoint upload failed");
909
- 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 });
910
881
  }
911
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
+ });
912
897
  if (!data.id || !data.webUrl || !data.name) throw new Error("SharePoint upload response missing required fields");
913
898
  return {
914
899
  id: data.id,
@@ -916,30 +901,11 @@ async function uploadToSharePoint(params) {
916
901
  name: data.name
917
902
  };
918
903
  }
919
- /**
920
- * Get driveItem properties needed for native Teams file card attachments.
921
- * This fetches the eTag and webDavUrl which are required for "reference" type attachments.
922
- *
923
- * @param params.siteId - SharePoint site ID
924
- * @param params.itemId - The driveItem ID (returned from upload)
925
- */
926
904
  async function getDriveItemProperties(params) {
927
- const data = await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
928
- label: SHAREPOINT_REQUEST_TIMEOUT_LABEL,
929
- work: async (signal) => {
930
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
931
- const token = await getGraphAccessToken(params.tokenProvider);
932
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
933
- const res = await requestSharePoint(`${GRAPH_ROOT}/sites/${params.siteId}/drive/items/${params.itemId}?$select=eTag,webDavUrl,name`, {
934
- headers: {
935
- "User-Agent": require_resolve_allowlist.buildUserAgent(),
936
- Authorization: `Bearer ${token}`
937
- },
938
- signal
939
- }, params);
940
- if (!res.ok) throw await require_resolve_allowlist.createMSTeamsHttpError(res, "Get driveItem properties failed");
941
- return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.getDriveItemProperties");
942
- }
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"
943
909
  });
944
910
  if (!data.eTag || !data.webDavUrl || !data.name) throw new Error("DriveItem response missing required properties (eTag, webDavUrl, or name)");
945
911
  return {
@@ -948,31 +914,12 @@ async function getDriveItemProperties(params) {
948
914
  name: data.name
949
915
  };
950
916
  }
951
- /**
952
- * Get members of a Teams chat for per-user sharing.
953
- * Used to create sharing links scoped to only the chat participants.
954
- */
955
- async function getChatMembers(params) {
956
- return await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
957
- label: SHAREPOINT_REQUEST_TIMEOUT_LABEL,
958
- work: async (signal) => {
959
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
960
- const token = await getGraphAccessToken(params.tokenProvider);
961
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
962
- const res = await requestSharePoint(`${GRAPH_ROOT}/chats/${params.chatId}/members`, {
963
- headers: {
964
- "User-Agent": require_resolve_allowlist.buildUserAgent(),
965
- Authorization: `Bearer ${token}`
966
- },
967
- signal
968
- }, params);
969
- if (!res.ok) {
970
- const message = res.status === 403 ? "Get chat members failed; verify Graph chat-member permissions and tenant access policies" : "Get chat members failed";
971
- throw await require_resolve_allowlist.createMSTeamsHttpError(res, message);
972
- }
973
- return ((await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.getChatMembers")).value ?? []).map((member) => ({ aadObjectId: member.userId ?? "" })).filter((member) => member.aadObjectId);
974
- }
975
- });
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] : []);
976
923
  }
977
924
  /**
978
925
  * Create a sharing link for a SharePoint drive item.
@@ -987,63 +934,40 @@ async function createSharePointSharingLink(params) {
987
934
  scope: scope === "users" ? "users" : "organization"
988
935
  };
989
936
  if (scope === "users" && params.recipientObjectIds?.length) body.recipients = params.recipientObjectIds.map((id) => ({ objectId: id }));
990
- const data = await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
991
- label: SHAREPOINT_REQUEST_TIMEOUT_LABEL,
992
- work: async (signal) => {
993
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
994
- const token = await getGraphAccessToken(params.tokenProvider);
995
- require_resolve_allowlist.assertMSTeamsSendHandoff(params);
996
- const res = await requestSharePoint(`${apiRoot}/sites/${params.siteId}/drive/items/${params.itemId}/createLink`, {
997
- method: "POST",
998
- headers: {
999
- "User-Agent": require_resolve_allowlist.buildUserAgent(),
1000
- Authorization: `Bearer ${token}`,
1001
- "Content-Type": "application/json"
1002
- },
1003
- body: JSON.stringify(body),
1004
- signal
1005
- }, params);
1006
- if (!res.ok) throw await require_resolve_allowlist.createMSTeamsHttpError(res, "Create SharePoint sharing link failed");
1007
- return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.createSharePointSharingLink");
1008
- }
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"
1009
946
  });
1010
947
  if (!data.link?.webUrl) throw new Error("Create SharePoint sharing link response missing webUrl");
1011
- return { webUrl: data.link.webUrl };
948
+ return data.link.webUrl;
1012
949
  }
1013
950
  /**
1014
951
  * Upload a file to SharePoint and create a sharing link.
1015
952
  *
1016
953
  * For group chats, this creates a per-user sharing link scoped to chat members.
1017
954
  * For channels, this creates an organization-wide sharing link.
1018
- *
1019
- * @param params.siteId - SharePoint site ID
1020
- * @param params.chatId - Optional chat ID for per-user sharing (group chats)
1021
- * @param params.usePerUserSharing - Whether to use per-user sharing (requires beta API + chat-member read access)
1022
955
  */
1023
956
  async function uploadAndShareSharePoint(params) {
1024
- const uploaded = await uploadToSharePoint({
1025
- buffer: params.buffer,
1026
- filename: params.filename,
1027
- contentType: params.contentType,
1028
- tokenProvider: params.tokenProvider,
1029
- siteId: params.siteId,
1030
- fetchFn: params.fetchFn,
1031
- assertDirectAdapterHandoff: params.assertDirectAdapterHandoff
1032
- });
957
+ const uploaded = await uploadToSharePoint(params);
1033
958
  let scope = "organization";
1034
959
  let recipientObjectIds;
1035
960
  if (params.usePerUserSharing && params.chatId) {
1036
- const members = await getChatMembers({
961
+ recipientObjectIds = await getChatMemberIds({
1037
962
  chatId: params.chatId,
1038
963
  tokenProvider: params.tokenProvider,
1039
964
  fetchFn: params.fetchFn,
1040
965
  assertDirectAdapterHandoff: params.assertDirectAdapterHandoff
1041
966
  });
1042
- 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");
1043
968
  scope = "users";
1044
- recipientObjectIds = members.map((member) => member.aadObjectId);
1045
969
  }
1046
- const shareLink = await createSharePointSharingLink({
970
+ const shareUrl = await createSharePointSharingLink({
1047
971
  siteId: params.siteId,
1048
972
  itemId: uploaded.id,
1049
973
  tokenProvider: params.tokenProvider,
@@ -1055,16 +979,13 @@ async function uploadAndShareSharePoint(params) {
1055
979
  return {
1056
980
  itemId: uploaded.id,
1057
981
  webUrl: uploaded.webUrl,
1058
- shareUrl: shareLink.webUrl,
982
+ shareUrl,
1059
983
  name: uploaded.name
1060
984
  };
1061
985
  }
1062
986
  //#endregion
1063
987
  //#region extensions/msteams/src/media-helpers.ts
1064
988
  /**
1065
- * MIME type detection and filename extraction for MSTeams media attachments.
1066
- */
1067
- /**
1068
989
  * Detect MIME type from URL extension or data URL.
1069
990
  * Uses shared MIME detection for consistency with core handling.
1070
991
  */
@@ -1103,25 +1024,16 @@ async function extractFilename(url) {
1103
1024
  return (0, openclaw_plugin_sdk_media_runtime.extractOriginalFilename)(url);
1104
1025
  }
1105
1026
  }
1106
- /**
1107
- * Check if a URL refers to a local file path.
1108
- */
1109
1027
  function isLocalPath(url) {
1110
- if (/^file:\/\//iu.test(url) || url.startsWith("/") || url.startsWith("~")) return true;
1111
- if (url.startsWith("\\") && !url.startsWith("\\\\")) return true;
1112
- if (/^[a-zA-Z]:[\\/]/.test(url)) return true;
1113
- if (url.startsWith("\\\\")) return true;
1114
- return false;
1028
+ return /^file:\/\//iu.test(url) || url.startsWith("/") || url.startsWith("~") || url.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(url);
1115
1029
  }
1116
1030
  /**
1117
1031
  * Extract the message ID from a Bot Framework response.
1118
1032
  */
1119
1033
  function extractMessageId(response) {
1120
- if (!response || typeof response !== "object") return null;
1121
- if (!("id" in response)) return null;
1034
+ if (!response || typeof response !== "object" || !("id" in response)) return null;
1122
1035
  const { id } = response;
1123
- if (typeof id !== "string" || !id) return null;
1124
- return id;
1036
+ return typeof id === "string" && id ? id : null;
1125
1037
  }
1126
1038
  //#endregion
1127
1039
  //#region extensions/msteams/src/mentions.ts
@@ -1189,6 +1101,15 @@ function buildMSTeamsMessageActivity(text) {
1189
1101
  entities: [...parsed.entities, AI_GENERATED_ENTITY]
1190
1102
  };
1191
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
+ }
1192
1113
  //#endregion
1193
1114
  //#region extensions/msteams/src/revoked-context.ts
1194
1115
  async function withRevokedProxyFallback(params) {
@@ -1201,6 +1122,36 @@ async function withRevokedProxyFallback(params) {
1201
1122
  }
1202
1123
  }
1203
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
1204
1155
  //#region extensions/msteams/src/sdk-proactive.ts
1205
1156
  const loadMSTeamsApiModule = (0, openclaw_plugin_sdk_lazy_runtime.createLazyRuntimeModule)(() => import("@microsoft/teams.api"));
1206
1157
  async function quoteMSTeamsActivity(activity, messageId) {
@@ -1210,12 +1161,12 @@ async function quoteMSTeamsActivity(activity, messageId) {
1210
1161
  return MessageActivityInput.from(activity).prependQuote(messageId);
1211
1162
  }
1212
1163
  function resolveThreadedConversationId(conversationId, threadActivityId) {
1213
- if (!threadActivityId) return conversationId.split(";")[0] ?? conversationId;
1214
- return `${conversationId.split(";")[0] ?? conversationId};messageid=${threadActivityId}`;
1164
+ const baseId = conversationId.split(";")[0] ?? conversationId;
1165
+ return threadActivityId ? `${baseId};messageid=${threadActivityId}` : baseId;
1215
1166
  }
1216
1167
  function normalizeRequiredServiceUrl(ref) {
1217
1168
  if (!ref.serviceUrl) throw new Error("Invalid stored reference: missing serviceUrl");
1218
- return require_resolve_allowlist.normalizeBotFrameworkServiceUrl(ref.serviceUrl);
1169
+ return require_bot_framework_service_url.normalizeBotFrameworkServiceUrl(ref.serviceUrl);
1219
1170
  }
1220
1171
  function buildSdkConversationReference(source, options) {
1221
1172
  const bot = source.agent ?? source.bot ?? void 0;
@@ -1250,13 +1201,10 @@ function buildSdkConversationReference(source, options) {
1250
1201
  ...source.aadObjectId ? { aadObjectId: source.aadObjectId } : {}
1251
1202
  };
1252
1203
  }
1253
- function getStructuralApiClient(app) {
1254
- return app.api;
1255
- }
1256
1204
  function sameServiceUrl(left, right) {
1257
1205
  if (!left) return false;
1258
1206
  try {
1259
- return require_resolve_allowlist.normalizeBotFrameworkServiceUrl(left) === right;
1207
+ return require_bot_framework_service_url.normalizeBotFrameworkServiceUrl(left) === right;
1260
1208
  } catch {
1261
1209
  return false;
1262
1210
  }
@@ -1268,7 +1216,7 @@ function stringifyReferenceFallbackActivity(activity) {
1268
1216
  return "";
1269
1217
  }
1270
1218
  async function getApiClientForReference(app, ref) {
1271
- const api = getStructuralApiClient(app);
1219
+ const api = app.api;
1272
1220
  if (sameServiceUrl(api.serviceUrl, ref.serviceUrl)) return api;
1273
1221
  const appInternals = app;
1274
1222
  const httpClient = appInternals.api?.http ?? appInternals.client;
@@ -1277,12 +1225,12 @@ async function getApiClientForReference(app, ref) {
1277
1225
  return new Client(ref.serviceUrl, httpClient);
1278
1226
  }
1279
1227
  function mergeReferenceIntoActivity(activity, ref) {
1280
- const source = activity && typeof activity === "object" && !Array.isArray(activity) ? activity : {
1228
+ const source = (0, openclaw_plugin_sdk_string_coerce_runtime.asOptionalRecord)(activity) ?? {
1281
1229
  type: "message",
1282
1230
  text: stringifyReferenceFallbackActivity(activity)
1283
1231
  };
1284
- const existingChannelData = source.channelData && typeof source.channelData === "object" && !Array.isArray(source.channelData) ? source.channelData : void 0;
1285
- 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);
1286
1234
  let channelData = existingChannelData ? { ...existingChannelData } : void 0;
1287
1235
  if (ref.tenantId) {
1288
1236
  channelData ??= {};
@@ -1321,6 +1269,8 @@ async function sendMSTeamsActivityWithReference(app, source, activity, options)
1321
1269
  };
1322
1270
  }
1323
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);
1324
1274
  return {
1325
1275
  ...activityWithRef,
1326
1276
  ...res
@@ -1347,9 +1297,6 @@ const MSTEAMS_MAX_MEDIA_BYTES = 104857600;
1347
1297
  * Files >= 4MB use consent flow; smaller images can use inline base64.
1348
1298
  */
1349
1299
  const FILE_CONSENT_THRESHOLD_BYTES = 4194304;
1350
- function normalizeConversationId(rawId) {
1351
- return rawId.split(";")[0] ?? rawId;
1352
- }
1353
1300
  function buildConversationReference(ref) {
1354
1301
  const conversationId = ref.conversation?.id?.trim();
1355
1302
  if (!conversationId) throw new Error("Invalid stored reference: missing conversation.id");
@@ -1367,7 +1314,7 @@ function buildConversationReference(ref) {
1367
1314
  } : user,
1368
1315
  agent,
1369
1316
  conversation: {
1370
- id: normalizeConversationId(conversationId),
1317
+ id: require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId),
1371
1318
  conversationType: ref.conversation?.conversationType,
1372
1319
  tenantId
1373
1320
  },
@@ -1380,17 +1327,11 @@ function buildConversationReference(ref) {
1380
1327
  }
1381
1328
  function pushTextMessages(out, text, opts) {
1382
1329
  if (!text) return;
1383
- if (opts.chunkText) {
1384
- for (const chunk of require_runtime.getMSTeamsRuntime().channel.text.chunkMarkdownTextWithMode(text, opts.chunkLimit, opts.chunkMode)) {
1385
- const trimmed = chunk.trim();
1386
- if (!trimmed || (0, openclaw_plugin_sdk_reply_chunking.isSilentReplyText)(trimmed, openclaw_plugin_sdk_reply_chunking.SILENT_REPLY_TOKEN)) continue;
1387
- out.push({ text: trimmed });
1388
- }
1389
- 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 });
1390
1334
  }
1391
- const trimmed = text.trim();
1392
- if (!trimmed || (0, openclaw_plugin_sdk_reply_chunking.isSilentReplyText)(trimmed, openclaw_plugin_sdk_reply_chunking.SILENT_REPLY_TOKEN)) return;
1393
- out.push({ text: trimmed });
1394
1335
  }
1395
1336
  function clampMs(value, maxMs) {
1396
1337
  if (!Number.isFinite(value) || value < 0) return 0;
@@ -1405,9 +1346,9 @@ function resolveRetryOptions(retry) {
1405
1346
  };
1406
1347
  return {
1407
1348
  enabled: true,
1408
- maxAttempts: Math.max(1, retry?.maxAttempts ?? 3),
1409
- baseDelayMs: Math.max(0, retry?.baseDelayMs ?? 250),
1410
- 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)
1411
1352
  };
1412
1353
  }
1413
1354
  function computeRetryDelayMs(attempt, classification, opts) {
@@ -1427,37 +1368,20 @@ function renderReplyPayloadsToMessages(replies, options) {
1427
1368
  for (const payload of replies) {
1428
1369
  const reply = (0, openclaw_plugin_sdk_reply_payload.resolveSendableOutboundReplyParts)(payload, { text: formatMSTeamsMarkdown(payload.text ?? "", tableMode) });
1429
1370
  if (!reply.hasContent) continue;
1430
- if (!reply.hasMedia) {
1431
- pushTextMessages(out, reply.text, {
1432
- chunkText,
1433
- chunkLimit,
1434
- chunkMode
1371
+ const [firstMedia, ...remainingMedia] = reply.mediaUrls;
1372
+ if (mediaMode === "inline" && firstMedia) {
1373
+ out.push({
1374
+ text: reply.text || void 0,
1375
+ mediaUrl: firstMedia
1435
1376
  });
1436
- continue;
1437
- }
1438
- if (mediaMode === "inline") {
1439
- const firstMedia = reply.mediaUrls[0];
1440
- if (firstMedia) {
1441
- out.push({
1442
- text: reply.text || void 0,
1443
- mediaUrl: firstMedia
1444
- });
1445
- for (let i = 1; i < reply.mediaUrls.length; i++) if (reply.mediaUrls[i]) out.push({ mediaUrl: reply.mediaUrls[i] });
1446
- } else pushTextMessages(out, reply.text, {
1377
+ out.push(...remainingMedia.map((mediaUrl) => ({ mediaUrl })));
1378
+ } else {
1379
+ pushTextMessages(out, reply.text, {
1447
1380
  chunkText,
1448
1381
  chunkLimit,
1449
1382
  chunkMode
1450
1383
  });
1451
- continue;
1452
- }
1453
- pushTextMessages(out, reply.text, {
1454
- chunkText,
1455
- chunkLimit,
1456
- chunkMode
1457
- });
1458
- for (const mediaUrl of reply.mediaUrls) {
1459
- if (!mediaUrl) continue;
1460
- out.push({ mediaUrl });
1384
+ out.push(...reply.mediaUrls.map((mediaUrl) => ({ mediaUrl })));
1461
1385
  }
1462
1386
  }
1463
1387
  return out;
@@ -1620,9 +1544,8 @@ async function sendMSTeamsMessages(params) {
1620
1544
  fellBack: false
1621
1545
  }),
1622
1546
  onRevoked: async () => {
1623
- const remaining = messages.slice(idx);
1624
1547
  return {
1625
- ids: remaining.length > 0 ? await sendProactively(remaining, idx, resolvedThreadId) : [],
1548
+ ids: await sendProactively(messages.slice(idx), idx, resolvedThreadId),
1626
1549
  fellBack: true
1627
1550
  };
1628
1551
  }
@@ -1635,6 +1558,423 @@ async function sendMSTeamsMessages(params) {
1635
1558
  return await sendProactively(messages, 0);
1636
1559
  }
1637
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
1638
1978
  Object.defineProperty(exports, "buildConversationReference", {
1639
1979
  enumerable: true,
1640
1980
  get: function() {
@@ -1647,18 +1987,42 @@ Object.defineProperty(exports, "buildFileInfoCard", {
1647
1987
  return buildFileInfoCard;
1648
1988
  }
1649
1989
  });
1990
+ Object.defineProperty(exports, "buildMSTeamsAdaptiveCardActivity", {
1991
+ enumerable: true,
1992
+ get: function() {
1993
+ return buildMSTeamsAdaptiveCardActivity;
1994
+ }
1995
+ });
1650
1996
  Object.defineProperty(exports, "buildMSTeamsMessageActivity", {
1651
1997
  enumerable: true,
1652
1998
  get: function() {
1653
1999
  return buildMSTeamsMessageActivity;
1654
2000
  }
1655
2001
  });
2002
+ Object.defineProperty(exports, "buildMSTeamsPollCard", {
2003
+ enumerable: true,
2004
+ get: function() {
2005
+ return buildMSTeamsPollCard;
2006
+ }
2007
+ });
1656
2008
  Object.defineProperty(exports, "buildTeamsFileInfoCard", {
1657
2009
  enumerable: true,
1658
2010
  get: function() {
1659
2011
  return buildTeamsFileInfoCard;
1660
2012
  }
1661
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
+ });
1662
2026
  Object.defineProperty(exports, "deleteMSTeamsActivityWithReference", {
1663
2027
  enumerable: true,
1664
2028
  get: function() {
@@ -1671,6 +2035,12 @@ Object.defineProperty(exports, "extractFilename", {
1671
2035
  return extractFilename;
1672
2036
  }
1673
2037
  });
2038
+ Object.defineProperty(exports, "extractMSTeamsPollVote", {
2039
+ enumerable: true,
2040
+ get: function() {
2041
+ return extractMSTeamsPollVote;
2042
+ }
2043
+ });
1674
2044
  Object.defineProperty(exports, "extractMessageId", {
1675
2045
  enumerable: true,
1676
2046
  get: function() {
@@ -1713,6 +2083,12 @@ Object.defineProperty(exports, "prepareFileConsentActivityFs", {
1713
2083
  return prepareFileConsentActivityFs;
1714
2084
  }
1715
2085
  });
2086
+ Object.defineProperty(exports, "recordMSTeamsSentMessage", {
2087
+ enumerable: true,
2088
+ get: function() {
2089
+ return recordMSTeamsSentMessage;
2090
+ }
2091
+ });
1716
2092
  Object.defineProperty(exports, "removePendingUpload", {
1717
2093
  enumerable: true,
1718
2094
  get: function() {
@@ -1743,6 +2119,12 @@ Object.defineProperty(exports, "requiresFileConsent", {
1743
2119
  return requiresFileConsent;
1744
2120
  }
1745
2121
  });
2122
+ Object.defineProperty(exports, "resolveMSTeamsSqliteStateEnv", {
2123
+ enumerable: true,
2124
+ get: function() {
2125
+ return resolveMSTeamsSqliteStateEnv;
2126
+ }
2127
+ });
1746
2128
  Object.defineProperty(exports, "sendMSTeamsActivityWithReference", {
1747
2129
  enumerable: true,
1748
2130
  get: function() {
@@ -1767,6 +2149,12 @@ Object.defineProperty(exports, "setPendingUploadActivityIdFs", {
1767
2149
  return setPendingUploadActivityIdFs;
1768
2150
  }
1769
2151
  });
2152
+ Object.defineProperty(exports, "toPluginJsonValue", {
2153
+ enumerable: true,
2154
+ get: function() {
2155
+ return toPluginJsonValue;
2156
+ }
2157
+ });
1770
2158
  Object.defineProperty(exports, "updateMSTeamsActivityWithReference", {
1771
2159
  enumerable: true,
1772
2160
  get: function() {
@@ -1785,6 +2173,18 @@ Object.defineProperty(exports, "uploadToConsentUrl", {
1785
2173
  return uploadToConsentUrl;
1786
2174
  }
1787
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
+ });
1788
2188
  Object.defineProperty(exports, "withRevokedProxyFallback", {
1789
2189
  enumerable: true,
1790
2190
  get: function() {