@openclaw/msteams 2026.6.2-beta.1 → 2026.6.5-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,657 +1,22 @@
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 { B as isAllowedBotFrameworkServiceUrl, C as resolveMSTeamsCredentials, F as createMSTeamsTokenProvider, G as validateMSTeamsProactiveServiceUrlBoundary, I as loadMSTeamsSdkWithAuth, L as buildUserAgent, N as readAccessToken, S as loadDelegatedTokens, T as resolveMSTeamsStorePath, U as createMSTeamsHttpError, V as normalizeBotFrameworkServiceUrl, W as resolveMSTeamsSdkCloudOptions, z as describeBotFrameworkServiceUrlHost } from "./resolve-allowlist-C6VVTfbj.js";
3
- import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel-BwNnuHbh.js";
4
- import { a as formatUnknownError, i as formatMSTeamsSendErrorHint, o as isRevokedProxyError, r as classifyMSTeamsSendError } from "./setup-surface-DdyMxq-W.js";
1
+ import { t as getMSTeamsRuntime } from "./runtime-BS5AZrKK.js";
2
+ import { detectMime, extensionForMime, extractOriginalFilename, getFileExtension, loadOutboundMediaFromUrl, normalizeStringEntries as normalizeStringEntries$1, resolveChannelMediaMaxBytes } from "./runtime-api.js";
3
+ import { B as normalizeBotFrameworkServiceUrl, C as resolveMSTeamsCredentials, F as loadMSTeamsSdkWithAuth, H as createMSTeamsHttpError, I as buildUserAgent, M as readAccessToken, P as createMSTeamsTokenProvider, R as describeBotFrameworkServiceUrlHost, S as loadDelegatedTokens, U as resolveMSTeamsSdkCloudOptions, W as validateMSTeamsProactiveServiceUrlBoundary, z as isAllowedBotFrameworkServiceUrl } from "./resolve-allowlist-BzIUWmMm.js";
4
+ import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel--oT3fyD5.js";
5
+ import { a as formatUnknownError, i as formatMSTeamsSendErrorHint, o as isRevokedProxyError, r as classifyMSTeamsSendError } from "./setup-surface-Dik4VU7f.js";
6
+ import { C as toPluginJsonValue, S as resolveMSTeamsSqliteStateEnv, o as buildMSTeamsPollCard, v as createMSTeamsConversationStoreState, w as withMSTeamsSqliteMutationLock } from "./polls-C1VgSvKE.js";
5
7
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
6
- import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
7
- import { withFileLock } from "openclaw/plugin-sdk/file-lock";
8
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
8
9
  import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
9
10
  import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
10
11
  import { lookup } from "node:dns/promises";
11
12
  import { isPrivateIpAddress } from "openclaw/plugin-sdk/ssrf-policy";
12
13
  import path from "node:path";
13
- import { isFutureDateTimestampMs, parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
14
- import { pathExists } from "openclaw/plugin-sdk/security-runtime";
14
+ import { isFutureDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
15
15
  import crypto, { createHash } from "node:crypto";
16
- import fs from "node:fs/promises";
17
- import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
18
16
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
19
17
  import { SILENT_REPLY_TOKEN, isSilentReplyText } from "openclaw/plugin-sdk/reply-chunking";
20
18
  import { sleep } from "openclaw/plugin-sdk/text-utility-runtime";
21
19
  import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
22
- //#region extensions/msteams/src/conversation-store-helpers.ts
23
- function normalizeStoredConversationId(raw) {
24
- return raw.split(";")[0] ?? raw;
25
- }
26
- function parseStoredConversationTimestamp(value) {
27
- if (!value) return null;
28
- const parsed = Date.parse(value);
29
- if (!Number.isFinite(parsed)) return null;
30
- return parsed;
31
- }
32
- function toConversationStoreEntries(entries) {
33
- return Array.from(entries, ([conversationId, reference]) => ({
34
- conversationId,
35
- reference
36
- }));
37
- }
38
- function mergeStoredConversationReference(existing, incoming, nowIso) {
39
- return {
40
- ...existing?.timezone && !incoming.timezone ? { timezone: existing.timezone } : {},
41
- ...existing?.graphChatId && !incoming.graphChatId ? { graphChatId: existing.graphChatId } : {},
42
- ...existing?.tenantId && !incoming.tenantId ? { tenantId: existing.tenantId } : {},
43
- ...existing?.aadObjectId && !incoming.aadObjectId ? { aadObjectId: existing.aadObjectId } : {},
44
- ...incoming,
45
- lastSeenAt: nowIso
46
- };
47
- }
48
- function findPreferredDmConversationByUserId(entries, id) {
49
- const target = id.trim();
50
- if (!target) return null;
51
- const personalMatches = [];
52
- const unknownTypeMatches = [];
53
- for (const entry of entries) {
54
- if (entry.reference.user?.aadObjectId !== target && entry.reference.user?.id !== target) continue;
55
- const convType = normalizeLowercaseStringOrEmpty(entry.reference.conversation?.conversationType ?? "");
56
- if (convType === "personal") personalMatches.push(entry);
57
- else if (convType === "channel" || convType === "groupchat") {} else unknownTypeMatches.push(entry);
58
- }
59
- const candidates = personalMatches.length > 0 ? personalMatches : unknownTypeMatches;
60
- if (candidates.length === 0) return null;
61
- if (candidates.length > 1) candidates.sort((a, b) => (parseStoredConversationTimestamp(b.reference.lastSeenAt) ?? 0) - (parseStoredConversationTimestamp(a.reference.lastSeenAt) ?? 0));
62
- return candidates[0] ?? null;
63
- }
64
- //#endregion
65
- //#region extensions/msteams/src/store-fs.ts
66
- const STORE_LOCK_OPTIONS = {
67
- retries: {
68
- retries: 10,
69
- factor: 2,
70
- minTimeout: 100,
71
- maxTimeout: 1e4,
72
- randomize: true
73
- },
74
- stale: 3e4
75
- };
76
- async function readJsonFile(filePath, fallback) {
77
- return await readJsonFileWithFallback(filePath, fallback);
78
- }
79
- async function writeJsonFile(filePath, value) {
80
- await writeJsonFileAtomically(filePath, value);
81
- }
82
- async function ensureJsonFile(filePath, fallback) {
83
- if (!await pathExists(filePath)) await writeJsonFile(filePath, fallback);
84
- }
85
- async function withFileLock$1(filePath, fallback, fn) {
86
- await ensureJsonFile(filePath, fallback);
87
- return await withFileLock(filePath, STORE_LOCK_OPTIONS, async () => {
88
- return await fn();
89
- });
90
- }
91
- //#endregion
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
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";
144
- const MAX_CONVERSATIONS = 1e3;
145
- const SQLITE_MAX_CONVERSATION_ROWS = 2e3;
146
- const CONVERSATION_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
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)
153
- });
154
- }
155
- function createConversationMigrationStore(params) {
156
- return getMSTeamsRuntime().state.openKeyedStore({
157
- namespace: CONVERSATION_MIGRATIONS_NAMESPACE,
158
- maxEntries: 100,
159
- env: resolveMSTeamsSqliteStateEnv(params)
160
- });
161
- }
162
- function resolveLegacyStorePath(params) {
163
- return resolveMSTeamsStorePath({
164
- filename: STORE_FILENAME$2,
165
- env: params?.env,
166
- homedir: params?.homedir,
167
- stateDir: params?.stateDir,
168
- storePath: params?.storePath
169
- });
170
- }
171
- function normalizeLegacyStore(value) {
172
- if (value.version !== 1 || !value.conversations || typeof value.conversations !== "object" || Array.isArray(value.conversations)) return {
173
- version: 1,
174
- conversations: {}
175
- };
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, {
215
- version: 1,
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);
277
- };
278
- const list = async () => {
279
- return toConversationStoreEntries(await entries());
280
- };
281
- const get = async (conversationId) => {
282
- return await lookup(conversationId);
283
- };
284
- const findPreferredDmByUserId = async (id) => {
285
- return findPreferredDmConversationByUserId(await list(), id);
286
- };
287
- const upsert = async (conversationId, reference) => {
288
- const normalizedId = normalizeStoredConversationId(conversationId);
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()));
292
- });
293
- };
294
- const remove = async (conversationId) => {
295
- const normalizedId = normalizeStoredConversationId(conversationId);
296
- return await withMSTeamsSqliteMutationLock(params, CONVERSATION_LOCK_FILENAME, async () => {
297
- await importLegacyStore();
298
- return await conversationStore.delete(buildConversationStateKey(normalizedId));
299
- });
300
- };
301
- return {
302
- upsert,
303
- get,
304
- list,
305
- remove,
306
- findPreferredDmByUserId,
307
- findByUserId: findPreferredDmByUserId
308
- };
309
- }
310
- //#endregion
311
- //#region extensions/msteams/src/polls.ts
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";
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;
321
- const POLL_TTL_MS = 720 * 60 * 60 * 1e3;
322
- const POLL_LOCK_FILENAME = "msteams-polls.sqlite.lock";
323
- function normalizeChoiceValue(value) {
324
- if (typeof value === "string") {
325
- const trimmed = value.trim();
326
- return trimmed ? trimmed : null;
327
- }
328
- if (typeof value === "number" && Number.isFinite(value)) return String(value);
329
- return null;
330
- }
331
- function extractSelections(value) {
332
- if (Array.isArray(value)) return value.map(normalizeChoiceValue).filter((entry) => Boolean(entry));
333
- const normalized = normalizeChoiceValue(value);
334
- if (!normalized) return [];
335
- if (normalized.includes(",")) return normalizeStringEntries(normalized.split(","));
336
- return [normalized];
337
- }
338
- function readNestedValue(value, keys) {
339
- let current = value;
340
- for (const key of keys) {
341
- if (!isRecord(current)) return;
342
- current = current[key];
343
- }
344
- return current;
345
- }
346
- function readNestedString(value, keys) {
347
- return normalizeOptionalString(readNestedValue(value, keys));
348
- }
349
- function extractMSTeamsPollVote(activity) {
350
- const value = activity?.value;
351
- if (!value || !isRecord(value)) return null;
352
- const pollId = readNestedString(value, ["openclawPollId"]) ?? readNestedString(value, ["pollId"]) ?? readNestedString(value, ["openclaw", "pollId"]) ?? readNestedString(value, [
353
- "openclaw",
354
- "poll",
355
- "id"
356
- ]) ?? readNestedString(value, ["data", "openclawPollId"]) ?? readNestedString(value, ["data", "pollId"]) ?? readNestedString(value, [
357
- "data",
358
- "openclaw",
359
- "pollId"
360
- ]) ?? readNestedString(value, [
361
- "action",
362
- "data",
363
- "openclawPollId"
364
- ]) ?? readNestedString(value, [
365
- "action",
366
- "data",
367
- "pollId"
368
- ]);
369
- if (!pollId) return null;
370
- const directSelections = extractSelections(value.choices);
371
- const nestedSelections = extractSelections(readNestedValue(value, ["choices"]));
372
- const dataSelections = extractSelections(readNestedValue(value, ["data", "choices"]));
373
- const actionDataSelections = extractSelections(readNestedValue(value, [
374
- "action",
375
- "data",
376
- "choices"
377
- ]));
378
- const selections = directSelections.length > 0 ? directSelections : nestedSelections.length > 0 ? nestedSelections : dataSelections.length > 0 ? dataSelections : actionDataSelections;
379
- if (selections.length === 0) return null;
380
- return {
381
- pollId,
382
- selections
383
- };
384
- }
385
- function buildMSTeamsPollCard(params) {
386
- const pollId = params.pollId ?? crypto.randomUUID();
387
- const maxSelections = typeof params.maxSelections === "number" && params.maxSelections > 1 ? Math.floor(params.maxSelections) : 1;
388
- const cappedMaxSelections = Math.min(Math.max(1, maxSelections), params.options.length);
389
- const choices = params.options.map((option, index) => ({
390
- title: option,
391
- value: String(index)
392
- }));
393
- const hint = cappedMaxSelections > 1 ? `Select up to ${cappedMaxSelections} option${cappedMaxSelections === 1 ? "" : "s"}.` : "Select one option.";
394
- const card = {
395
- type: "AdaptiveCard",
396
- version: "1.5",
397
- body: [
398
- {
399
- type: "TextBlock",
400
- text: params.question,
401
- wrap: true,
402
- weight: "Bolder",
403
- size: "Medium"
404
- },
405
- {
406
- type: "Input.ChoiceSet",
407
- id: "choices",
408
- isMultiSelect: cappedMaxSelections > 1,
409
- style: "expanded",
410
- choices
411
- },
412
- {
413
- type: "TextBlock",
414
- text: hint,
415
- wrap: true,
416
- isSubtle: true,
417
- spacing: "Small"
418
- }
419
- ],
420
- actions: [{
421
- type: "Action.Execute",
422
- title: "Vote",
423
- verb: "openclaw.poll.vote",
424
- data: {
425
- openclawPollId: pollId,
426
- pollId
427
- }
428
- }]
429
- };
430
- const fallbackLines = [`Poll: ${params.question}`, ...params.options.map((option, index) => `${index + 1}. ${option}`)];
431
- return {
432
- pollId,
433
- question: params.question,
434
- options: params.options,
435
- maxSelections: cappedMaxSelections,
436
- card,
437
- fallbackText: fallbackLines.join("\n")
438
- };
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
- }
461
- function parseTimestamp(value) {
462
- if (!value) return null;
463
- const parsed = Date.parse(value);
464
- return Number.isFinite(parsed) ? parsed : null;
465
- }
466
- function pruneExpired$1(polls) {
467
- const cutoff = Date.now() - POLL_TTL_MS;
468
- const entries = Object.entries(polls).filter(([, poll]) => {
469
- return (parseTimestamp(poll.updatedAt ?? poll.createdAt) ?? 0) >= cutoff;
470
- });
471
- return Object.fromEntries(entries);
472
- }
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]);
478
- });
479
- return retained.slice(retained.length - MAX_POLLS);
480
- }
481
- function normalizeMSTeamsPollSelections(poll, selections) {
482
- const maxSelections = Math.max(1, poll.maxSelections);
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));
484
- return uniqueStrings(maxSelections > 1 ? mapped.slice(0, maxSelections) : mapped.slice(0, 1));
485
- }
486
- function createMSTeamsPollStoreState(params) {
487
- const pollStore = createPollStateStore(params);
488
- const voteBucketStore = createPollVoteBucketStateStore(params);
489
- const migrationStore = createPollMigrationStore(params);
490
- const legacyStorePath = resolveMSTeamsStorePath({
491
- filename: STORE_FILENAME$1,
492
- env: params?.env,
493
- homedir: params?.homedir,
494
- stateDir: params?.stateDir,
495
- storePath: params?.storePath
496
- });
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}`;
517
- };
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) => {
563
- return {
564
- ...metadata,
565
- votes: await readPollVotes(metadata.id)
566
- };
567
- };
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
- }
610
- };
611
- const createPoll = async (poll) => {
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();
619
- });
620
- };
621
- const getPoll = async (pollId) => {
622
- await ensureLegacyImported();
623
- const poll = await pollStore.lookup(buildPollStateKey(pollId));
624
- if (!poll) return null;
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);
646
- });
647
- };
648
- return {
649
- createPoll,
650
- getPoll,
651
- recordVote
652
- };
653
- }
654
- //#endregion
655
20
  //#region extensions/msteams/src/file-consent.ts
656
21
  /**
657
22
  * FileConsentCard utilities for MS Teams large file uploads (>4MB) in personal chats.
@@ -795,24 +160,9 @@ const MAX_CHUNKS_PER_UPLOAD = 3072;
795
160
  const MAX_PENDING_UPLOAD_CHUNK_ROWS = 45e3;
796
161
  const RAW_CHUNK_BYTES = 36 * 1024;
797
162
  const PENDING_UPLOAD_META_MAX_ENTRIES = 200;
798
- const STORE_FILENAME = "msteams-pending-uploads.json";
799
163
  const PENDING_UPLOAD_META_NAMESPACE = "pending-uploads";
800
164
  const PENDING_UPLOAD_CHUNKS_NAMESPACE = "pending-upload-chunks";
801
- const PENDING_UPLOAD_MIGRATIONS_NAMESPACE = "pending-upload-migrations";
802
165
  const PENDING_UPLOAD_LOCK_FILENAME = "msteams-pending-uploads.sqlite.lock";
803
- const empty = {
804
- version: 1,
805
- uploads: {}
806
- };
807
- function resolveLegacyFilePath(options) {
808
- return resolveMSTeamsStorePath({
809
- filename: STORE_FILENAME,
810
- env: options?.env,
811
- homedir: options?.homedir,
812
- stateDir: options?.stateDir,
813
- storePath: options?.storePath
814
- });
815
- }
816
166
  function createMetaStore(options) {
817
167
  return getMSTeamsRuntime().state.openKeyedStore({
818
168
  namespace: PENDING_UPLOAD_META_NAMESPACE,
@@ -827,13 +177,6 @@ function createChunkStore(options) {
827
177
  env: resolveMSTeamsSqliteStateEnv(options)
828
178
  });
829
179
  }
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
180
  function buildUploadKey(id) {
838
181
  return `upload:${createHash("sha256").update(id).digest("hex")}`;
839
182
  }
@@ -843,24 +186,6 @@ function buildMetaKey(id) {
843
186
  function buildChunkKey(id, index) {
844
187
  return `${buildUploadKey(id)}:chunk:${String(index).padStart(4, "0")}`;
845
188
  }
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
- }
852
- function pruneExpired(uploads, nowMs, ttlMs) {
853
- const kept = {};
854
- for (const [id, record] of Object.entries(uploads)) if (nowMs - record.createdAt <= ttlMs) kept[id] = record;
855
- return kept;
856
- }
857
- function pruneToLimit(uploads) {
858
- const entries = Object.entries(uploads);
859
- if (entries.length <= MAX_PENDING_UPLOADS) return uploads;
860
- entries.sort((a, b) => a[1].createdAt - b[1].createdAt);
861
- const keep = entries.slice(entries.length - MAX_PENDING_UPLOADS);
862
- return Object.fromEntries(keep);
863
- }
864
189
  function recordToUpload(record, buffer) {
865
190
  return {
866
191
  id: record.id,
@@ -872,34 +197,6 @@ function recordToUpload(record, buffer) {
872
197
  createdAt: record.createdAt
873
198
  };
874
199
  }
875
- function isValidStore(value) {
876
- if (!value || typeof value !== "object") return false;
877
- const candidate = value;
878
- return candidate.version === 1 && typeof candidate.uploads === "object" && candidate.uploads !== null && !Array.isArray(candidate.uploads);
879
- }
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;
884
- return {
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
892
- };
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
200
  async function deleteUploadRows(id, metaStore, chunkStore) {
904
201
  const existing = await metaStore.lookup(buildMetaKey(id));
905
202
  await metaStore.delete(buildMetaKey(id));
@@ -933,30 +230,9 @@ async function registerUploadRows(record, metaStore, chunkStore, ttlMs, overwrit
933
230
  byteLength: buffer.byteLength
934
231
  }));
935
232
  }
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
233
  async function withPendingUploadLock(options, run) {
955
234
  return await withMSTeamsSqliteMutationLock(options, PENDING_UPLOAD_LOCK_FILENAME, run);
956
235
  }
957
- async function ensureLegacyImported(options, metaStore, chunkStore, ttlMs) {
958
- await withPendingUploadLock(options, () => importLegacyStore(options, metaStore, chunkStore, ttlMs));
959
- }
960
236
  async function readUploadRows(id, metaStore, chunkStore) {
961
237
  const meta = await metaStore.lookup(buildMetaKey(id));
962
238
  if (!meta) return;
@@ -1000,7 +276,6 @@ async function storePendingUploadFs(upload, options) {
1000
276
  const metaStore = createMetaStore(options);
1001
277
  const chunkStore = createChunkStore(options);
1002
278
  await withPendingUploadLock(options, async () => {
1003
- await importLegacyStore(options, metaStore, chunkStore, ttlMs);
1004
279
  await registerUploadRows({
1005
280
  id: upload.id,
1006
281
  bufferBase64: upload.buffer.toString("base64"),
@@ -1019,10 +294,7 @@ async function storePendingUploadFs(upload, options) {
1019
294
  async function getPendingUploadFs(id, options) {
1020
295
  if (!id) return;
1021
296
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
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);
297
+ const upload = await readUploadRows(id, createMetaStore(options), createChunkStore(options));
1026
298
  if (!upload) return;
1027
299
  if (Date.now() - upload.createdAt > ttlMs) {
1028
300
  await removePendingUploadFs(id, options);
@@ -1036,11 +308,9 @@ async function getPendingUploadFs(id, options) {
1036
308
  */
1037
309
  async function removePendingUploadFs(id, options) {
1038
310
  if (!id) return;
1039
- const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
1040
311
  const metaStore = createMetaStore(options);
1041
312
  const chunkStore = createChunkStore(options);
1042
313
  await withPendingUploadLock(options, async () => {
1043
- await importLegacyStore(options, metaStore, chunkStore, ttlMs);
1044
314
  await deleteUploadRows(id, metaStore, chunkStore);
1045
315
  });
1046
316
  }
@@ -1051,9 +321,7 @@ async function removePendingUploadFs(id, options) {
1051
321
  async function setPendingUploadActivityIdFs(id, activityId, options) {
1052
322
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
1053
323
  const metaStore = createMetaStore(options);
1054
- const chunkStore = createChunkStore(options);
1055
324
  await withPendingUploadLock(options, async () => {
1056
- await importLegacyStore(options, metaStore, chunkStore, ttlMs);
1057
325
  const record = await metaStore.lookup(buildMetaKey(id));
1058
326
  if (!record || Date.now() - record.createdAt > ttlMs) return;
1059
327
  await metaStore.register(buildMetaKey(id), toPluginJsonValue({
@@ -2706,4 +1974,4 @@ async function probeMSTeams(cfg) {
2706
1974
  }
2707
1975
  }
2708
1976
  //#endregion
2709
- 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 };
1977
+ export { buildFileInfoCard as _, sendMessageMSTeams as a, 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, uploadToConsentUrl as y };