@openclaw/msteams 2026.5.28 → 2026.5.31-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { C as normalizeStringEntries$1, E as resolveChannelMediaMaxBytes, M as getMSTeamsRuntime, d as detectMime, g as getFileExtension, m as extractOriginalFilename, p as extensionForMime, y as loadOutboundMediaFromUrl } from "./runtime-api-BlvMnDKz.js";
2
- import { A as isAllowedBotFrameworkServiceUrl, C as readAccessToken, D as buildUserAgent, E as loadMSTeamsSdkWithAuth, N as resolveMSTeamsSdkCloudOptions, P as validateMSTeamsProactiveServiceUrlBoundary, T as createMSTeamsTokenProvider, i as isRevokedProxyError, j as normalizeBotFrameworkServiceUrl, k as describeBotFrameworkServiceUrlHost, n as formatMSTeamsSendErrorHint, r as formatUnknownError, t as classifyMSTeamsSendError, v as loadDelegatedTokens, x as resolveMSTeamsStorePath, y as resolveMSTeamsCredentials } from "./errors-DZGI_mqq.js";
2
+ import { A as isAllowedBotFrameworkServiceUrl, C as readAccessToken, D as buildUserAgent, E as loadMSTeamsSdkWithAuth, N as resolveMSTeamsSdkCloudOptions, P as validateMSTeamsProactiveServiceUrlBoundary, T as createMSTeamsTokenProvider, i as isRevokedProxyError, j as normalizeBotFrameworkServiceUrl, k as describeBotFrameworkServiceUrlHost, n as formatMSTeamsSendErrorHint, r as formatUnknownError, t as classifyMSTeamsSendError, v as loadDelegatedTokens, x as resolveMSTeamsStorePath, y as resolveMSTeamsCredentials } from "./errors-Dpn8B05h.js";
3
3
  import { c as createMSTeamsHttpError } from "./oauth.token-BKzEFepQ.js";
4
- import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel-2_L55KDI.js";
4
+ import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel-CqRTzeBc.js";
5
5
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
6
6
  import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
7
7
  import { withFileLock } from "openclaw/plugin-sdk/file-lock";
@@ -10,10 +10,11 @@ import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
10
10
  import { lookup } from "node:dns/promises";
11
11
  import { isPrivateIpAddress } from "openclaw/plugin-sdk/ssrf-policy";
12
12
  import path from "node:path";
13
+ import { isFutureDateTimestampMs, parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
13
14
  import { pathExists } from "openclaw/plugin-sdk/security-runtime";
14
- import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
15
+ import crypto, { createHash } from "node:crypto";
16
+ import fs from "node:fs/promises";
15
17
  import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
16
- import crypto from "node:crypto";
17
18
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
18
19
  import { SILENT_REPLY_TOKEN, isSilentReplyText } from "openclaw/plugin-sdk/reply-chunking";
19
20
  import { sleep } from "openclaw/plugin-sdk/text-utility-runtime";
@@ -88,87 +89,213 @@ async function withFileLock$1(filePath, fallback, fn) {
88
89
  });
89
90
  }
90
91
  //#endregion
91
- //#region extensions/msteams/src/conversation-store-fs.ts
92
+ //#region extensions/msteams/src/sqlite-state.ts
93
+ function resolveStateDirOverride(options) {
94
+ if (!options) return;
95
+ if (options.stateDir) return options.stateDir;
96
+ if (options.storePath) return path.dirname(options.storePath);
97
+ if (options.homedir) return getMSTeamsRuntime().state.resolveStateDir(options.env ?? process.env, options.homedir);
98
+ return options.env?.OPENCLAW_STATE_DIR?.trim() || void 0;
99
+ }
100
+ function resolveMSTeamsSqliteStateEnv(options) {
101
+ const stateDir = resolveStateDirOverride(options);
102
+ if (!stateDir) return options?.env;
103
+ return {
104
+ ...options?.env ?? process.env,
105
+ OPENCLAW_STATE_DIR: stateDir
106
+ };
107
+ }
108
+ function toPluginJsonValue(value) {
109
+ const serialized = JSON.stringify(value);
110
+ return JSON.parse(serialized);
111
+ }
112
+ function resolveMSTeamsSqliteStateDir(options) {
113
+ return resolveStateDirOverride(options) ?? getMSTeamsRuntime().state.resolveStateDir(options?.env ?? process.env, options?.homedir);
114
+ }
115
+ const sqliteMutationLocks = /* @__PURE__ */ new Map();
116
+ async function withProcessMutationLock(lockPath, fn) {
117
+ const previous = sqliteMutationLocks.get(lockPath) ?? Promise.resolve();
118
+ let release = () => {};
119
+ const next = new Promise((resolve) => {
120
+ release = resolve;
121
+ });
122
+ const chained = previous.then(() => next, () => next);
123
+ sqliteMutationLocks.set(lockPath, chained);
124
+ await previous.catch(() => void 0);
125
+ try {
126
+ return await fn();
127
+ } finally {
128
+ release();
129
+ if (sqliteMutationLocks.get(lockPath) === chained) sqliteMutationLocks.delete(lockPath);
130
+ }
131
+ }
132
+ async function withMSTeamsSqliteMutationLock(options, lockFilename, fn) {
133
+ const lockPath = path.join(resolveMSTeamsSqliteStateDir(options), lockFilename);
134
+ return await withProcessMutationLock(lockPath, async () => {
135
+ return await withFileLock$1(lockPath, { version: 1 }, fn);
136
+ });
137
+ }
138
+ //#endregion
139
+ //#region extensions/msteams/src/conversation-store-state.ts
92
140
  const STORE_FILENAME$2 = "msteams-conversations.json";
141
+ const CONVERSATIONS_NAMESPACE = "conversations";
142
+ const CONVERSATION_MIGRATIONS_NAMESPACE = "conversation-migrations";
143
+ const LEGACY_JSON_MIGRATION_KEY = "msteams-conversations-json-v1";
93
144
  const MAX_CONVERSATIONS = 1e3;
145
+ const SQLITE_MAX_CONVERSATION_ROWS = 2e3;
94
146
  const CONVERSATION_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
95
- function pruneToLimit$2(conversations) {
96
- const entries = Object.entries(conversations);
97
- if (entries.length <= MAX_CONVERSATIONS) return conversations;
98
- entries.sort((a, b) => {
99
- return (parseStoredConversationTimestamp(a[1].lastSeenAt) ?? 0) - (parseStoredConversationTimestamp(b[1].lastSeenAt) ?? 0);
147
+ const CONVERSATION_LOCK_FILENAME = "msteams-conversations.sqlite.lock";
148
+ function createConversationStateStore(params) {
149
+ return getMSTeamsRuntime().state.openKeyedStore({
150
+ namespace: CONVERSATIONS_NAMESPACE,
151
+ maxEntries: SQLITE_MAX_CONVERSATION_ROWS,
152
+ env: resolveMSTeamsSqliteStateEnv(params)
100
153
  });
101
- const keep = entries.slice(entries.length - MAX_CONVERSATIONS);
102
- return Object.fromEntries(keep);
103
154
  }
104
- function pruneExpired$2(conversations, nowMs, ttlMs) {
105
- let removed = false;
106
- const kept = {};
107
- for (const [conversationId, reference] of Object.entries(conversations)) {
108
- const lastSeenAt = parseStoredConversationTimestamp(reference.lastSeenAt);
109
- if (lastSeenAt != null && nowMs - lastSeenAt > ttlMs) {
110
- removed = true;
111
- continue;
112
- }
113
- kept[conversationId] = reference;
114
- }
115
- return {
116
- conversations: kept,
117
- removed
118
- };
155
+ function createConversationMigrationStore(params) {
156
+ return getMSTeamsRuntime().state.openKeyedStore({
157
+ namespace: CONVERSATION_MIGRATIONS_NAMESPACE,
158
+ maxEntries: 100,
159
+ env: resolveMSTeamsSqliteStateEnv(params)
160
+ });
119
161
  }
120
- function createMSTeamsConversationStoreFs(params) {
121
- const ttlMs = params?.ttlMs ?? CONVERSATION_TTL_MS;
122
- const filePath = resolveMSTeamsStorePath({
162
+ function resolveLegacyStorePath(params) {
163
+ return resolveMSTeamsStorePath({
123
164
  filename: STORE_FILENAME$2,
124
165
  env: params?.env,
125
166
  homedir: params?.homedir,
126
167
  stateDir: params?.stateDir,
127
168
  storePath: params?.storePath
128
169
  });
129
- const empty = {
170
+ }
171
+ function normalizeLegacyStore(value) {
172
+ if (value.version !== 1 || !value.conversations || typeof value.conversations !== "object" || Array.isArray(value.conversations)) return {
130
173
  version: 1,
131
174
  conversations: {}
132
175
  };
133
- const readStore = async () => {
134
- const { value } = await readJsonFile(filePath, empty);
135
- if (value.version !== 1 || !value.conversations || typeof value.conversations !== "object" || Array.isArray(value.conversations)) return empty;
136
- const nowMs = Date.now();
137
- const pruned = pruneExpired$2(value.conversations, nowMs, ttlMs).conversations;
138
- return {
176
+ return value;
177
+ }
178
+ function buildConversationStateKey(conversationId) {
179
+ return crypto.createHash("sha256").update(conversationId).digest("hex");
180
+ }
181
+ function prepareConversationReferenceForStorage(conversationId, reference) {
182
+ return {
183
+ ...reference,
184
+ conversation: {
185
+ ...reference.conversation,
186
+ id: conversationId
187
+ }
188
+ };
189
+ }
190
+ function getStoredConversationId(reference) {
191
+ const rawId = reference.conversation?.id;
192
+ return rawId ? normalizeStoredConversationId(rawId) : null;
193
+ }
194
+ function createMSTeamsConversationStoreState(params) {
195
+ const ttlMs = params?.ttlMs ?? CONVERSATION_TTL_MS;
196
+ const conversationStore = createConversationStateStore(params);
197
+ const migrationStore = createConversationMigrationStore(params);
198
+ const legacyStorePath = resolveLegacyStorePath(params);
199
+ let legacyImportPromise = null;
200
+ const isExpired = (reference) => {
201
+ const lastSeenAt = parseStoredConversationTimestamp(reference.lastSeenAt);
202
+ return lastSeenAt != null && Date.now() - lastSeenAt > ttlMs;
203
+ };
204
+ const selectRetainedConversations = (conversations) => {
205
+ const retained = Object.entries(conversations).filter(([, reference]) => !isExpired(reference));
206
+ if (retained.length <= MAX_CONVERSATIONS) return retained;
207
+ retained.sort((a, b) => {
208
+ return (parseStoredConversationTimestamp(a[1].lastSeenAt) ?? 0) - (parseStoredConversationTimestamp(b[1].lastSeenAt) ?? 0) || a[0].localeCompare(b[0]);
209
+ });
210
+ return retained.slice(retained.length - MAX_CONVERSATIONS);
211
+ };
212
+ const importLegacyStore = async () => {
213
+ if (await migrationStore.lookup(LEGACY_JSON_MIGRATION_KEY)) return;
214
+ const { value, exists } = await readJsonFile(legacyStorePath, {
139
215
  version: 1,
140
- conversations: pruneToLimit$2(pruned)
141
- };
216
+ conversations: {}
217
+ });
218
+ if (!exists) {
219
+ await migrationStore.register(LEGACY_JSON_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
220
+ return;
221
+ }
222
+ const legacy = normalizeLegacyStore(value);
223
+ for (const [rawConversationId, reference] of selectRetainedConversations(legacy.conversations)) {
224
+ const conversationId = normalizeStoredConversationId(rawConversationId);
225
+ if (!conversationId) continue;
226
+ await conversationStore.registerIfAbsent(buildConversationStateKey(conversationId), toPluginJsonValue(prepareConversationReferenceForStorage(conversationId, reference)));
227
+ }
228
+ await migrationStore.register(LEGACY_JSON_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
229
+ await fs.rm(legacyStorePath, { force: true }).catch(() => {});
230
+ };
231
+ const ensureLegacyImported = async () => {
232
+ legacyImportPromise ??= withMSTeamsSqliteMutationLock(params, CONVERSATION_LOCK_FILENAME, importLegacyStore);
233
+ await legacyImportPromise;
234
+ };
235
+ const lookupStored = async (conversationId) => {
236
+ const normalizedId = normalizeStoredConversationId(conversationId);
237
+ const value = await conversationStore.lookup(buildConversationStateKey(normalizedId));
238
+ if (!value) return null;
239
+ if (isExpired(value)) return null;
240
+ return value;
241
+ };
242
+ const entries = async () => {
243
+ await ensureLegacyImported();
244
+ const rows = await conversationStore.entries();
245
+ const kept = [];
246
+ for (const row of rows) {
247
+ if (isExpired(row.value)) continue;
248
+ const conversationId = getStoredConversationId(row.value);
249
+ if (conversationId) kept.push([conversationId, row.value]);
250
+ }
251
+ return kept;
252
+ };
253
+ const lookup = async (conversationId) => {
254
+ await ensureLegacyImported();
255
+ return await lookupStored(conversationId);
256
+ };
257
+ const register = async (conversationId, reference) => {
258
+ const normalizedId = normalizeStoredConversationId(conversationId);
259
+ await conversationStore.register(buildConversationStateKey(normalizedId), toPluginJsonValue(prepareConversationReferenceForStorage(normalizedId, reference)));
260
+ const rows = [];
261
+ for (const row of await conversationStore.entries()) {
262
+ if (isExpired(row.value)) {
263
+ await conversationStore.delete(row.key);
264
+ continue;
265
+ }
266
+ rows.push(row);
267
+ }
268
+ if (rows.length <= MAX_CONVERSATIONS) return;
269
+ const sorted = rows.toSorted((a, b) => {
270
+ const aTs = parseStoredConversationTimestamp(a.value.lastSeenAt) ?? 0;
271
+ const bTs = parseStoredConversationTimestamp(b.value.lastSeenAt) ?? 0;
272
+ const aId = getStoredConversationId(a.value) ?? a.key;
273
+ const bId = getStoredConversationId(b.value) ?? b.key;
274
+ return aTs - bTs || aId.localeCompare(bId);
275
+ });
276
+ for (const row of sorted.slice(0, rows.length - MAX_CONVERSATIONS)) await conversationStore.delete(row.key);
142
277
  };
143
278
  const list = async () => {
144
- const store = await readStore();
145
- return toConversationStoreEntries(Object.entries(store.conversations));
279
+ return toConversationStoreEntries(await entries());
146
280
  };
147
281
  const get = async (conversationId) => {
148
- return (await readStore()).conversations[normalizeStoredConversationId(conversationId)] ?? null;
282
+ return await lookup(conversationId);
149
283
  };
150
284
  const findPreferredDmByUserId = async (id) => {
151
285
  return findPreferredDmConversationByUserId(await list(), id);
152
286
  };
153
287
  const upsert = async (conversationId, reference) => {
154
288
  const normalizedId = normalizeStoredConversationId(conversationId);
155
- await withFileLock$1(filePath, empty, async () => {
156
- const store = await readStore();
157
- store.conversations[normalizedId] = mergeStoredConversationReference(store.conversations[normalizedId], reference, (/* @__PURE__ */ new Date()).toISOString());
158
- const nowMs = Date.now();
159
- store.conversations = pruneExpired$2(store.conversations, nowMs, ttlMs).conversations;
160
- store.conversations = pruneToLimit$2(store.conversations);
161
- await writeJsonFile(filePath, store);
289
+ await withMSTeamsSqliteMutationLock(params, CONVERSATION_LOCK_FILENAME, async () => {
290
+ await importLegacyStore();
291
+ await register(normalizedId, mergeStoredConversationReference(await lookupStored(normalizedId) ?? void 0, reference, (/* @__PURE__ */ new Date()).toISOString()));
162
292
  });
163
293
  };
164
294
  const remove = async (conversationId) => {
165
295
  const normalizedId = normalizeStoredConversationId(conversationId);
166
- return await withFileLock$1(filePath, empty, async () => {
167
- const store = await readStore();
168
- if (!(normalizedId in store.conversations)) return false;
169
- delete store.conversations[normalizedId];
170
- await writeJsonFile(filePath, store);
171
- return true;
296
+ return await withMSTeamsSqliteMutationLock(params, CONVERSATION_LOCK_FILENAME, async () => {
297
+ await importLegacyStore();
298
+ return await conversationStore.delete(buildConversationStateKey(normalizedId));
172
299
  });
173
300
  };
174
301
  return {
@@ -183,8 +310,16 @@ function createMSTeamsConversationStoreFs(params) {
183
310
  //#endregion
184
311
  //#region extensions/msteams/src/polls.ts
185
312
  const STORE_FILENAME$1 = "msteams-polls.json";
313
+ const POLLS_NAMESPACE = "polls";
314
+ const POLL_VOTE_BUCKETS_NAMESPACE = "poll-vote-buckets";
315
+ const POLL_MIGRATIONS_NAMESPACE = "poll-migrations";
316
+ const LEGACY_POLLS_MIGRATION_KEY = "msteams-polls-json-v1";
186
317
  const MAX_POLLS = 1e3;
318
+ const SQLITE_MAX_POLL_ROWS = 2e3;
319
+ const POLL_VOTE_BUCKET_COUNT = 32;
320
+ const MAX_POLL_VOTE_BUCKET_ROWS = 1001 * POLL_VOTE_BUCKET_COUNT;
187
321
  const POLL_TTL_MS = 720 * 60 * 60 * 1e3;
322
+ const POLL_LOCK_FILENAME = "msteams-polls.sqlite.lock";
188
323
  function normalizeChoiceValue(value) {
189
324
  if (typeof value === "string") {
190
325
  const trimmed = value.trim();
@@ -302,6 +437,27 @@ function buildMSTeamsPollCard(params) {
302
437
  fallbackText: fallbackLines.join("\n")
303
438
  };
304
439
  }
440
+ function createPollStateStore(params) {
441
+ return getMSTeamsRuntime().state.openKeyedStore({
442
+ namespace: POLLS_NAMESPACE,
443
+ maxEntries: SQLITE_MAX_POLL_ROWS,
444
+ env: resolveMSTeamsSqliteStateEnv(params)
445
+ });
446
+ }
447
+ function createPollVoteBucketStateStore(params) {
448
+ return getMSTeamsRuntime().state.openKeyedStore({
449
+ namespace: POLL_VOTE_BUCKETS_NAMESPACE,
450
+ maxEntries: MAX_POLL_VOTE_BUCKET_ROWS,
451
+ env: resolveMSTeamsSqliteStateEnv(params)
452
+ });
453
+ }
454
+ function createPollMigrationStore(params) {
455
+ return getMSTeamsRuntime().state.openKeyedStore({
456
+ namespace: POLL_MIGRATIONS_NAMESPACE,
457
+ maxEntries: 100,
458
+ env: resolveMSTeamsSqliteStateEnv(params)
459
+ });
460
+ }
305
461
  function parseTimestamp(value) {
306
462
  if (!value) return null;
307
463
  const parsed = Date.parse(value);
@@ -314,69 +470,181 @@ function pruneExpired$1(polls) {
314
470
  });
315
471
  return Object.fromEntries(entries);
316
472
  }
317
- function pruneToLimit$1(polls) {
318
- const entries = Object.entries(polls);
319
- if (entries.length <= MAX_POLLS) return polls;
320
- entries.sort((a, b) => {
321
- return (parseTimestamp(a[1].updatedAt ?? a[1].createdAt) ?? 0) - (parseTimestamp(b[1].updatedAt ?? b[1].createdAt) ?? 0);
473
+ function selectRetainedPolls(polls) {
474
+ const retained = Object.entries(pruneExpired$1(polls));
475
+ if (retained.length <= MAX_POLLS) return retained;
476
+ retained.sort((a, b) => {
477
+ return (parseTimestamp(a[1].updatedAt ?? a[1].createdAt) ?? 0) - (parseTimestamp(b[1].updatedAt ?? b[1].createdAt) ?? 0) || a[0].localeCompare(b[0]);
322
478
  });
323
- const keep = entries.slice(entries.length - MAX_POLLS);
324
- return Object.fromEntries(keep);
479
+ return retained.slice(retained.length - MAX_POLLS);
325
480
  }
326
481
  function normalizeMSTeamsPollSelections(poll, selections) {
327
482
  const maxSelections = Math.max(1, poll.maxSelections);
328
483
  const mapped = selections.map((entry) => parseStrictNonNegativeInteger(entry)).filter((value) => value !== void 0).filter((value) => value >= 0 && value < poll.options.length).map((value) => String(value));
329
484
  return uniqueStrings(maxSelections > 1 ? mapped.slice(0, maxSelections) : mapped.slice(0, 1));
330
485
  }
331
- function createMSTeamsPollStoreFs(params) {
332
- const filePath = resolveMSTeamsStorePath({
486
+ function createMSTeamsPollStoreState(params) {
487
+ const pollStore = createPollStateStore(params);
488
+ const voteBucketStore = createPollVoteBucketStateStore(params);
489
+ const migrationStore = createPollMigrationStore(params);
490
+ const legacyStorePath = resolveMSTeamsStorePath({
333
491
  filename: STORE_FILENAME$1,
334
492
  env: params?.env,
335
493
  homedir: params?.homedir,
336
494
  stateDir: params?.stateDir,
337
495
  storePath: params?.storePath
338
496
  });
339
- const empty = {
340
- version: 1,
341
- polls: {}
497
+ let legacyImportPromise = null;
498
+ const splitPoll = (poll) => {
499
+ const { votes, ...metadata } = poll;
500
+ return {
501
+ metadata,
502
+ votes
503
+ };
504
+ };
505
+ const hashVote = (pollId, voterId) => {
506
+ return crypto.createHash("sha256").update(pollId).update("\0").update(voterId).digest("hex");
507
+ };
508
+ const buildPollStateKey = (pollId) => {
509
+ return crypto.createHash("sha256").update(pollId).digest("hex");
510
+ };
511
+ const selectVoteBucket = (pollId, voterId) => {
512
+ const bucket = Number.parseInt(hashVote(pollId, voterId).slice(0, 8), 16);
513
+ return String(bucket % POLL_VOTE_BUCKET_COUNT).padStart(4, "0");
514
+ };
515
+ const buildVoteBucketKey = (pollId, bucket) => {
516
+ return `${crypto.createHash("sha256").update(pollId).digest("hex")}:${bucket}`;
342
517
  };
343
- const readStore = async () => {
344
- const { value } = await readJsonFile(filePath, empty);
518
+ const readPollVotes = async (pollId) => {
519
+ const votes = {};
520
+ for (const row of await voteBucketStore.entries()) if (row.value.pollId === pollId) Object.assign(votes, row.value.votes);
521
+ return votes;
522
+ };
523
+ const deletePollVotes = async (pollId) => {
524
+ for (const row of await voteBucketStore.entries()) if (row.value.pollId === pollId) await voteBucketStore.delete(row.key);
525
+ };
526
+ const registerPollVotes = async (pollId, votes, updatedAt) => {
527
+ const buckets = /* @__PURE__ */ new Map();
528
+ for (const [voterId, selections] of Object.entries(votes)) {
529
+ const bucket = selectVoteBucket(pollId, voterId);
530
+ const bucketVotes = buckets.get(bucket) ?? {};
531
+ bucketVotes[voterId] = selections;
532
+ buckets.set(bucket, bucketVotes);
533
+ }
534
+ for (const [bucket, bucketVotes] of buckets) {
535
+ const key = buildVoteBucketKey(pollId, bucket);
536
+ const existing = await voteBucketStore.lookup(key);
537
+ await voteBucketStore.register(key, toPluginJsonValue({
538
+ pollId,
539
+ bucket,
540
+ votes: {
541
+ ...bucketVotes,
542
+ ...existing?.votes
543
+ },
544
+ updatedAt
545
+ }));
546
+ }
547
+ };
548
+ const registerPollVote = async (pollId, voterId, selections, updatedAt) => {
549
+ const bucket = selectVoteBucket(pollId, voterId);
550
+ const key = buildVoteBucketKey(pollId, bucket);
551
+ const existing = await voteBucketStore.lookup(key);
552
+ await voteBucketStore.register(key, toPluginJsonValue({
553
+ pollId,
554
+ bucket,
555
+ votes: {
556
+ ...existing?.votes,
557
+ [voterId]: selections
558
+ },
559
+ updatedAt
560
+ }));
561
+ };
562
+ const reconstructPoll = async (metadata) => {
345
563
  return {
346
- version: 1,
347
- polls: pruneToLimit$1(pruneExpired$1(value.polls ?? {}))
564
+ ...metadata,
565
+ votes: await readPollVotes(metadata.id)
348
566
  };
349
567
  };
350
- const writeStore = async (data) => {
351
- await writeJsonFile(filePath, data);
568
+ const importLegacyStore = async () => {
569
+ if (await migrationStore.lookup(LEGACY_POLLS_MIGRATION_KEY)) return;
570
+ const { value, exists } = await readJsonFile(legacyStorePath, {
571
+ version: 1,
572
+ polls: {}
573
+ });
574
+ if (!exists) {
575
+ await migrationStore.register(LEGACY_POLLS_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
576
+ return;
577
+ }
578
+ const legacyPolls = value.version === 1 && value.polls && typeof value.polls === "object" && !Array.isArray(value.polls) ? value.polls : {};
579
+ for (const [pollId, poll] of selectRetainedPolls(legacyPolls)) {
580
+ if (!pollId) continue;
581
+ const { metadata, votes } = splitPoll(poll);
582
+ await pollStore.registerIfAbsent(buildPollStateKey(pollId), toPluginJsonValue(metadata));
583
+ await registerPollVotes(pollId, votes, poll.updatedAt ?? poll.createdAt);
584
+ }
585
+ await migrationStore.register(LEGACY_POLLS_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
586
+ await fs.rm(legacyStorePath, { force: true }).catch(() => {});
587
+ };
588
+ const ensureLegacyImported = async () => {
589
+ legacyImportPromise ??= withMSTeamsSqliteMutationLock(params, POLL_LOCK_FILENAME, importLegacyStore);
590
+ await legacyImportPromise;
591
+ };
592
+ const prunePollStoreToLimit = async () => {
593
+ const rows = [];
594
+ for (const row of await pollStore.entries()) {
595
+ if (!pruneExpired$1({ [row.key]: row.value })[row.key]) {
596
+ await pollStore.delete(row.key);
597
+ await deletePollVotes(row.value.id);
598
+ continue;
599
+ }
600
+ rows.push(row);
601
+ }
602
+ if (rows.length <= MAX_POLLS) return;
603
+ const sorted = rows.toSorted((a, b) => {
604
+ return (parseTimestamp(a.value.updatedAt ?? a.value.createdAt) ?? 0) - (parseTimestamp(b.value.updatedAt ?? b.value.createdAt) ?? 0) || a.key.localeCompare(b.key);
605
+ });
606
+ for (const row of sorted.slice(0, rows.length - MAX_POLLS)) {
607
+ await pollStore.delete(row.key);
608
+ await deletePollVotes(row.value.id);
609
+ }
352
610
  };
353
611
  const createPoll = async (poll) => {
354
- await withFileLock$1(filePath, empty, async () => {
355
- const data = await readStore();
356
- data.polls[poll.id] = poll;
357
- await writeStore({
358
- version: 1,
359
- polls: pruneToLimit$1(data.polls)
360
- });
612
+ await withMSTeamsSqliteMutationLock(params, POLL_LOCK_FILENAME, async () => {
613
+ await importLegacyStore();
614
+ const { metadata, votes } = splitPoll(poll);
615
+ await pollStore.register(buildPollStateKey(poll.id), toPluginJsonValue(metadata));
616
+ await deletePollVotes(poll.id);
617
+ await registerPollVotes(poll.id, votes, poll.updatedAt ?? poll.createdAt);
618
+ await prunePollStoreToLimit();
361
619
  });
362
620
  };
363
- const getPoll = async (pollId) => await withFileLock$1(filePath, empty, async () => {
364
- return (await readStore()).polls[pollId] ?? null;
365
- });
366
- const recordVote = async (params) => await withFileLock$1(filePath, empty, async () => {
367
- const data = await readStore();
368
- const poll = data.polls[params.pollId];
621
+ const getPoll = async (pollId) => {
622
+ await ensureLegacyImported();
623
+ const poll = await pollStore.lookup(buildPollStateKey(pollId));
369
624
  if (!poll) return null;
370
- const normalized = normalizeMSTeamsPollSelections(poll, params.selections);
371
- poll.votes[params.voterId] = normalized;
372
- poll.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
373
- data.polls[poll.id] = poll;
374
- await writeStore({
375
- version: 1,
376
- polls: pruneToLimit$1(data.polls)
625
+ if (!pruneExpired$1({ [pollId]: poll })[pollId]) return null;
626
+ return await reconstructPoll(poll);
627
+ };
628
+ const recordVote = async (vote) => {
629
+ return await withMSTeamsSqliteMutationLock(params, POLL_LOCK_FILENAME, async () => {
630
+ await importLegacyStore();
631
+ const pollKey = buildPollStateKey(vote.pollId);
632
+ const poll = await pollStore.lookup(pollKey);
633
+ if (!poll) return null;
634
+ if (!pruneExpired$1({ [vote.pollId]: poll })[vote.pollId]) {
635
+ await pollStore.delete(pollKey);
636
+ await deletePollVotes(vote.pollId);
637
+ return null;
638
+ }
639
+ const normalized = normalizeMSTeamsPollSelections(await reconstructPoll(poll), vote.selections);
640
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
641
+ poll.updatedAt = updatedAt;
642
+ await pollStore.register(pollKey, toPluginJsonValue(poll));
643
+ await registerPollVote(vote.pollId, vote.voterId, normalized, updatedAt);
644
+ await prunePollStoreToLimit();
645
+ return await reconstructPoll(poll);
377
646
  });
378
- return poll;
379
- });
647
+ };
380
648
  return {
381
649
  createPoll,
382
650
  getPoll,
@@ -519,31 +787,24 @@ async function uploadToConsentUrl(params) {
519
787
  }
520
788
  //#endregion
521
789
  //#region extensions/msteams/src/pending-uploads-fs.ts
522
- /**
523
- * Filesystem-backed pending upload store for the FileConsentCard flow.
524
- *
525
- * The CLI `message send --media` path runs in a different process from the
526
- * gateway's bot monitor that receives the `fileConsent/invoke` callback.
527
- * An in-memory `pending-uploads.ts` store cannot bridge those processes, so
528
- * when the user clicks "Allow" the monitor handler's lookup misses and the
529
- * user sees "card action not supported".
530
- *
531
- * This FS store persists pending uploads to a JSON file (with the file buffer
532
- * base64-encoded) so any process that shares the OpenClaw state dir can read
533
- * them back. The in-memory store in `pending-uploads.ts` is still the fast
534
- * path for same-process flows (for example the messenger reply path); this FS
535
- * store is a cross-process fallback.
536
- */
537
790
  /** TTL for persisted pending uploads (matches in-memory store). */
538
791
  const PENDING_UPLOAD_TTL_MS$1 = 300 * 1e3;
539
792
  /** Cap to avoid unbounded growth if a process crashes mid-flow. */
540
793
  const MAX_PENDING_UPLOADS = 100;
794
+ const MAX_CHUNKS_PER_UPLOAD = 3072;
795
+ const MAX_PENDING_UPLOAD_CHUNK_ROWS = 45e3;
796
+ const RAW_CHUNK_BYTES = 36 * 1024;
797
+ const PENDING_UPLOAD_META_MAX_ENTRIES = 200;
541
798
  const STORE_FILENAME = "msteams-pending-uploads.json";
799
+ const PENDING_UPLOAD_META_NAMESPACE = "pending-uploads";
800
+ const PENDING_UPLOAD_CHUNKS_NAMESPACE = "pending-upload-chunks";
801
+ const PENDING_UPLOAD_MIGRATIONS_NAMESPACE = "pending-upload-migrations";
802
+ const PENDING_UPLOAD_LOCK_FILENAME = "msteams-pending-uploads.sqlite.lock";
542
803
  const empty = {
543
804
  version: 1,
544
805
  uploads: {}
545
806
  };
546
- function resolveFilePath(options) {
807
+ function resolveLegacyFilePath(options) {
547
808
  return resolveMSTeamsStorePath({
548
809
  filename: STORE_FILENAME,
549
810
  env: options?.env,
@@ -552,6 +813,42 @@ function resolveFilePath(options) {
552
813
  storePath: options?.storePath
553
814
  });
554
815
  }
816
+ function createMetaStore(options) {
817
+ return getMSTeamsRuntime().state.openKeyedStore({
818
+ namespace: PENDING_UPLOAD_META_NAMESPACE,
819
+ maxEntries: PENDING_UPLOAD_META_MAX_ENTRIES,
820
+ env: resolveMSTeamsSqliteStateEnv(options)
821
+ });
822
+ }
823
+ function createChunkStore(options) {
824
+ return getMSTeamsRuntime().state.openKeyedStore({
825
+ namespace: PENDING_UPLOAD_CHUNKS_NAMESPACE,
826
+ maxEntries: MAX_PENDING_UPLOAD_CHUNK_ROWS,
827
+ env: resolveMSTeamsSqliteStateEnv(options)
828
+ });
829
+ }
830
+ function createMigrationStore(options) {
831
+ return getMSTeamsRuntime().state.openKeyedStore({
832
+ namespace: PENDING_UPLOAD_MIGRATIONS_NAMESPACE,
833
+ maxEntries: 100,
834
+ env: resolveMSTeamsSqliteStateEnv(options)
835
+ });
836
+ }
837
+ function buildUploadKey(id) {
838
+ return `upload:${createHash("sha256").update(id).digest("hex")}`;
839
+ }
840
+ function buildMetaKey(id) {
841
+ return `${buildUploadKey(id)}:meta`;
842
+ }
843
+ function buildChunkKey(id, index) {
844
+ return `${buildUploadKey(id)}:chunk:${String(index).padStart(4, "0")}`;
845
+ }
846
+ function buildMigrationKey(filePath) {
847
+ return `legacy-json:${createHash("sha256").update(filePath).digest("hex")}`;
848
+ }
849
+ function buildMigrationContentKey(filePath, value) {
850
+ return `legacy-json-content:${createHash("sha256").update(filePath).update("\0").update(JSON.stringify(value) ?? "undefined").digest("hex")}`;
851
+ }
555
852
  function pruneExpired(uploads, nowMs, ttlMs) {
556
853
  const kept = {};
557
854
  for (const [id, record] of Object.entries(uploads)) if (nowMs - record.createdAt <= ttlMs) kept[id] = record;
@@ -564,10 +861,10 @@ function pruneToLimit(uploads) {
564
861
  const keep = entries.slice(entries.length - MAX_PENDING_UPLOADS);
565
862
  return Object.fromEntries(keep);
566
863
  }
567
- function recordToUpload(record) {
864
+ function recordToUpload(record, buffer) {
568
865
  return {
569
866
  id: record.id,
570
- buffer: Buffer.from(record.bufferBase64, "base64"),
867
+ buffer,
571
868
  filename: record.filename,
572
869
  contentType: record.contentType,
573
870
  conversationId: record.conversationId,
@@ -580,17 +877,119 @@ function isValidStore(value) {
580
877
  const candidate = value;
581
878
  return candidate.version === 1 && typeof candidate.uploads === "object" && candidate.uploads !== null && !Array.isArray(candidate.uploads);
582
879
  }
583
- async function readStore(filePath, ttlMs) {
584
- const { value } = await readJsonFile(filePath, empty);
585
- if (!isValidStore(value)) return {
586
- version: 1,
587
- uploads: {}
588
- };
880
+ function normalizeLegacyUploadRecord(value) {
881
+ if (!value || typeof value !== "object") return null;
882
+ const record = value;
883
+ if (typeof record.id !== "string" || !record.id || typeof record.bufferBase64 !== "string" || typeof record.filename !== "string" || !record.filename || typeof record.conversationId !== "string" || !record.conversationId || typeof record.createdAt !== "number" || !Number.isFinite(record.createdAt)) return null;
589
884
  return {
590
- version: 1,
591
- uploads: pruneToLimit(pruneExpired(value.uploads, Date.now(), ttlMs))
885
+ id: record.id,
886
+ bufferBase64: record.bufferBase64,
887
+ filename: record.filename,
888
+ contentType: typeof record.contentType === "string" ? record.contentType : void 0,
889
+ conversationId: record.conversationId,
890
+ consentCardActivityId: typeof record.consentCardActivityId === "string" ? record.consentCardActivityId : void 0,
891
+ createdAt: record.createdAt
592
892
  };
593
893
  }
894
+ function normalizeLegacyUploads(value) {
895
+ if (!isValidStore(value)) return {};
896
+ const uploads = {};
897
+ for (const record of Object.values(value.uploads)) {
898
+ const normalized = normalizeLegacyUploadRecord(record);
899
+ if (normalized) uploads[normalized.id] = normalized;
900
+ }
901
+ return uploads;
902
+ }
903
+ async function deleteUploadRows(id, metaStore, chunkStore) {
904
+ const existing = await metaStore.lookup(buildMetaKey(id));
905
+ await metaStore.delete(buildMetaKey(id));
906
+ if (!existing) return;
907
+ const chunkCount = existing.chunkCount;
908
+ for (let index = 0; index < chunkCount; index += 1) await chunkStore.delete(buildChunkKey(id, index));
909
+ }
910
+ async function registerUploadRows(record, metaStore, chunkStore, ttlMs, overwrite) {
911
+ const buffer = Buffer.from(record.bufferBase64, "base64");
912
+ const chunkCount = Math.max(1, Math.ceil(buffer.byteLength / RAW_CHUNK_BYTES));
913
+ if (chunkCount > MAX_CHUNKS_PER_UPLOAD) throw new Error(`Microsoft Teams pending upload ${record.id} exceeds SQLite chunk limit (${chunkCount}/${MAX_CHUNKS_PER_UPLOAD})`);
914
+ if (overwrite) await deleteUploadRows(record.id, metaStore, chunkStore);
915
+ else if (await metaStore.lookup(buildMetaKey(record.id))) return;
916
+ await pruneUploadStore(metaStore, chunkStore, ttlMs, chunkCount);
917
+ for (let index = 0; index < chunkCount; index += 1) {
918
+ const chunk = buffer.subarray(index * RAW_CHUNK_BYTES, (index + 1) * RAW_CHUNK_BYTES);
919
+ await chunkStore.register(buildChunkKey(record.id, index), toPluginJsonValue({
920
+ id: record.id,
921
+ index,
922
+ dataBase64: chunk.toString("base64")
923
+ }));
924
+ }
925
+ await metaStore.register(buildMetaKey(record.id), toPluginJsonValue({
926
+ id: record.id,
927
+ filename: record.filename,
928
+ contentType: record.contentType,
929
+ conversationId: record.conversationId,
930
+ consentCardActivityId: record.consentCardActivityId,
931
+ createdAt: record.createdAt,
932
+ chunkCount,
933
+ byteLength: buffer.byteLength
934
+ }));
935
+ }
936
+ async function importLegacyStore(options, metaStore, chunkStore, ttlMs) {
937
+ const legacyFilePath = resolveLegacyFilePath(options);
938
+ const migrationStore = createMigrationStore(options);
939
+ const migrationKey = buildMigrationKey(legacyFilePath);
940
+ const imported = await migrationStore.lookup(migrationKey) !== void 0;
941
+ const { value, exists } = await readJsonFile(legacyFilePath, empty);
942
+ if (!exists) {
943
+ if (!imported) await migrationStore.register(migrationKey, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
944
+ return;
945
+ }
946
+ const contentKey = buildMigrationContentKey(legacyFilePath, value);
947
+ if (await migrationStore.lookup(contentKey)) return;
948
+ const legacy = pruneToLimit(pruneExpired(normalizeLegacyUploads(value), Date.now(), ttlMs));
949
+ for (const record of Object.values(legacy)) await registerUploadRows(record, metaStore, chunkStore, ttlMs, false);
950
+ await migrationStore.register(contentKey, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
951
+ if (!imported) await migrationStore.register(migrationKey, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
952
+ await fs.rm(legacyFilePath, { force: true }).catch(() => {});
953
+ }
954
+ async function withPendingUploadLock(options, run) {
955
+ return await withMSTeamsSqliteMutationLock(options, PENDING_UPLOAD_LOCK_FILENAME, run);
956
+ }
957
+ async function ensureLegacyImported(options, metaStore, chunkStore, ttlMs) {
958
+ await withPendingUploadLock(options, () => importLegacyStore(options, metaStore, chunkStore, ttlMs));
959
+ }
960
+ async function readUploadRows(id, metaStore, chunkStore) {
961
+ const meta = await metaStore.lookup(buildMetaKey(id));
962
+ if (!meta) return;
963
+ const chunks = [];
964
+ for (let index = 0; index < meta.chunkCount; index += 1) {
965
+ const chunk = await chunkStore.lookup(buildChunkKey(id, index));
966
+ if (!chunk || chunk.id !== id || chunk.index !== index) return;
967
+ chunks.push(Buffer.from(chunk.dataBase64, "base64"));
968
+ }
969
+ return recordToUpload(meta, Buffer.concat(chunks, meta.byteLength));
970
+ }
971
+ async function pruneUploadStore(metaStore, chunkStore, ttlMs, extraChunkRows = 0) {
972
+ const rows = await metaStore.entries();
973
+ const liveRows = [];
974
+ const now = Date.now();
975
+ let liveChunkRows = 0;
976
+ for (const row of rows) {
977
+ if (now - row.value.createdAt > ttlMs) {
978
+ await deleteUploadRows(row.value.id, metaStore, chunkStore);
979
+ continue;
980
+ }
981
+ liveChunkRows += row.value.chunkCount;
982
+ liveRows.push(row);
983
+ }
984
+ if (liveRows.length <= MAX_PENDING_UPLOADS && liveChunkRows + extraChunkRows <= MAX_PENDING_UPLOAD_CHUNK_ROWS) return;
985
+ const sorted = liveRows.toSorted((a, b) => a.value.createdAt - b.value.createdAt || a.value.id.localeCompare(b.value.id));
986
+ for (const row of sorted) {
987
+ if (liveRows.length <= MAX_PENDING_UPLOADS && liveChunkRows + extraChunkRows <= MAX_PENDING_UPLOAD_CHUNK_ROWS) break;
988
+ await deleteUploadRows(row.value.id, metaStore, chunkStore);
989
+ liveChunkRows -= row.value.chunkCount;
990
+ liveRows.pop();
991
+ }
992
+ }
594
993
  /**
595
994
  * Persist a pending upload record so another process can read it back.
596
995
  * Pass in the pre-generated id (same as the one placed in the consent card
@@ -598,10 +997,11 @@ async function readStore(filePath, ttlMs) {
598
997
  */
599
998
  async function storePendingUploadFs(upload, options) {
600
999
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
601
- const filePath = resolveFilePath(options);
602
- await withFileLock$1(filePath, empty, async () => {
603
- const store = await readStore(filePath, ttlMs);
604
- store.uploads[upload.id] = {
1000
+ const metaStore = createMetaStore(options);
1001
+ const chunkStore = createChunkStore(options);
1002
+ await withPendingUploadLock(options, async () => {
1003
+ await importLegacyStore(options, metaStore, chunkStore, ttlMs);
1004
+ await registerUploadRows({
605
1005
  id: upload.id,
606
1006
  bufferBase64: upload.buffer.toString("base64"),
607
1007
  filename: upload.filename,
@@ -609,9 +1009,8 @@ async function storePendingUploadFs(upload, options) {
609
1009
  conversationId: upload.conversationId,
610
1010
  consentCardActivityId: upload.consentCardActivityId,
611
1011
  createdAt: Date.now()
612
- };
613
- store.uploads = pruneToLimit(pruneExpired(store.uploads, Date.now(), ttlMs));
614
- await writeJsonFile(filePath, store);
1012
+ }, metaStore, chunkStore, ttlMs, true);
1013
+ await pruneUploadStore(metaStore, chunkStore, ttlMs);
615
1014
  });
616
1015
  }
617
1016
  /**
@@ -620,10 +1019,16 @@ async function storePendingUploadFs(upload, options) {
620
1019
  async function getPendingUploadFs(id, options) {
621
1020
  if (!id) return;
622
1021
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
623
- const record = (await readStore(resolveFilePath(options), ttlMs)).uploads[id];
624
- if (!record) return;
625
- if (Date.now() - record.createdAt > ttlMs) return;
626
- return recordToUpload(record);
1022
+ const metaStore = createMetaStore(options);
1023
+ const chunkStore = createChunkStore(options);
1024
+ await ensureLegacyImported(options, metaStore, chunkStore, ttlMs);
1025
+ const upload = await readUploadRows(id, metaStore, chunkStore);
1026
+ if (!upload) return;
1027
+ if (Date.now() - upload.createdAt > ttlMs) {
1028
+ await removePendingUploadFs(id, options);
1029
+ return;
1030
+ }
1031
+ return upload;
627
1032
  }
628
1033
  /**
629
1034
  * Remove a persisted pending upload (after successful upload or decline).
@@ -632,12 +1037,11 @@ async function getPendingUploadFs(id, options) {
632
1037
  async function removePendingUploadFs(id, options) {
633
1038
  if (!id) return;
634
1039
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
635
- const filePath = resolveFilePath(options);
636
- await withFileLock$1(filePath, empty, async () => {
637
- const store = await readStore(filePath, ttlMs);
638
- if (!(id in store.uploads)) return;
639
- delete store.uploads[id];
640
- await writeJsonFile(filePath, store);
1040
+ const metaStore = createMetaStore(options);
1041
+ const chunkStore = createChunkStore(options);
1042
+ await withPendingUploadLock(options, async () => {
1043
+ await importLegacyStore(options, metaStore, chunkStore, ttlMs);
1044
+ await deleteUploadRows(id, metaStore, chunkStore);
641
1045
  });
642
1046
  }
643
1047
  /**
@@ -646,13 +1050,16 @@ async function removePendingUploadFs(id, options) {
646
1050
  */
647
1051
  async function setPendingUploadActivityIdFs(id, activityId, options) {
648
1052
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
649
- const filePath = resolveFilePath(options);
650
- await withFileLock$1(filePath, empty, async () => {
651
- const store = await readStore(filePath, ttlMs);
652
- const record = store.uploads[id];
653
- if (!record) return;
654
- record.consentCardActivityId = activityId;
655
- await writeJsonFile(filePath, store);
1053
+ const metaStore = createMetaStore(options);
1054
+ const chunkStore = createChunkStore(options);
1055
+ await withPendingUploadLock(options, async () => {
1056
+ await importLegacyStore(options, metaStore, chunkStore, ttlMs);
1057
+ const record = await metaStore.lookup(buildMetaKey(id));
1058
+ if (!record || Date.now() - record.createdAt > ttlMs) return;
1059
+ await metaStore.register(buildMetaKey(id), toPluginJsonValue({
1060
+ ...record,
1061
+ consentCardActivityId: activityId
1062
+ }));
656
1063
  });
657
1064
  }
658
1065
  //#endregion
@@ -1693,7 +2100,7 @@ async function resolveMSTeamsSendContext(params) {
1693
2100
  if (!msteamsCfg?.enabled) throw new Error("msteams provider is not enabled");
1694
2101
  const creds = resolveMSTeamsCredentials(msteamsCfg);
1695
2102
  if (!creds) throw new Error("msteams credentials not configured");
1696
- const store = createMSTeamsConversationStoreFs();
2103
+ const store = createMSTeamsConversationStoreState();
1697
2104
  const recipient = parseRecipient(params.to);
1698
2105
  const found = await findConversationReference({
1699
2106
  ...recipient,
@@ -2268,7 +2675,7 @@ async function probeMSTeams(cfg) {
2268
2675
  if (cfg?.delegatedAuth?.enabled) try {
2269
2676
  const tokens = loadDelegatedTokens();
2270
2677
  if (tokens) {
2271
- const isExpired = tokens.expiresAt <= Date.now();
2678
+ const isExpired = !isFutureDateTimestampMs(tokens.expiresAt);
2272
2679
  delegatedAuth = {
2273
2680
  ok: !isExpired,
2274
2681
  scopes: tokens.scopes,
@@ -2300,4 +2707,4 @@ async function probeMSTeams(cfg) {
2300
2707
  }
2301
2708
  }
2302
2709
  //#endregion
2303
- export { readJsonFile as C, createMSTeamsConversationStoreFs as S, writeJsonFile as T, buildFileInfoCard as _, sendMessageMSTeams as a, createMSTeamsPollStoreFs as b, renderReplyPayloadsToMessages as c, withRevokedProxyFallback as d, resolveGraphChatId as f, removePendingUploadFs as g, getPendingUploadFs as h, sendAdaptiveCardMSTeams as i, sendMSTeamsMessages as l, removePendingUpload as m, deleteMessageMSTeams as n, sendPollMSTeams as o, getPendingUpload as p, editMessageMSTeams as r, buildConversationReference as s, probeMSTeams as t, sendMSTeamsActivityWithReference as u, parseFileConsentInvoke as v, withFileLock$1 as w, extractMSTeamsPollVote as x, uploadToConsentUrl as y };
2710
+ export { resolveMSTeamsSqliteStateEnv as C, readJsonFile as E, createMSTeamsConversationStoreState as S, withMSTeamsSqliteMutationLock as T, buildFileInfoCard as _, sendMessageMSTeams as a, createMSTeamsPollStoreState as b, renderReplyPayloadsToMessages as c, withRevokedProxyFallback as d, resolveGraphChatId as f, removePendingUploadFs as g, getPendingUploadFs as h, sendAdaptiveCardMSTeams as i, sendMSTeamsMessages as l, removePendingUpload as m, deleteMessageMSTeams as n, sendPollMSTeams as o, getPendingUpload as p, editMessageMSTeams as r, buildConversationReference as s, probeMSTeams as t, sendMSTeamsActivityWithReference as u, parseFileConsentInvoke as v, toPluginJsonValue as w, extractMSTeamsPollVote as x, uploadToConsentUrl as y };