@openclaw/msteams 2026.6.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.
- package/dist/api.js +2 -2
- package/dist/{channel-Dhw8AvTl.js → channel--oT3fyD5.js} +5 -5
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-t52N99bX.js → channel.runtime-KNuY2Oaw.js} +5 -4
- package/dist/directory-contract-api.js +35 -0
- package/dist/doctor-contract-api.js +254 -44
- package/dist/{oauth-ei63gdyS.js → oauth-CDUB5xPY.js} +1 -1
- package/dist/polls-C1VgSvKE.js +548 -0
- package/dist/{probe-DYb-0Hmx.js → probe-C-rWMb-m.js} +10 -742
- package/dist/{errors-Dpn8B05h.js → resolve-allowlist-BzIUWmMm.js} +334 -165
- package/dist/runtime-BS5AZrKK.js +8 -0
- package/dist/runtime-api.js +19 -1
- package/dist/setup-plugin-api.js +2 -2
- package/dist/{setup-surface-DGTz8Mlf.js → setup-surface-Dik4VU7f.js} +180 -220
- package/dist/{src-DZ76sgTp.js → src-CLsoqMj1.js} +9 -124
- package/dist/sso-token-store-BYZaKr82.js +70 -0
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +3 -0
- package/package.json +4 -4
- package/dist/oauth.token-BKzEFepQ.js +0 -132
- package/dist/runtime-api-BlvMnDKz.js +0 -26
|
@@ -1,657 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel
|
|
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 {
|
|
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
|
|
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
|
|
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 {
|
|
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 };
|