@openclaw/msteams 2026.6.2-beta.1 → 2026.6.5-beta.2

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.
@@ -0,0 +1,548 @@
1
+ import { t as getMSTeamsRuntime } from "./runtime-BS5AZrKK.js";
2
+ import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ import { withFileLock } from "openclaw/plugin-sdk/file-lock";
4
+ import path from "node:path";
5
+ import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
6
+ import { pathExists } from "openclaw/plugin-sdk/security-runtime";
7
+ import crypto from "node:crypto";
8
+ import { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
9
+ //#region extensions/msteams/src/conversation-store-helpers.ts
10
+ function normalizeStoredConversationId(raw) {
11
+ return raw.split(";")[0] ?? raw;
12
+ }
13
+ function parseStoredConversationTimestamp(value) {
14
+ if (!value) return null;
15
+ const parsed = Date.parse(value);
16
+ if (!Number.isFinite(parsed)) return null;
17
+ return parsed;
18
+ }
19
+ function toConversationStoreEntries(entries) {
20
+ return Array.from(entries, ([conversationId, reference]) => ({
21
+ conversationId,
22
+ reference
23
+ }));
24
+ }
25
+ function mergeStoredConversationReference(existing, incoming, nowIso) {
26
+ return {
27
+ ...existing?.timezone && !incoming.timezone ? { timezone: existing.timezone } : {},
28
+ ...existing?.graphChatId && !incoming.graphChatId ? { graphChatId: existing.graphChatId } : {},
29
+ ...existing?.tenantId && !incoming.tenantId ? { tenantId: existing.tenantId } : {},
30
+ ...existing?.aadObjectId && !incoming.aadObjectId ? { aadObjectId: existing.aadObjectId } : {},
31
+ ...incoming,
32
+ lastSeenAt: nowIso
33
+ };
34
+ }
35
+ function findPreferredDmConversationByUserId(entries, id) {
36
+ const target = id.trim();
37
+ if (!target) return null;
38
+ const personalMatches = [];
39
+ const unknownTypeMatches = [];
40
+ for (const entry of entries) {
41
+ if (entry.reference.user?.aadObjectId !== target && entry.reference.user?.id !== target) continue;
42
+ const convType = normalizeLowercaseStringOrEmpty(entry.reference.conversation?.conversationType ?? "");
43
+ if (convType === "personal") personalMatches.push(entry);
44
+ else if (convType === "channel" || convType === "groupchat") {} else unknownTypeMatches.push(entry);
45
+ }
46
+ const candidates = personalMatches.length > 0 ? personalMatches : unknownTypeMatches;
47
+ if (candidates.length === 0) return null;
48
+ if (candidates.length > 1) candidates.sort((a, b) => (parseStoredConversationTimestamp(b.reference.lastSeenAt) ?? 0) - (parseStoredConversationTimestamp(a.reference.lastSeenAt) ?? 0));
49
+ return candidates[0] ?? null;
50
+ }
51
+ //#endregion
52
+ //#region extensions/msteams/src/store-fs.ts
53
+ const STORE_LOCK_OPTIONS = {
54
+ retries: {
55
+ retries: 10,
56
+ factor: 2,
57
+ minTimeout: 100,
58
+ maxTimeout: 1e4,
59
+ randomize: true
60
+ },
61
+ stale: 3e4
62
+ };
63
+ async function writeJsonFile(filePath, value) {
64
+ await writeJsonFileAtomically(filePath, value);
65
+ }
66
+ async function ensureJsonFile(filePath, fallback) {
67
+ if (!await pathExists(filePath)) await writeJsonFile(filePath, fallback);
68
+ }
69
+ async function withFileLock$1(filePath, fallback, fn) {
70
+ await ensureJsonFile(filePath, fallback);
71
+ return await withFileLock(filePath, STORE_LOCK_OPTIONS, async () => {
72
+ return await fn();
73
+ });
74
+ }
75
+ //#endregion
76
+ //#region extensions/msteams/src/sqlite-state.ts
77
+ function resolveStateDirOverride(options) {
78
+ if (!options) return;
79
+ if (options.stateDir) return options.stateDir;
80
+ if (options.storePath) return path.dirname(options.storePath);
81
+ if (options.homedir) return getMSTeamsRuntime().state.resolveStateDir(options.env ?? process.env, options.homedir);
82
+ return options.env?.OPENCLAW_STATE_DIR?.trim() || void 0;
83
+ }
84
+ function resolveMSTeamsSqliteStateEnv(options) {
85
+ const stateDir = resolveStateDirOverride(options);
86
+ if (!stateDir) return options?.env;
87
+ return {
88
+ ...options?.env ?? process.env,
89
+ OPENCLAW_STATE_DIR: stateDir
90
+ };
91
+ }
92
+ function toPluginJsonValue(value) {
93
+ const serialized = JSON.stringify(value);
94
+ return JSON.parse(serialized);
95
+ }
96
+ function resolveMSTeamsSqliteStateDir(options) {
97
+ return resolveStateDirOverride(options) ?? getMSTeamsRuntime().state.resolveStateDir(options?.env ?? process.env, options?.homedir);
98
+ }
99
+ const sqliteMutationLocks = /* @__PURE__ */ new Map();
100
+ async function withProcessMutationLock(lockPath, fn) {
101
+ const previous = sqliteMutationLocks.get(lockPath) ?? Promise.resolve();
102
+ let release = () => {};
103
+ const next = new Promise((resolve) => {
104
+ release = resolve;
105
+ });
106
+ const chained = previous.then(() => next, () => next);
107
+ sqliteMutationLocks.set(lockPath, chained);
108
+ await previous.catch(() => void 0);
109
+ try {
110
+ return await fn();
111
+ } finally {
112
+ release();
113
+ if (sqliteMutationLocks.get(lockPath) === chained) sqliteMutationLocks.delete(lockPath);
114
+ }
115
+ }
116
+ async function withMSTeamsSqliteMutationLock(options, lockFilename, fn) {
117
+ const lockPath = path.join(resolveMSTeamsSqliteStateDir(options), lockFilename);
118
+ return await withProcessMutationLock(lockPath, async () => {
119
+ return await withFileLock$1(lockPath, { version: 1 }, fn);
120
+ });
121
+ }
122
+ //#endregion
123
+ //#region extensions/msteams/src/conversation-store-state.ts
124
+ const MSTEAMS_CONVERSATIONS_LEGACY_FILENAME = "msteams-conversations.json";
125
+ const MSTEAMS_CONVERSATIONS_NAMESPACE = "conversations";
126
+ const MSTEAMS_MAX_CONVERSATIONS = 1e3;
127
+ const MSTEAMS_SQLITE_MAX_CONVERSATION_ROWS = 2e3;
128
+ const MSTEAMS_CONVERSATION_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
129
+ const CONVERSATION_LOCK_FILENAME = "msteams-conversations.sqlite.lock";
130
+ function createConversationStateStore(params) {
131
+ return getMSTeamsRuntime().state.openKeyedStore({
132
+ namespace: MSTEAMS_CONVERSATIONS_NAMESPACE,
133
+ maxEntries: MSTEAMS_SQLITE_MAX_CONVERSATION_ROWS,
134
+ env: resolveMSTeamsSqliteStateEnv(params)
135
+ });
136
+ }
137
+ function normalizeMSTeamsLegacyConversationStore(value) {
138
+ if (value.version !== 1 || !value.conversations || typeof value.conversations !== "object" || Array.isArray(value.conversations)) return {
139
+ version: 1,
140
+ conversations: {}
141
+ };
142
+ return value;
143
+ }
144
+ function buildMSTeamsConversationStateKey(conversationId) {
145
+ return crypto.createHash("sha256").update(conversationId).digest("hex");
146
+ }
147
+ function prepareMSTeamsConversationReferenceForStorage(conversationId, reference) {
148
+ return {
149
+ ...reference,
150
+ conversation: {
151
+ ...reference.conversation,
152
+ id: conversationId
153
+ }
154
+ };
155
+ }
156
+ function getStoredConversationId(reference) {
157
+ const rawId = reference.conversation?.id;
158
+ return rawId ? normalizeStoredConversationId(rawId) : null;
159
+ }
160
+ function selectRetainedMSTeamsConversations(conversations, ttlMs = MSTEAMS_CONVERSATION_TTL_MS) {
161
+ const retained = Object.entries(conversations).filter(([, reference]) => {
162
+ const lastSeenAt = parseStoredConversationTimestamp(reference.lastSeenAt);
163
+ return lastSeenAt == null || Date.now() - lastSeenAt <= ttlMs;
164
+ });
165
+ if (retained.length <= 1e3) return retained;
166
+ retained.sort((a, b) => {
167
+ return (parseStoredConversationTimestamp(a[1].lastSeenAt) ?? 0) - (parseStoredConversationTimestamp(b[1].lastSeenAt) ?? 0) || a[0].localeCompare(b[0]);
168
+ });
169
+ return retained.slice(retained.length - MSTEAMS_MAX_CONVERSATIONS);
170
+ }
171
+ function createMSTeamsConversationStoreState(params) {
172
+ const ttlMs = params?.ttlMs ?? 31536e6;
173
+ const conversationStore = createConversationStateStore(params);
174
+ const isExpired = (reference) => {
175
+ const lastSeenAt = parseStoredConversationTimestamp(reference.lastSeenAt);
176
+ return lastSeenAt != null && Date.now() - lastSeenAt > ttlMs;
177
+ };
178
+ const lookupStored = async (conversationId) => {
179
+ const normalizedId = normalizeStoredConversationId(conversationId);
180
+ const value = await conversationStore.lookup(buildMSTeamsConversationStateKey(normalizedId));
181
+ if (!value) return null;
182
+ if (isExpired(value)) return null;
183
+ return value;
184
+ };
185
+ const entries = async () => {
186
+ const rows = await conversationStore.entries();
187
+ const kept = [];
188
+ for (const row of rows) {
189
+ if (isExpired(row.value)) continue;
190
+ const conversationId = getStoredConversationId(row.value);
191
+ if (conversationId) kept.push([conversationId, row.value]);
192
+ }
193
+ return kept;
194
+ };
195
+ const lookup = async (conversationId) => {
196
+ return await lookupStored(conversationId);
197
+ };
198
+ const register = async (conversationId, reference) => {
199
+ const normalizedId = normalizeStoredConversationId(conversationId);
200
+ await conversationStore.register(buildMSTeamsConversationStateKey(normalizedId), toPluginJsonValue(prepareMSTeamsConversationReferenceForStorage(normalizedId, reference)));
201
+ const rows = [];
202
+ for (const row of await conversationStore.entries()) {
203
+ if (isExpired(row.value)) {
204
+ await conversationStore.delete(row.key);
205
+ continue;
206
+ }
207
+ rows.push(row);
208
+ }
209
+ if (rows.length <= 1e3) return;
210
+ const sorted = rows.toSorted((a, b) => {
211
+ const aTs = parseStoredConversationTimestamp(a.value.lastSeenAt) ?? 0;
212
+ const bTs = parseStoredConversationTimestamp(b.value.lastSeenAt) ?? 0;
213
+ const aId = getStoredConversationId(a.value) ?? a.key;
214
+ const bId = getStoredConversationId(b.value) ?? b.key;
215
+ return aTs - bTs || aId.localeCompare(bId);
216
+ });
217
+ for (const row of sorted.slice(0, rows.length - MSTEAMS_MAX_CONVERSATIONS)) await conversationStore.delete(row.key);
218
+ };
219
+ const list = async () => {
220
+ return toConversationStoreEntries(await entries());
221
+ };
222
+ const get = async (conversationId) => {
223
+ return await lookup(conversationId);
224
+ };
225
+ const findPreferredDmByUserId = async (id) => {
226
+ return findPreferredDmConversationByUserId(await list(), id);
227
+ };
228
+ const upsert = async (conversationId, reference) => {
229
+ const normalizedId = normalizeStoredConversationId(conversationId);
230
+ await withMSTeamsSqliteMutationLock(params, CONVERSATION_LOCK_FILENAME, async () => {
231
+ await register(normalizedId, mergeStoredConversationReference(await lookupStored(normalizedId) ?? void 0, reference, (/* @__PURE__ */ new Date()).toISOString()));
232
+ });
233
+ };
234
+ const remove = async (conversationId) => {
235
+ const normalizedId = normalizeStoredConversationId(conversationId);
236
+ return await withMSTeamsSqliteMutationLock(params, CONVERSATION_LOCK_FILENAME, async () => {
237
+ return await conversationStore.delete(buildMSTeamsConversationStateKey(normalizedId));
238
+ });
239
+ };
240
+ return {
241
+ upsert,
242
+ get,
243
+ list,
244
+ remove,
245
+ findPreferredDmByUserId,
246
+ findByUserId: findPreferredDmByUserId
247
+ };
248
+ }
249
+ //#endregion
250
+ //#region extensions/msteams/src/polls.ts
251
+ const MSTEAMS_POLLS_LEGACY_FILENAME = "msteams-polls.json";
252
+ const MSTEAMS_POLLS_NAMESPACE = "polls";
253
+ const MSTEAMS_POLL_VOTE_BUCKETS_NAMESPACE = "poll-vote-buckets";
254
+ const MSTEAMS_MAX_POLLS = 1e3;
255
+ const MSTEAMS_SQLITE_MAX_POLL_ROWS = 2e3;
256
+ const MSTEAMS_MAX_POLL_VOTE_BUCKET_ROWS = 1001 * 32;
257
+ const MSTEAMS_POLL_TTL_MS = 720 * 60 * 60 * 1e3;
258
+ const POLL_LOCK_FILENAME = "msteams-polls.sqlite.lock";
259
+ function normalizeChoiceValue(value) {
260
+ if (typeof value === "string") {
261
+ const trimmed = value.trim();
262
+ return trimmed ? trimmed : null;
263
+ }
264
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
265
+ return null;
266
+ }
267
+ function extractSelections(value) {
268
+ if (Array.isArray(value)) return value.map(normalizeChoiceValue).filter((entry) => Boolean(entry));
269
+ const normalized = normalizeChoiceValue(value);
270
+ if (!normalized) return [];
271
+ if (normalized.includes(",")) return normalizeStringEntries(normalized.split(","));
272
+ return [normalized];
273
+ }
274
+ function readNestedValue(value, keys) {
275
+ let current = value;
276
+ for (const key of keys) {
277
+ if (!isRecord(current)) return;
278
+ current = current[key];
279
+ }
280
+ return current;
281
+ }
282
+ function readNestedString(value, keys) {
283
+ return normalizeOptionalString(readNestedValue(value, keys));
284
+ }
285
+ function extractMSTeamsPollVote(activity) {
286
+ const value = activity?.value;
287
+ if (!value || !isRecord(value)) return null;
288
+ const pollId = readNestedString(value, ["openclawPollId"]) ?? readNestedString(value, ["pollId"]) ?? readNestedString(value, ["openclaw", "pollId"]) ?? readNestedString(value, [
289
+ "openclaw",
290
+ "poll",
291
+ "id"
292
+ ]) ?? readNestedString(value, ["data", "openclawPollId"]) ?? readNestedString(value, ["data", "pollId"]) ?? readNestedString(value, [
293
+ "data",
294
+ "openclaw",
295
+ "pollId"
296
+ ]) ?? readNestedString(value, [
297
+ "action",
298
+ "data",
299
+ "openclawPollId"
300
+ ]) ?? readNestedString(value, [
301
+ "action",
302
+ "data",
303
+ "pollId"
304
+ ]);
305
+ if (!pollId) return null;
306
+ const directSelections = extractSelections(value.choices);
307
+ const nestedSelections = extractSelections(readNestedValue(value, ["choices"]));
308
+ const dataSelections = extractSelections(readNestedValue(value, ["data", "choices"]));
309
+ const actionDataSelections = extractSelections(readNestedValue(value, [
310
+ "action",
311
+ "data",
312
+ "choices"
313
+ ]));
314
+ const selections = directSelections.length > 0 ? directSelections : nestedSelections.length > 0 ? nestedSelections : dataSelections.length > 0 ? dataSelections : actionDataSelections;
315
+ if (selections.length === 0) return null;
316
+ return {
317
+ pollId,
318
+ selections
319
+ };
320
+ }
321
+ function buildMSTeamsPollCard(params) {
322
+ const pollId = params.pollId ?? crypto.randomUUID();
323
+ const maxSelections = typeof params.maxSelections === "number" && params.maxSelections > 1 ? Math.floor(params.maxSelections) : 1;
324
+ const cappedMaxSelections = Math.min(Math.max(1, maxSelections), params.options.length);
325
+ const choices = params.options.map((option, index) => ({
326
+ title: option,
327
+ value: String(index)
328
+ }));
329
+ const hint = cappedMaxSelections > 1 ? `Select up to ${cappedMaxSelections} option${cappedMaxSelections === 1 ? "" : "s"}.` : "Select one option.";
330
+ const card = {
331
+ type: "AdaptiveCard",
332
+ version: "1.5",
333
+ body: [
334
+ {
335
+ type: "TextBlock",
336
+ text: params.question,
337
+ wrap: true,
338
+ weight: "Bolder",
339
+ size: "Medium"
340
+ },
341
+ {
342
+ type: "Input.ChoiceSet",
343
+ id: "choices",
344
+ isMultiSelect: cappedMaxSelections > 1,
345
+ style: "expanded",
346
+ choices
347
+ },
348
+ {
349
+ type: "TextBlock",
350
+ text: hint,
351
+ wrap: true,
352
+ isSubtle: true,
353
+ spacing: "Small"
354
+ }
355
+ ],
356
+ actions: [{
357
+ type: "Action.Execute",
358
+ title: "Vote",
359
+ verb: "openclaw.poll.vote",
360
+ data: {
361
+ openclawPollId: pollId,
362
+ pollId
363
+ }
364
+ }]
365
+ };
366
+ const fallbackLines = [`Poll: ${params.question}`, ...params.options.map((option, index) => `${index + 1}. ${option}`)];
367
+ return {
368
+ pollId,
369
+ question: params.question,
370
+ options: params.options,
371
+ maxSelections: cappedMaxSelections,
372
+ card,
373
+ fallbackText: fallbackLines.join("\n")
374
+ };
375
+ }
376
+ function createPollStateStore(params) {
377
+ return getMSTeamsRuntime().state.openKeyedStore({
378
+ namespace: MSTEAMS_POLLS_NAMESPACE,
379
+ maxEntries: MSTEAMS_SQLITE_MAX_POLL_ROWS,
380
+ env: resolveMSTeamsSqliteStateEnv(params)
381
+ });
382
+ }
383
+ function createPollVoteBucketStateStore(params) {
384
+ return getMSTeamsRuntime().state.openKeyedStore({
385
+ namespace: MSTEAMS_POLL_VOTE_BUCKETS_NAMESPACE,
386
+ maxEntries: MSTEAMS_MAX_POLL_VOTE_BUCKET_ROWS,
387
+ env: resolveMSTeamsSqliteStateEnv(params)
388
+ });
389
+ }
390
+ function parseTimestamp(value) {
391
+ if (!value) return null;
392
+ const parsed = Date.parse(value);
393
+ return Number.isFinite(parsed) ? parsed : null;
394
+ }
395
+ function pruneExpired(polls) {
396
+ const cutoff = Date.now() - MSTEAMS_POLL_TTL_MS;
397
+ const entries = Object.entries(polls).filter(([, poll]) => {
398
+ return (parseTimestamp(poll.updatedAt ?? poll.createdAt) ?? 0) >= cutoff;
399
+ });
400
+ return Object.fromEntries(entries);
401
+ }
402
+ function selectRetainedMSTeamsPolls(polls) {
403
+ const retained = Object.entries(pruneExpired(polls));
404
+ if (retained.length <= 1e3) return retained;
405
+ retained.sort((a, b) => {
406
+ return (parseTimestamp(a[1].updatedAt ?? a[1].createdAt) ?? 0) - (parseTimestamp(b[1].updatedAt ?? b[1].createdAt) ?? 0) || a[0].localeCompare(b[0]);
407
+ });
408
+ return retained.slice(retained.length - MSTEAMS_MAX_POLLS);
409
+ }
410
+ function normalizeMSTeamsPollSelections(poll, selections) {
411
+ const maxSelections = Math.max(1, poll.maxSelections);
412
+ const mapped = selections.map((entry) => parseStrictNonNegativeInteger(entry)).filter((value) => value !== void 0).filter((value) => value >= 0 && value < poll.options.length).map((value) => String(value));
413
+ return uniqueStrings(maxSelections > 1 ? mapped.slice(0, maxSelections) : mapped.slice(0, 1));
414
+ }
415
+ function splitMSTeamsPoll(poll) {
416
+ const { votes, ...metadata } = poll;
417
+ return {
418
+ metadata,
419
+ votes
420
+ };
421
+ }
422
+ function hashMSTeamsPollVote(pollId, voterId) {
423
+ return crypto.createHash("sha256").update(pollId).update("\0").update(voterId).digest("hex");
424
+ }
425
+ function buildMSTeamsPollStateKey(pollId) {
426
+ return crypto.createHash("sha256").update(pollId).digest("hex");
427
+ }
428
+ function selectMSTeamsPollVoteBucket(pollId, voterId) {
429
+ const bucket = Number.parseInt(hashMSTeamsPollVote(pollId, voterId).slice(0, 8), 16);
430
+ return String(bucket % 32).padStart(4, "0");
431
+ }
432
+ function buildMSTeamsPollVoteBucketKey(pollId, bucket) {
433
+ return `${crypto.createHash("sha256").update(pollId).digest("hex")}:${bucket}`;
434
+ }
435
+ function createMSTeamsPollStoreState(params) {
436
+ const pollStore = createPollStateStore(params);
437
+ const voteBucketStore = createPollVoteBucketStateStore(params);
438
+ const readPollVotes = async (pollId) => {
439
+ const votes = {};
440
+ for (const row of await voteBucketStore.entries()) if (row.value.pollId === pollId) Object.assign(votes, row.value.votes);
441
+ return votes;
442
+ };
443
+ const deletePollVotes = async (pollId) => {
444
+ for (const row of await voteBucketStore.entries()) if (row.value.pollId === pollId) await voteBucketStore.delete(row.key);
445
+ };
446
+ const registerPollVotes = async (pollId, votes, updatedAt) => {
447
+ const buckets = /* @__PURE__ */ new Map();
448
+ for (const [voterId, selections] of Object.entries(votes)) {
449
+ const bucket = selectMSTeamsPollVoteBucket(pollId, voterId);
450
+ const bucketVotes = buckets.get(bucket) ?? {};
451
+ bucketVotes[voterId] = selections;
452
+ buckets.set(bucket, bucketVotes);
453
+ }
454
+ for (const [bucket, bucketVotes] of buckets) {
455
+ const key = buildMSTeamsPollVoteBucketKey(pollId, bucket);
456
+ const existing = await voteBucketStore.lookup(key);
457
+ await voteBucketStore.register(key, toPluginJsonValue({
458
+ pollId,
459
+ bucket,
460
+ votes: {
461
+ ...bucketVotes,
462
+ ...existing?.votes
463
+ },
464
+ updatedAt
465
+ }));
466
+ }
467
+ };
468
+ const registerPollVote = async (pollId, voterId, selections, updatedAt) => {
469
+ const bucket = selectMSTeamsPollVoteBucket(pollId, voterId);
470
+ const key = buildMSTeamsPollVoteBucketKey(pollId, bucket);
471
+ const existing = await voteBucketStore.lookup(key);
472
+ await voteBucketStore.register(key, toPluginJsonValue({
473
+ pollId,
474
+ bucket,
475
+ votes: {
476
+ ...existing?.votes,
477
+ [voterId]: selections
478
+ },
479
+ updatedAt
480
+ }));
481
+ };
482
+ const reconstructPoll = async (metadata) => {
483
+ return {
484
+ ...metadata,
485
+ votes: await readPollVotes(metadata.id)
486
+ };
487
+ };
488
+ const prunePollStoreToLimit = async () => {
489
+ const rows = [];
490
+ for (const row of await pollStore.entries()) {
491
+ if (!pruneExpired({ [row.key]: row.value })[row.key]) {
492
+ await pollStore.delete(row.key);
493
+ await deletePollVotes(row.value.id);
494
+ continue;
495
+ }
496
+ rows.push(row);
497
+ }
498
+ if (rows.length <= 1e3) return;
499
+ const sorted = rows.toSorted((a, b) => {
500
+ return (parseTimestamp(a.value.updatedAt ?? a.value.createdAt) ?? 0) - (parseTimestamp(b.value.updatedAt ?? b.value.createdAt) ?? 0) || a.key.localeCompare(b.key);
501
+ });
502
+ for (const row of sorted.slice(0, rows.length - MSTEAMS_MAX_POLLS)) {
503
+ await pollStore.delete(row.key);
504
+ await deletePollVotes(row.value.id);
505
+ }
506
+ };
507
+ const createPoll = async (poll) => {
508
+ await withMSTeamsSqliteMutationLock(params, POLL_LOCK_FILENAME, async () => {
509
+ const { metadata, votes } = splitMSTeamsPoll(poll);
510
+ await pollStore.register(buildMSTeamsPollStateKey(poll.id), toPluginJsonValue(metadata));
511
+ await deletePollVotes(poll.id);
512
+ await registerPollVotes(poll.id, votes, poll.updatedAt ?? poll.createdAt);
513
+ await prunePollStoreToLimit();
514
+ });
515
+ };
516
+ const getPoll = async (pollId) => {
517
+ const poll = await pollStore.lookup(buildMSTeamsPollStateKey(pollId));
518
+ if (!poll) return null;
519
+ if (!pruneExpired({ [pollId]: poll })[pollId]) return null;
520
+ return await reconstructPoll(poll);
521
+ };
522
+ const recordVote = async (vote) => {
523
+ return await withMSTeamsSqliteMutationLock(params, POLL_LOCK_FILENAME, async () => {
524
+ const pollKey = buildMSTeamsPollStateKey(vote.pollId);
525
+ const poll = await pollStore.lookup(pollKey);
526
+ if (!poll) return null;
527
+ if (!pruneExpired({ [vote.pollId]: poll })[vote.pollId]) {
528
+ await pollStore.delete(pollKey);
529
+ await deletePollVotes(vote.pollId);
530
+ return null;
531
+ }
532
+ const normalized = normalizeMSTeamsPollSelections(await reconstructPoll(poll), vote.selections);
533
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
534
+ poll.updatedAt = updatedAt;
535
+ await pollStore.register(pollKey, toPluginJsonValue(poll));
536
+ await registerPollVote(vote.pollId, vote.voterId, normalized, updatedAt);
537
+ await prunePollStoreToLimit();
538
+ return await reconstructPoll(poll);
539
+ });
540
+ };
541
+ return {
542
+ createPoll,
543
+ getPoll,
544
+ recordVote
545
+ };
546
+ }
547
+ //#endregion
548
+ export { toPluginJsonValue as C, resolveMSTeamsSqliteStateEnv as S, normalizeStoredConversationId as T, buildMSTeamsConversationStateKey as _, MSTEAMS_SQLITE_MAX_POLL_ROWS as a, prepareMSTeamsConversationReferenceForStorage as b, buildMSTeamsPollVoteBucketKey as c, selectMSTeamsPollVoteBucket as d, selectRetainedMSTeamsPolls as f, MSTEAMS_SQLITE_MAX_CONVERSATION_ROWS as g, MSTEAMS_CONVERSATIONS_NAMESPACE as h, MSTEAMS_POLL_VOTE_BUCKETS_NAMESPACE as i, createMSTeamsPollStoreState as l, MSTEAMS_CONVERSATIONS_LEGACY_FILENAME as m, MSTEAMS_POLLS_LEGACY_FILENAME as n, buildMSTeamsPollCard as o, splitMSTeamsPoll as p, MSTEAMS_POLLS_NAMESPACE as r, buildMSTeamsPollStateKey as s, MSTEAMS_MAX_POLL_VOTE_BUCKET_ROWS as t, extractMSTeamsPollVote as u, createMSTeamsConversationStoreState as v, withMSTeamsSqliteMutationLock as w, selectRetainedMSTeamsConversations as x, normalizeMSTeamsLegacyConversationStore as y };