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