@openclaw/msteams 2026.9.6 → 2026.9.7
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/assets/icon.png +0 -0
- package/dist/.setup/{adapter.runtime-CQWZeqB5.cjs → adapter.runtime-BiWeMKXE.cjs} +21 -29
- package/dist/.setup/{approval-card-CPNH_q3P.cjs → approval-card-DWPDItvq.cjs} +6 -3
- package/dist/.setup/{approval-handler.runtime-CUnanUdR.cjs → approval-handler.runtime-D1s7UR3U.cjs} +4 -4
- package/dist/.setup/bot-framework-service-url-BUhTe8F3.cjs +55 -0
- package/dist/.setup/{channel-BDp16XRR.cjs → channel-ByzS7QJ0.cjs} +286 -180
- package/dist/.setup/{channel.runtime-Cv5F8JCa.cjs → channel.runtime-Baz8XDFs.cjs} +43 -142
- package/dist/.setup/{channel.setup-BVDfgdCc.cjs → channel.setup-DL8lcWT_.cjs} +6 -6
- package/dist/.setup/{config-schema-D22Lr1Qk.cjs → config-schema-0KoBzb0t.cjs} +24 -38
- package/dist/.setup/{messenger-8RA1FUJa.cjs → conversation-store-state-B7JuyZTm.cjs} +693 -287
- package/dist/.setup/{src-BfOCwlHp.cjs → monitor-BJ7IyH4y.cjs} +422 -592
- package/dist/.setup/{oauth-C6m7dvHj.cjs → oauth-DQjOnJ4l.cjs} +1 -1
- package/dist/.setup/{reaction-types-C7RA7Wxt.cjs → reaction-types-CQ4oSUe7.cjs} +2 -6
- package/dist/.setup/{resolve-allowlist-CHzaaWiC.cjs → resolve-allowlist-Pl_NQFYH.cjs} +55 -312
- package/dist/.setup/{rolldown-runtime-VH7oDXx4.cjs → rolldown-runtime-BloeesfJ.cjs} +22 -0
- package/dist/.setup/{send-CYN_N7O1.cjs → send-VUM7Sryb.cjs} +43 -96
- package/dist/.setup/{setup-plugin-api-DpiaA8z1.cjs → setup-plugin-api-Dyf8kF-l.cjs} +1 -1
- package/dist/.setup/{thread-session-CXgHK-yi.cjs → thread-session--rw3BTal.cjs} +2 -5
- package/dist/api.cjs +2 -2
- package/dist/channel-config-api.cjs +1 -1
- package/dist/channel-plugin-api.cjs +1 -1
- package/dist/configured-state.cjs +0 -1
- package/dist/directory-contract-api.cjs +1 -1
- package/dist/doctor-contract-api.cjs +201 -371
- package/dist/qa-runner-api.cjs +1 -1
- package/dist/runtime-api.cjs +0 -6
- package/dist/setup-entry.cjs +1 -1
- package/dist/setup-plugin-api.cjs +1 -1
- package/openclaw.plugin.json +46 -5
- package/package.json +9 -9
- package/dist/.setup/polls-BJ4-4uZm.cjs +0 -668
- package/dist/.setup/secret-input-C65t6kjM.cjs +0 -1
- package/dist/.setup/sso-token-store-YYLHXgFH.cjs +0 -111
|
@@ -1,44 +1,40 @@
|
|
|
1
|
-
const require_rolldown_runtime = require("./rolldown-runtime-
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-BloeesfJ.cjs");
|
|
2
2
|
const require_runtime = require("./runtime-gZTAuFux.cjs");
|
|
3
3
|
require("../runtime-api.cjs");
|
|
4
|
-
const require_resolve_allowlist = require("./resolve-allowlist-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
4
|
+
const require_resolve_allowlist = require("./resolve-allowlist-Pl_NQFYH.cjs");
|
|
5
|
+
const require_bot_framework_service_url = require("./bot-framework-service-url-BUhTe8F3.cjs");
|
|
6
|
+
const require_channel_setup = require("./channel.setup-DL8lcWT_.cjs");
|
|
7
7
|
let openclaw_plugin_sdk_lazy_runtime = require("openclaw/plugin-sdk/lazy-runtime");
|
|
8
8
|
let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
|
|
9
9
|
let openclaw_plugin_sdk_fetch_runtime = require("openclaw/plugin-sdk/fetch-runtime");
|
|
10
10
|
let openclaw_plugin_sdk_provider_http = require("openclaw/plugin-sdk/provider-http");
|
|
11
|
+
let openclaw_plugin_sdk_file_lock = require("openclaw/plugin-sdk/file-lock");
|
|
11
12
|
let openclaw_plugin_sdk_reply_payload = require("openclaw/plugin-sdk/reply-payload");
|
|
12
13
|
let openclaw_plugin_sdk_ssrf_runtime = require("openclaw/plugin-sdk/ssrf-runtime");
|
|
13
14
|
let openclaw_plugin_sdk_text_chunking = require("openclaw/plugin-sdk/text-chunking");
|
|
14
15
|
let node_dns_promises = require("node:dns/promises");
|
|
15
16
|
let openclaw_plugin_sdk_ssrf_policy = require("openclaw/plugin-sdk/ssrf-policy");
|
|
17
|
+
let openclaw_plugin_sdk_number_runtime = require("openclaw/plugin-sdk/number-runtime");
|
|
16
18
|
let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text-utility-runtime");
|
|
17
19
|
let openclaw_plugin_sdk_error_runtime = require("openclaw/plugin-sdk/error-runtime");
|
|
18
20
|
let openclaw_plugin_sdk_retry_runtime = require("openclaw/plugin-sdk/retry-runtime");
|
|
19
|
-
let node_crypto = require("node:crypto");
|
|
20
|
-
node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
|
|
21
21
|
let node_path = require("node:path");
|
|
22
22
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
23
23
|
let openclaw_plugin_sdk_runtime_env = require("openclaw/plugin-sdk/runtime-env");
|
|
24
24
|
let openclaw_plugin_sdk_markdown_table_runtime = require("openclaw/plugin-sdk/markdown-table-runtime");
|
|
25
25
|
let openclaw_plugin_sdk_blob_runtime = require("openclaw/plugin-sdk/blob-runtime");
|
|
26
|
+
let node_crypto = require("node:crypto");
|
|
27
|
+
node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
|
|
28
|
+
let openclaw_plugin_sdk_keyed_async_queue = require("openclaw/plugin-sdk/keyed-async-queue");
|
|
26
29
|
let openclaw_plugin_sdk_global_singleton = require("openclaw/plugin-sdk/global-singleton");
|
|
27
30
|
let openclaw_plugin_sdk_expect_runtime = require("openclaw/plugin-sdk/expect-runtime");
|
|
28
31
|
let openclaw_plugin_sdk_reply_chunking = require("openclaw/plugin-sdk/reply-chunking");
|
|
29
32
|
let openclaw_plugin_sdk_web_media = require("openclaw/plugin-sdk/web-media");
|
|
33
|
+
let openclaw_plugin_sdk_dedupe_runtime = require("openclaw/plugin-sdk/dedupe-runtime");
|
|
34
|
+
let openclaw_plugin_sdk_plugin_state_runtime = require("openclaw/plugin-sdk/plugin-state-runtime");
|
|
30
35
|
let openclaw_plugin_sdk_media_runtime = require("openclaw/plugin-sdk/media-runtime");
|
|
31
36
|
//#region extensions/msteams/src/file-consent.ts
|
|
32
37
|
/**
|
|
33
|
-
* FileConsentCard utilities for MS Teams large file uploads (>4MB) in personal chats.
|
|
34
|
-
*
|
|
35
|
-
* Teams requires user consent before the bot can upload large files. This module provides
|
|
36
|
-
* utilities for:
|
|
37
|
-
* - Building FileConsentCard attachments (to request upload permission)
|
|
38
|
-
* - Building FileInfoCard attachments (to confirm upload completion)
|
|
39
|
-
* - Parsing fileConsent/invoke activities
|
|
40
|
-
*/
|
|
41
|
-
/**
|
|
42
38
|
* Allowlist of domains that are valid targets for file consent uploads.
|
|
43
39
|
* These are the Microsoft/SharePoint domains that Teams legitimately provides
|
|
44
40
|
* as upload destinations in the FileConsentCard flow.
|
|
@@ -58,11 +54,6 @@ const CONSENT_UPLOAD_HOST_ALLOWLIST = [
|
|
|
58
54
|
"graph.microsoft.cn"
|
|
59
55
|
];
|
|
60
56
|
/**
|
|
61
|
-
* Returns true if the given IPv4 or IPv6 address is private, internal, or
|
|
62
|
-
* special-use and must never be reached via consent uploads.
|
|
63
|
-
*/
|
|
64
|
-
const isPrivateOrReservedIP = openclaw_plugin_sdk_ssrf_policy.isPrivateIpAddress;
|
|
65
|
-
/**
|
|
66
57
|
* Validate that a consent upload URL is safe to PUT to.
|
|
67
58
|
* Checks:
|
|
68
59
|
* 1. Protocol is HTTPS
|
|
@@ -89,7 +80,7 @@ async function validateConsentUploadUrl(url, opts) {
|
|
|
89
80
|
} catch {
|
|
90
81
|
throw new Error(`Failed to resolve consent upload URL hostname "${hostname}"`);
|
|
91
82
|
}
|
|
92
|
-
for (const entry of resolved) if (
|
|
83
|
+
for (const entry of resolved) if ((0, openclaw_plugin_sdk_ssrf_policy.isPrivateIpAddress)(entry.address)) throw new Error(`Consent upload URL resolves to a private/reserved IP (${entry.address})`);
|
|
93
84
|
}
|
|
94
85
|
/**
|
|
95
86
|
* Build a FileConsentCard attachment for requesting upload permission.
|
|
@@ -164,6 +155,47 @@ async function uploadToConsentUrl(params) {
|
|
|
164
155
|
if (!res.ok) throw new Error(`File upload to consent URL failed: ${res.status} ${res.statusText}`);
|
|
165
156
|
}
|
|
166
157
|
//#endregion
|
|
158
|
+
//#region extensions/msteams/src/sqlite-state.ts
|
|
159
|
+
function resolveStateDirOverride(options) {
|
|
160
|
+
if (!options) return;
|
|
161
|
+
if (options.stateDir) return options.stateDir;
|
|
162
|
+
if (options.storePath) return node_path.default.dirname(options.storePath);
|
|
163
|
+
if (options.homedir) return require_runtime.getMSTeamsRuntime().state.resolveStateDir(options.env ?? process.env, options.homedir);
|
|
164
|
+
return options.env?.OPENCLAW_STATE_DIR?.trim() || void 0;
|
|
165
|
+
}
|
|
166
|
+
function resolveMSTeamsSqliteStateEnv(options) {
|
|
167
|
+
const stateDir = resolveStateDirOverride(options);
|
|
168
|
+
if (!stateDir) return options?.env;
|
|
169
|
+
return {
|
|
170
|
+
...options?.env ?? process.env,
|
|
171
|
+
OPENCLAW_STATE_DIR: stateDir
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function toPluginJsonValue(value) {
|
|
175
|
+
const serialized = JSON.stringify(value);
|
|
176
|
+
return JSON.parse(serialized);
|
|
177
|
+
}
|
|
178
|
+
function resolveMSTeamsSqliteStateDir(options) {
|
|
179
|
+
return resolveStateDirOverride(options) ?? require_runtime.getMSTeamsRuntime().state.resolveStateDir(options?.env ?? process.env, options?.homedir);
|
|
180
|
+
}
|
|
181
|
+
const sqliteMutationLocks = new openclaw_plugin_sdk_keyed_async_queue.KeyedAsyncQueue();
|
|
182
|
+
const MSTEAMS_MUTATION_LOCK_OPTIONS = {
|
|
183
|
+
retries: {
|
|
184
|
+
retries: 10,
|
|
185
|
+
factor: 2,
|
|
186
|
+
minTimeout: 100,
|
|
187
|
+
maxTimeout: 1e4,
|
|
188
|
+
randomize: true
|
|
189
|
+
},
|
|
190
|
+
stale: 3e4
|
|
191
|
+
};
|
|
192
|
+
async function withMSTeamsSqliteMutationLock(options, mutationKey, fn) {
|
|
193
|
+
const scopedMutationKey = node_path.default.join(resolveMSTeamsSqliteStateDir(options), mutationKey);
|
|
194
|
+
return await sqliteMutationLocks.enqueue(scopedMutationKey, async () => {
|
|
195
|
+
return await (0, openclaw_plugin_sdk_file_lock.withFileLock)(scopedMutationKey, MSTEAMS_MUTATION_LOCK_OPTIONS, fn);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
167
199
|
//#region extensions/msteams/src/pending-uploads-fs.ts
|
|
168
200
|
/** TTL for persisted pending uploads (matches in-memory store). */
|
|
169
201
|
const PENDING_UPLOAD_TTL_MS$1 = 3e5;
|
|
@@ -180,14 +212,14 @@ function createMetaStore(options) {
|
|
|
180
212
|
return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
|
|
181
213
|
namespace: PENDING_UPLOAD_META_NAMESPACE,
|
|
182
214
|
maxEntries: PENDING_UPLOAD_META_MAX_ENTRIES,
|
|
183
|
-
env:
|
|
215
|
+
env: resolveMSTeamsSqliteStateEnv(options)
|
|
184
216
|
});
|
|
185
217
|
}
|
|
186
218
|
function createChunkStore(options) {
|
|
187
219
|
return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
|
|
188
220
|
namespace: PENDING_UPLOAD_CHUNKS_NAMESPACE,
|
|
189
221
|
maxEntries: MAX_PENDING_UPLOAD_CHUNK_ROWS,
|
|
190
|
-
env:
|
|
222
|
+
env: resolveMSTeamsSqliteStateEnv(options)
|
|
191
223
|
});
|
|
192
224
|
}
|
|
193
225
|
function buildUploadKey(id) {
|
|
@@ -217,22 +249,21 @@ async function deleteUploadRows(id, metaStore, chunkStore) {
|
|
|
217
249
|
const chunkCount = existing.chunkCount;
|
|
218
250
|
for (let index = 0; index < chunkCount; index += 1) await chunkStore.delete(buildChunkKey(id, index));
|
|
219
251
|
}
|
|
220
|
-
async function registerUploadRows(record, metaStore, chunkStore, ttlMs
|
|
221
|
-
const buffer = Buffer.from(record.
|
|
252
|
+
async function registerUploadRows(record, metaStore, chunkStore, ttlMs) {
|
|
253
|
+
const buffer = Buffer.from(record.buffer);
|
|
222
254
|
const chunkCount = Math.max(1, Math.ceil(buffer.byteLength / RAW_CHUNK_BYTES));
|
|
223
255
|
if (chunkCount > MAX_CHUNKS_PER_UPLOAD) throw new Error(`Microsoft Teams pending upload ${record.id} exceeds SQLite chunk limit (${chunkCount}/${MAX_CHUNKS_PER_UPLOAD})`);
|
|
224
|
-
|
|
225
|
-
else if (await metaStore.lookup(buildMetaKey(record.id))) return;
|
|
256
|
+
await deleteUploadRows(record.id, metaStore, chunkStore);
|
|
226
257
|
await pruneUploadStore(metaStore, chunkStore, ttlMs, chunkCount);
|
|
227
258
|
for (let index = 0; index < chunkCount; index += 1) {
|
|
228
259
|
const chunk = buffer.subarray(index * RAW_CHUNK_BYTES, (index + 1) * RAW_CHUNK_BYTES);
|
|
229
|
-
await chunkStore.register(buildChunkKey(record.id, index),
|
|
260
|
+
await chunkStore.register(buildChunkKey(record.id, index), toPluginJsonValue({
|
|
230
261
|
id: record.id,
|
|
231
262
|
index,
|
|
232
263
|
dataBase64: chunk.toString("base64")
|
|
233
264
|
}));
|
|
234
265
|
}
|
|
235
|
-
await metaStore.register(buildMetaKey(record.id),
|
|
266
|
+
await metaStore.register(buildMetaKey(record.id), toPluginJsonValue({
|
|
236
267
|
id: record.id,
|
|
237
268
|
filename: record.filename,
|
|
238
269
|
contentType: record.contentType,
|
|
@@ -244,7 +275,7 @@ async function registerUploadRows(record, metaStore, chunkStore, ttlMs, overwrit
|
|
|
244
275
|
}));
|
|
245
276
|
}
|
|
246
277
|
async function withPendingUploadLock(options, run) {
|
|
247
|
-
return await
|
|
278
|
+
return await withMSTeamsSqliteMutationLock(options, PENDING_UPLOAD_MUTATION_KEY, run);
|
|
248
279
|
}
|
|
249
280
|
async function readUploadRows(id, metaStore, chunkStore) {
|
|
250
281
|
const meta = await metaStore.lookup(buildMetaKey(id));
|
|
@@ -295,19 +326,16 @@ async function storePendingUploadFs(upload, options) {
|
|
|
295
326
|
await withPendingUploadLock(options, async () => {
|
|
296
327
|
await registerUploadRows({
|
|
297
328
|
id: upload.id,
|
|
298
|
-
|
|
329
|
+
buffer: upload.buffer,
|
|
299
330
|
filename: upload.filename,
|
|
300
331
|
contentType: upload.contentType,
|
|
301
332
|
conversationId: upload.conversationId,
|
|
302
333
|
consentCardActivityId: upload.consentCardActivityId,
|
|
303
334
|
createdAt: Date.now()
|
|
304
|
-
}, metaStore, chunkStore, ttlMs
|
|
335
|
+
}, metaStore, chunkStore, ttlMs);
|
|
305
336
|
await pruneUploadStore(metaStore, chunkStore, ttlMs);
|
|
306
337
|
});
|
|
307
338
|
}
|
|
308
|
-
/**
|
|
309
|
-
* Retrieve a persisted pending upload. Expired entries are treated as absent.
|
|
310
|
-
*/
|
|
311
339
|
async function getPendingUploadFs(id, options) {
|
|
312
340
|
if (!id) return;
|
|
313
341
|
const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
|
|
@@ -319,10 +347,6 @@ async function getPendingUploadFs(id, options) {
|
|
|
319
347
|
}
|
|
320
348
|
return upload;
|
|
321
349
|
}
|
|
322
|
-
/**
|
|
323
|
-
* Remove a persisted pending upload (after successful upload or decline).
|
|
324
|
-
* No-op if the entry is already gone.
|
|
325
|
-
*/
|
|
326
350
|
async function removePendingUploadFs(id, options) {
|
|
327
351
|
if (!id) return;
|
|
328
352
|
const metaStore = createMetaStore(options);
|
|
@@ -331,17 +355,13 @@ async function removePendingUploadFs(id, options) {
|
|
|
331
355
|
await deleteUploadRows(id, metaStore, chunkStore);
|
|
332
356
|
});
|
|
333
357
|
}
|
|
334
|
-
/**
|
|
335
|
-
* Set the consent card activity ID on a persisted entry. Called after the
|
|
336
|
-
* FileConsentCard activity is sent and we know its message id.
|
|
337
|
-
*/
|
|
338
358
|
async function setPendingUploadActivityIdFs(id, activityId, options) {
|
|
339
359
|
const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
|
|
340
360
|
const metaStore = createMetaStore(options);
|
|
341
361
|
await withPendingUploadLock(options, async () => {
|
|
342
362
|
const record = await metaStore.lookup(buildMetaKey(id));
|
|
343
363
|
if (!record || Date.now() - record.createdAt > ttlMs) return;
|
|
344
|
-
await metaStore.register(buildMetaKey(id),
|
|
364
|
+
await metaStore.register(buildMetaKey(id), toPluginJsonValue({
|
|
345
365
|
...record,
|
|
346
366
|
consentCardActivityId: activityId
|
|
347
367
|
}));
|
|
@@ -365,7 +385,6 @@ const { pendingUploads, pendingUploadTimers } = (0, openclaw_plugin_sdk_global_s
|
|
|
365
385
|
state.pendingUploadTimers.clear();
|
|
366
386
|
state.pendingUploads.clear();
|
|
367
387
|
});
|
|
368
|
-
/** TTL for pending uploads: 5 minutes */
|
|
369
388
|
const PENDING_UPLOAD_TTL_MS = 3e5;
|
|
370
389
|
/**
|
|
371
390
|
* Store a file pending user consent.
|
|
@@ -386,21 +405,12 @@ function storePendingUpload(upload) {
|
|
|
386
405
|
pendingUploadTimers.set(id, timer);
|
|
387
406
|
return id;
|
|
388
407
|
}
|
|
389
|
-
/**
|
|
390
|
-
* Retrieve a pending upload by ID.
|
|
391
|
-
* Returns undefined if not found or expired.
|
|
392
|
-
*/
|
|
393
408
|
function getPendingUpload(id) {
|
|
394
409
|
if (!id) return;
|
|
395
410
|
const entry = pendingUploads.get(id);
|
|
396
411
|
if (!entry) return;
|
|
397
412
|
if (Date.now() - entry.createdAt > PENDING_UPLOAD_TTL_MS) {
|
|
398
|
-
|
|
399
|
-
const timer = pendingUploadTimers.get(id);
|
|
400
|
-
if (timer !== void 0) {
|
|
401
|
-
clearTimeout(timer);
|
|
402
|
-
pendingUploadTimers.delete(id);
|
|
403
|
-
}
|
|
413
|
+
removePendingUpload(id);
|
|
404
414
|
return;
|
|
405
415
|
}
|
|
406
416
|
return entry;
|
|
@@ -419,10 +429,6 @@ function removePendingUpload(id) {
|
|
|
419
429
|
}
|
|
420
430
|
}
|
|
421
431
|
}
|
|
422
|
-
/**
|
|
423
|
-
* Set the consent card activity ID on an existing pending upload.
|
|
424
|
-
* Called after the FileConsentCard is sent and we know its activity ID.
|
|
425
|
-
*/
|
|
426
432
|
function setPendingUploadActivityId(uploadId, activityId) {
|
|
427
433
|
const entry = pendingUploads.get(uploadId);
|
|
428
434
|
if (entry) entry.consentCardActivityId = activityId;
|
|
@@ -769,14 +775,15 @@ function formatMSTeamsMarkdown(markdown, tableMode) {
|
|
|
769
775
|
const index = escapedMarkdown.push(escaped) - 1;
|
|
770
776
|
return `${tokenPrefix}e${index}${TOKEN_END}`;
|
|
771
777
|
});
|
|
772
|
-
const
|
|
778
|
+
const parsed = (0, openclaw_plugin_sdk_text_chunking.markdownToIR)(protectedMarkdown, {
|
|
773
779
|
autolink: false,
|
|
774
780
|
enableSpoilers: true,
|
|
775
781
|
enableTaskLists: true,
|
|
776
782
|
headingStyle: "rich",
|
|
777
783
|
linkify: false,
|
|
778
784
|
blockquotePrefix: ""
|
|
779
|
-
})
|
|
785
|
+
});
|
|
786
|
+
const protectedValues = [
|
|
780
787
|
{
|
|
781
788
|
prefix: `${tokenPrefix}e`,
|
|
782
789
|
values: escapedMarkdown
|
|
@@ -797,8 +804,9 @@ function formatMSTeamsMarkdown(markdown, tableMode) {
|
|
|
797
804
|
prefix: `${tokenPrefix}h`,
|
|
798
805
|
values: entities
|
|
799
806
|
}
|
|
800
|
-
]
|
|
801
|
-
|
|
807
|
+
];
|
|
808
|
+
const ir = prefixMSTeamsBlockquotes(protectMSTeamsCode(parsed, tokenPrefix, codeRegions, protectedValues));
|
|
809
|
+
const rendered = (0, openclaw_plugin_sdk_text_chunking.renderMarkdownWithMarkers)(ir, {
|
|
802
810
|
styleMarkers: MSTEAMS_MARKERS,
|
|
803
811
|
escapeText: (text) => text,
|
|
804
812
|
buildLink: (link) => ({
|
|
@@ -807,12 +815,8 @@ function formatMSTeamsMarkdown(markdown, tableMode) {
|
|
|
807
815
|
open: "[",
|
|
808
816
|
close: `](${serializeMarkdownDestination(link.href)})`
|
|
809
817
|
})
|
|
810
|
-
}, MSTEAMS_FORMAT_CAPABILITIES)
|
|
811
|
-
|
|
812
|
-
restored = restoreTokens(restored, `${tokenPrefix}t`, rawTables);
|
|
813
|
-
restored = restoreTokens(restored, `${tokenPrefix}m`, mentions);
|
|
814
|
-
restored = restoreTokens(restored, `${tokenPrefix}i`, images);
|
|
815
|
-
return restoreTokens(restored, `${tokenPrefix}h`, entities);
|
|
818
|
+
}, MSTEAMS_FORMAT_CAPABILITIES);
|
|
819
|
+
return protectedValues.reduce((text, { prefix, values }) => restoreTokens(text, prefix, values), restoreTokens(rendered, `${tokenPrefix}c`, codeRegions));
|
|
816
820
|
}
|
|
817
821
|
//#endregion
|
|
818
822
|
//#region extensions/msteams/src/graph-chat.ts
|
|
@@ -821,43 +825,18 @@ function buildTeamsFileInfoCard(file) {
|
|
|
821
825
|
const uniqueId = rawETag.replace(/^["']|["']$/g, "").replace(/[{}]/g, "").split(",")[0] ?? rawETag;
|
|
822
826
|
const lastDot = file.name.lastIndexOf(".");
|
|
823
827
|
const fileType = lastDot >= 0 ? (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(file.name.slice(lastDot + 1)) : "";
|
|
824
|
-
return {
|
|
825
|
-
|
|
828
|
+
return buildFileInfoCard({
|
|
829
|
+
filename: file.name,
|
|
826
830
|
contentUrl: file.webDavUrl,
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
fileType
|
|
831
|
-
}
|
|
832
|
-
};
|
|
831
|
+
uniqueId,
|
|
832
|
+
fileType
|
|
833
|
+
});
|
|
833
834
|
}
|
|
834
835
|
//#endregion
|
|
835
836
|
//#region extensions/msteams/src/graph-upload.ts
|
|
836
|
-
/**
|
|
837
|
-
* SharePoint upload utilities for MS Teams file sending.
|
|
838
|
-
*
|
|
839
|
-
* For group chats and channels, files are uploaded to SharePoint and shared via a link.
|
|
840
|
-
* This module provides utilities for:
|
|
841
|
-
* - Uploading files to SharePoint (group/channel scope)
|
|
842
|
-
* - Creating sharing links (organization-wide or per-user)
|
|
843
|
-
* - Getting chat members for per-user sharing
|
|
844
|
-
*/
|
|
845
837
|
const GRAPH_ROOT = "https://graph.microsoft.com/v1.0";
|
|
846
838
|
const GRAPH_BETA = "https://graph.microsoft.com/beta";
|
|
847
839
|
const GRAPH_SCOPE = "https://graph.microsoft.com";
|
|
848
|
-
async function requestSharePoint(url, init, params) {
|
|
849
|
-
const { response, release } = await (0, openclaw_plugin_sdk_ssrf_runtime.fetchWithSsrFGuard)({
|
|
850
|
-
url,
|
|
851
|
-
init,
|
|
852
|
-
fetchImpl: params.fetchFn,
|
|
853
|
-
mode: "trusted_env_proxy",
|
|
854
|
-
beforeRequest: () => require_resolve_allowlist.assertMSTeamsSendHandoff(params),
|
|
855
|
-
maxRedirects: 20,
|
|
856
|
-
allowCrossOriginUnsafeRedirectReplay: true,
|
|
857
|
-
auditContext: "msteams.graph-upload"
|
|
858
|
-
});
|
|
859
|
-
return (0, openclaw_plugin_sdk_fetch_runtime.responseWithRelease)(response, release);
|
|
860
|
-
}
|
|
861
840
|
function requireMSTeamsSharePointSiteId(siteId) {
|
|
862
841
|
const normalized = siteId?.trim();
|
|
863
842
|
if (!normalized) throw new Error("channels.msteams.sharePointSiteId is required to send files to group chats or channels");
|
|
@@ -866,43 +845,55 @@ function requireMSTeamsSharePointSiteId(siteId) {
|
|
|
866
845
|
const SHAREPOINT_REQUEST_TIMEOUT_LABEL = "MS Teams SharePoint request";
|
|
867
846
|
const SHAREPOINT_UPLOAD_TIMEOUT_LABEL = "MS Teams SharePoint upload";
|
|
868
847
|
const GRAPH_TOKEN_TIMEOUT_LABEL = "MS Teams Graph token acquisition";
|
|
869
|
-
async function
|
|
870
|
-
return await require_resolve_allowlist.
|
|
871
|
-
label:
|
|
872
|
-
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
/**
|
|
876
|
-
* Upload a file to a SharePoint site.
|
|
877
|
-
* This is used for group chats and channels where /me/drive doesn't work for bots.
|
|
878
|
-
*
|
|
879
|
-
* @param params.siteId - SharePoint site ID (e.g., "contoso.sharepoint.com,guid1,guid2")
|
|
880
|
-
*/
|
|
881
|
-
async function uploadToSharePoint(params) {
|
|
882
|
-
const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`;
|
|
883
|
-
const uploadUrl = `${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content?@microsoft.graph.conflictBehavior=rename`;
|
|
884
|
-
const timeoutMs = require_resolve_allowlist.resolveMSTeamsSharePointUploadTimeoutMs(params.buffer.length);
|
|
885
|
-
const data = await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
|
|
886
|
-
label: SHAREPOINT_UPLOAD_TIMEOUT_LABEL,
|
|
887
|
-
timeoutMs,
|
|
848
|
+
async function requestSharePointJson(params, request) {
|
|
849
|
+
return await require_resolve_allowlist.withMSTeamsAbortableRequestTimeout({
|
|
850
|
+
label: request.timeoutMs !== void 0 ? SHAREPOINT_UPLOAD_TIMEOUT_LABEL : SHAREPOINT_REQUEST_TIMEOUT_LABEL,
|
|
851
|
+
timeoutMs: request.timeoutMs,
|
|
888
852
|
work: async (signal) => {
|
|
889
853
|
require_resolve_allowlist.assertMSTeamsSendHandoff(params);
|
|
890
|
-
const token = await
|
|
854
|
+
const token = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
855
|
+
label: GRAPH_TOKEN_TIMEOUT_LABEL,
|
|
856
|
+
work: () => params.tokenProvider.getAccessToken(GRAPH_SCOPE)
|
|
857
|
+
});
|
|
891
858
|
require_resolve_allowlist.assertMSTeamsSendHandoff(params);
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
859
|
+
const init = request.init?.();
|
|
860
|
+
const { response, release } = await (0, openclaw_plugin_sdk_ssrf_runtime.fetchWithSsrFGuard)({
|
|
861
|
+
url: request.url,
|
|
862
|
+
init: {
|
|
863
|
+
...init,
|
|
864
|
+
headers: {
|
|
865
|
+
"User-Agent": require_resolve_allowlist.buildUserAgent(),
|
|
866
|
+
Authorization: `Bearer ${token}`,
|
|
867
|
+
...init?.headers
|
|
868
|
+
},
|
|
869
|
+
signal
|
|
898
870
|
},
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
871
|
+
fetchImpl: params.fetchFn,
|
|
872
|
+
mode: "trusted_env_proxy",
|
|
873
|
+
beforeRequest: () => require_resolve_allowlist.assertMSTeamsSendHandoff(params),
|
|
874
|
+
maxRedirects: 20,
|
|
875
|
+
allowCrossOriginUnsafeRedirectReplay: true,
|
|
876
|
+
auditContext: "msteams.graph-upload"
|
|
877
|
+
});
|
|
878
|
+
const res = (0, openclaw_plugin_sdk_fetch_runtime.responseWithRelease)(response, release);
|
|
879
|
+
if (!res.ok) throw await (0, openclaw_plugin_sdk_provider_http.createProviderHttpError)(res, typeof request.error === "string" ? request.error : request.error(res));
|
|
880
|
+
return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, request.label, { chunkTimeoutMs: request.timeoutMs });
|
|
904
881
|
}
|
|
905
882
|
});
|
|
883
|
+
}
|
|
884
|
+
async function uploadToSharePoint(params) {
|
|
885
|
+
const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`;
|
|
886
|
+
const data = await requestSharePointJson(params, {
|
|
887
|
+
url: `${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content?@microsoft.graph.conflictBehavior=rename`,
|
|
888
|
+
timeoutMs: require_resolve_allowlist.resolveMSTeamsSharePointUploadTimeoutMs(params.buffer.length),
|
|
889
|
+
init: () => ({
|
|
890
|
+
method: "PUT",
|
|
891
|
+
headers: { "Content-Type": params.contentType ?? "application/octet-stream" },
|
|
892
|
+
body: new Blob([(0, openclaw_plugin_sdk_blob_runtime.bufferToBlobPart)(params.buffer)])
|
|
893
|
+
}),
|
|
894
|
+
error: "SharePoint upload failed",
|
|
895
|
+
label: "msteams.graph-upload.uploadSharePointFile"
|
|
896
|
+
});
|
|
906
897
|
if (!data.id || !data.webUrl || !data.name) throw new Error("SharePoint upload response missing required fields");
|
|
907
898
|
return {
|
|
908
899
|
id: data.id,
|
|
@@ -910,30 +901,11 @@ async function uploadToSharePoint(params) {
|
|
|
910
901
|
name: data.name
|
|
911
902
|
};
|
|
912
903
|
}
|
|
913
|
-
/**
|
|
914
|
-
* Get driveItem properties needed for native Teams file card attachments.
|
|
915
|
-
* This fetches the eTag and webDavUrl which are required for "reference" type attachments.
|
|
916
|
-
*
|
|
917
|
-
* @param params.siteId - SharePoint site ID
|
|
918
|
-
* @param params.itemId - The driveItem ID (returned from upload)
|
|
919
|
-
*/
|
|
920
904
|
async function getDriveItemProperties(params) {
|
|
921
|
-
const data = await
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const token = await getGraphAccessToken(params.tokenProvider);
|
|
926
|
-
require_resolve_allowlist.assertMSTeamsSendHandoff(params);
|
|
927
|
-
const res = await requestSharePoint(`${GRAPH_ROOT}/sites/${params.siteId}/drive/items/${params.itemId}?$select=eTag,webDavUrl,name`, {
|
|
928
|
-
headers: {
|
|
929
|
-
"User-Agent": require_resolve_allowlist.buildUserAgent(),
|
|
930
|
-
Authorization: `Bearer ${token}`
|
|
931
|
-
},
|
|
932
|
-
signal
|
|
933
|
-
}, params);
|
|
934
|
-
if (!res.ok) throw await require_resolve_allowlist.createMSTeamsHttpError(res, "Get driveItem properties failed");
|
|
935
|
-
return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.getDriveItemProperties");
|
|
936
|
-
}
|
|
905
|
+
const data = await requestSharePointJson(params, {
|
|
906
|
+
url: `${GRAPH_ROOT}/sites/${params.siteId}/drive/items/${params.itemId}?$select=eTag,webDavUrl,name`,
|
|
907
|
+
error: "Get driveItem properties failed",
|
|
908
|
+
label: "msteams.graph-upload.getDriveItemProperties"
|
|
937
909
|
});
|
|
938
910
|
if (!data.eTag || !data.webDavUrl || !data.name) throw new Error("DriveItem response missing required properties (eTag, webDavUrl, or name)");
|
|
939
911
|
return {
|
|
@@ -942,31 +914,12 @@ async function getDriveItemProperties(params) {
|
|
|
942
914
|
name: data.name
|
|
943
915
|
};
|
|
944
916
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
label: SHAREPOINT_REQUEST_TIMEOUT_LABEL,
|
|
952
|
-
work: async (signal) => {
|
|
953
|
-
require_resolve_allowlist.assertMSTeamsSendHandoff(params);
|
|
954
|
-
const token = await getGraphAccessToken(params.tokenProvider);
|
|
955
|
-
require_resolve_allowlist.assertMSTeamsSendHandoff(params);
|
|
956
|
-
const res = await requestSharePoint(`${GRAPH_ROOT}/chats/${params.chatId}/members`, {
|
|
957
|
-
headers: {
|
|
958
|
-
"User-Agent": require_resolve_allowlist.buildUserAgent(),
|
|
959
|
-
Authorization: `Bearer ${token}`
|
|
960
|
-
},
|
|
961
|
-
signal
|
|
962
|
-
}, params);
|
|
963
|
-
if (!res.ok) {
|
|
964
|
-
const message = res.status === 403 ? "Get chat members failed; verify Graph chat-member permissions and tenant access policies" : "Get chat members failed";
|
|
965
|
-
throw await require_resolve_allowlist.createMSTeamsHttpError(res, message);
|
|
966
|
-
}
|
|
967
|
-
return ((await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.getChatMembers")).value ?? []).map((member) => ({ aadObjectId: member.userId ?? "" })).filter((member) => member.aadObjectId);
|
|
968
|
-
}
|
|
969
|
-
});
|
|
917
|
+
async function getChatMemberIds(params) {
|
|
918
|
+
return ((await requestSharePointJson(params, {
|
|
919
|
+
url: `${GRAPH_ROOT}/chats/${params.chatId}/members`,
|
|
920
|
+
error: (res) => res.status === 403 ? "Get chat members failed; verify Graph chat-member permissions and tenant access policies" : "Get chat members failed",
|
|
921
|
+
label: "msteams.graph-upload.getChatMembers"
|
|
922
|
+
})).value ?? []).flatMap((member) => member.userId ? [member.userId] : []);
|
|
970
923
|
}
|
|
971
924
|
/**
|
|
972
925
|
* Create a sharing link for a SharePoint drive item.
|
|
@@ -981,63 +934,40 @@ async function createSharePointSharingLink(params) {
|
|
|
981
934
|
scope: scope === "users" ? "users" : "organization"
|
|
982
935
|
};
|
|
983
936
|
if (scope === "users" && params.recipientObjectIds?.length) body.recipients = params.recipientObjectIds.map((id) => ({ objectId: id }));
|
|
984
|
-
const data = await
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
"User-Agent": require_resolve_allowlist.buildUserAgent(),
|
|
994
|
-
Authorization: `Bearer ${token}`,
|
|
995
|
-
"Content-Type": "application/json"
|
|
996
|
-
},
|
|
997
|
-
body: JSON.stringify(body),
|
|
998
|
-
signal
|
|
999
|
-
}, params);
|
|
1000
|
-
if (!res.ok) throw await require_resolve_allowlist.createMSTeamsHttpError(res, "Create SharePoint sharing link failed");
|
|
1001
|
-
return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(res, "msteams.graph-upload.createSharePointSharingLink");
|
|
1002
|
-
}
|
|
937
|
+
const data = await requestSharePointJson(params, {
|
|
938
|
+
url: `${apiRoot}/sites/${params.siteId}/drive/items/${params.itemId}/createLink`,
|
|
939
|
+
init: () => ({
|
|
940
|
+
method: "POST",
|
|
941
|
+
headers: { "Content-Type": "application/json" },
|
|
942
|
+
body: JSON.stringify(body)
|
|
943
|
+
}),
|
|
944
|
+
error: "Create SharePoint sharing link failed",
|
|
945
|
+
label: "msteams.graph-upload.createSharePointSharingLink"
|
|
1003
946
|
});
|
|
1004
947
|
if (!data.link?.webUrl) throw new Error("Create SharePoint sharing link response missing webUrl");
|
|
1005
|
-
return
|
|
948
|
+
return data.link.webUrl;
|
|
1006
949
|
}
|
|
1007
950
|
/**
|
|
1008
951
|
* Upload a file to SharePoint and create a sharing link.
|
|
1009
952
|
*
|
|
1010
953
|
* For group chats, this creates a per-user sharing link scoped to chat members.
|
|
1011
954
|
* For channels, this creates an organization-wide sharing link.
|
|
1012
|
-
*
|
|
1013
|
-
* @param params.siteId - SharePoint site ID
|
|
1014
|
-
* @param params.chatId - Optional chat ID for per-user sharing (group chats)
|
|
1015
|
-
* @param params.usePerUserSharing - Whether to use per-user sharing (requires beta API + chat-member read access)
|
|
1016
955
|
*/
|
|
1017
956
|
async function uploadAndShareSharePoint(params) {
|
|
1018
|
-
const uploaded = await uploadToSharePoint(
|
|
1019
|
-
buffer: params.buffer,
|
|
1020
|
-
filename: params.filename,
|
|
1021
|
-
contentType: params.contentType,
|
|
1022
|
-
tokenProvider: params.tokenProvider,
|
|
1023
|
-
siteId: params.siteId,
|
|
1024
|
-
fetchFn: params.fetchFn,
|
|
1025
|
-
assertDirectAdapterHandoff: params.assertDirectAdapterHandoff
|
|
1026
|
-
});
|
|
957
|
+
const uploaded = await uploadToSharePoint(params);
|
|
1027
958
|
let scope = "organization";
|
|
1028
959
|
let recipientObjectIds;
|
|
1029
960
|
if (params.usePerUserSharing && params.chatId) {
|
|
1030
|
-
|
|
961
|
+
recipientObjectIds = await getChatMemberIds({
|
|
1031
962
|
chatId: params.chatId,
|
|
1032
963
|
tokenProvider: params.tokenProvider,
|
|
1033
964
|
fetchFn: params.fetchFn,
|
|
1034
965
|
assertDirectAdapterHandoff: params.assertDirectAdapterHandoff
|
|
1035
966
|
});
|
|
1036
|
-
if (
|
|
967
|
+
if (recipientObjectIds.length === 0) throw new Error("MS Teams chat member lookup returned no recipients");
|
|
1037
968
|
scope = "users";
|
|
1038
|
-
recipientObjectIds = members.map((member) => member.aadObjectId);
|
|
1039
969
|
}
|
|
1040
|
-
const
|
|
970
|
+
const shareUrl = await createSharePointSharingLink({
|
|
1041
971
|
siteId: params.siteId,
|
|
1042
972
|
itemId: uploaded.id,
|
|
1043
973
|
tokenProvider: params.tokenProvider,
|
|
@@ -1049,16 +979,13 @@ async function uploadAndShareSharePoint(params) {
|
|
|
1049
979
|
return {
|
|
1050
980
|
itemId: uploaded.id,
|
|
1051
981
|
webUrl: uploaded.webUrl,
|
|
1052
|
-
shareUrl
|
|
982
|
+
shareUrl,
|
|
1053
983
|
name: uploaded.name
|
|
1054
984
|
};
|
|
1055
985
|
}
|
|
1056
986
|
//#endregion
|
|
1057
987
|
//#region extensions/msteams/src/media-helpers.ts
|
|
1058
988
|
/**
|
|
1059
|
-
* MIME type detection and filename extraction for MSTeams media attachments.
|
|
1060
|
-
*/
|
|
1061
|
-
/**
|
|
1062
989
|
* Detect MIME type from URL extension or data URL.
|
|
1063
990
|
* Uses shared MIME detection for consistency with core handling.
|
|
1064
991
|
*/
|
|
@@ -1097,25 +1024,16 @@ async function extractFilename(url) {
|
|
|
1097
1024
|
return (0, openclaw_plugin_sdk_media_runtime.extractOriginalFilename)(url);
|
|
1098
1025
|
}
|
|
1099
1026
|
}
|
|
1100
|
-
/**
|
|
1101
|
-
* Check if a URL refers to a local file path.
|
|
1102
|
-
*/
|
|
1103
1027
|
function isLocalPath(url) {
|
|
1104
|
-
|
|
1105
|
-
if (url.startsWith("\\") && !url.startsWith("\\\\")) return true;
|
|
1106
|
-
if (/^[a-zA-Z]:[\\/]/.test(url)) return true;
|
|
1107
|
-
if (url.startsWith("\\\\")) return true;
|
|
1108
|
-
return false;
|
|
1028
|
+
return /^file:\/\//iu.test(url) || url.startsWith("/") || url.startsWith("~") || url.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(url);
|
|
1109
1029
|
}
|
|
1110
1030
|
/**
|
|
1111
1031
|
* Extract the message ID from a Bot Framework response.
|
|
1112
1032
|
*/
|
|
1113
1033
|
function extractMessageId(response) {
|
|
1114
|
-
if (!response || typeof response !== "object") return null;
|
|
1115
|
-
if (!("id" in response)) return null;
|
|
1034
|
+
if (!response || typeof response !== "object" || !("id" in response)) return null;
|
|
1116
1035
|
const { id } = response;
|
|
1117
|
-
|
|
1118
|
-
return id;
|
|
1036
|
+
return typeof id === "string" && id ? id : null;
|
|
1119
1037
|
}
|
|
1120
1038
|
//#endregion
|
|
1121
1039
|
//#region extensions/msteams/src/mentions.ts
|
|
@@ -1183,6 +1101,15 @@ function buildMSTeamsMessageActivity(text) {
|
|
|
1183
1101
|
entities: [...parsed.entities, AI_GENERATED_ENTITY]
|
|
1184
1102
|
};
|
|
1185
1103
|
}
|
|
1104
|
+
function buildMSTeamsAdaptiveCardActivity(card) {
|
|
1105
|
+
return {
|
|
1106
|
+
type: "message",
|
|
1107
|
+
attachments: [{
|
|
1108
|
+
contentType: "application/vnd.microsoft.card.adaptive",
|
|
1109
|
+
content: card
|
|
1110
|
+
}]
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1186
1113
|
//#endregion
|
|
1187
1114
|
//#region extensions/msteams/src/revoked-context.ts
|
|
1188
1115
|
async function withRevokedProxyFallback(params) {
|
|
@@ -1195,6 +1122,36 @@ async function withRevokedProxyFallback(params) {
|
|
|
1195
1122
|
}
|
|
1196
1123
|
}
|
|
1197
1124
|
//#endregion
|
|
1125
|
+
//#region extensions/msteams/src/sent-message-cache.ts
|
|
1126
|
+
const sentMessages = (0, openclaw_plugin_sdk_dedupe_runtime.createPersistentDedupeCache)({
|
|
1127
|
+
globalKey: Symbol.for("openclaw.msteamsSentMessages"),
|
|
1128
|
+
ttlMs: 864e5,
|
|
1129
|
+
maxSize: 2e4,
|
|
1130
|
+
persistent: {
|
|
1131
|
+
namespace: "msteams.sent-messages",
|
|
1132
|
+
maxEntries: 1e3,
|
|
1133
|
+
openStore: (options) => require_runtime.getOptionalMSTeamsRuntime()?.state.openKeyedStore(options),
|
|
1134
|
+
logError: (0, openclaw_plugin_sdk_plugin_state_runtime.createPluginStateErrorReporter)(require_runtime.getOptionalMSTeamsRuntime, "msteams", "sent-message-state", "Microsoft Teams persistent sent-message state failed"),
|
|
1135
|
+
readTimestamp: (record) => record.sentAt
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
function makeKey(conversationId, messageId, botId) {
|
|
1139
|
+
return botId ? JSON.stringify([
|
|
1140
|
+
botId,
|
|
1141
|
+
conversationId,
|
|
1142
|
+
messageId
|
|
1143
|
+
]) : `${conversationId}:${messageId}`;
|
|
1144
|
+
}
|
|
1145
|
+
function recordMSTeamsSentMessage(conversationId, messageId, botId) {
|
|
1146
|
+
if (!conversationId || !messageId || messageId === "unknown") return;
|
|
1147
|
+
const sentAt = Date.now();
|
|
1148
|
+
sentMessages.register(makeKey(conversationId, messageId, botId), { sentAt }, { at: sentAt });
|
|
1149
|
+
}
|
|
1150
|
+
async function wasMSTeamsMessageSentWithPersistence(params) {
|
|
1151
|
+
if (!params.conversationId || !params.messageId) return false;
|
|
1152
|
+
return await sentMessages.lookup(makeKey(params.conversationId, params.messageId, params.botId));
|
|
1153
|
+
}
|
|
1154
|
+
//#endregion
|
|
1198
1155
|
//#region extensions/msteams/src/sdk-proactive.ts
|
|
1199
1156
|
const loadMSTeamsApiModule = (0, openclaw_plugin_sdk_lazy_runtime.createLazyRuntimeModule)(() => import("@microsoft/teams.api"));
|
|
1200
1157
|
async function quoteMSTeamsActivity(activity, messageId) {
|
|
@@ -1204,12 +1161,12 @@ async function quoteMSTeamsActivity(activity, messageId) {
|
|
|
1204
1161
|
return MessageActivityInput.from(activity).prependQuote(messageId);
|
|
1205
1162
|
}
|
|
1206
1163
|
function resolveThreadedConversationId(conversationId, threadActivityId) {
|
|
1207
|
-
|
|
1208
|
-
return `${
|
|
1164
|
+
const baseId = conversationId.split(";")[0] ?? conversationId;
|
|
1165
|
+
return threadActivityId ? `${baseId};messageid=${threadActivityId}` : baseId;
|
|
1209
1166
|
}
|
|
1210
1167
|
function normalizeRequiredServiceUrl(ref) {
|
|
1211
1168
|
if (!ref.serviceUrl) throw new Error("Invalid stored reference: missing serviceUrl");
|
|
1212
|
-
return
|
|
1169
|
+
return require_bot_framework_service_url.normalizeBotFrameworkServiceUrl(ref.serviceUrl);
|
|
1213
1170
|
}
|
|
1214
1171
|
function buildSdkConversationReference(source, options) {
|
|
1215
1172
|
const bot = source.agent ?? source.bot ?? void 0;
|
|
@@ -1244,13 +1201,10 @@ function buildSdkConversationReference(source, options) {
|
|
|
1244
1201
|
...source.aadObjectId ? { aadObjectId: source.aadObjectId } : {}
|
|
1245
1202
|
};
|
|
1246
1203
|
}
|
|
1247
|
-
function getStructuralApiClient(app) {
|
|
1248
|
-
return app.api;
|
|
1249
|
-
}
|
|
1250
1204
|
function sameServiceUrl(left, right) {
|
|
1251
1205
|
if (!left) return false;
|
|
1252
1206
|
try {
|
|
1253
|
-
return
|
|
1207
|
+
return require_bot_framework_service_url.normalizeBotFrameworkServiceUrl(left) === right;
|
|
1254
1208
|
} catch {
|
|
1255
1209
|
return false;
|
|
1256
1210
|
}
|
|
@@ -1262,7 +1216,7 @@ function stringifyReferenceFallbackActivity(activity) {
|
|
|
1262
1216
|
return "";
|
|
1263
1217
|
}
|
|
1264
1218
|
async function getApiClientForReference(app, ref) {
|
|
1265
|
-
const api =
|
|
1219
|
+
const api = app.api;
|
|
1266
1220
|
if (sameServiceUrl(api.serviceUrl, ref.serviceUrl)) return api;
|
|
1267
1221
|
const appInternals = app;
|
|
1268
1222
|
const httpClient = appInternals.api?.http ?? appInternals.client;
|
|
@@ -1271,12 +1225,12 @@ async function getApiClientForReference(app, ref) {
|
|
|
1271
1225
|
return new Client(ref.serviceUrl, httpClient);
|
|
1272
1226
|
}
|
|
1273
1227
|
function mergeReferenceIntoActivity(activity, ref) {
|
|
1274
|
-
const source =
|
|
1228
|
+
const source = (0, openclaw_plugin_sdk_string_coerce_runtime.asOptionalRecord)(activity) ?? {
|
|
1275
1229
|
type: "message",
|
|
1276
1230
|
text: stringifyReferenceFallbackActivity(activity)
|
|
1277
1231
|
};
|
|
1278
|
-
const existingChannelData =
|
|
1279
|
-
const existingTenant =
|
|
1232
|
+
const existingChannelData = (0, openclaw_plugin_sdk_string_coerce_runtime.asOptionalRecord)(source.channelData);
|
|
1233
|
+
const existingTenant = (0, openclaw_plugin_sdk_string_coerce_runtime.asOptionalRecord)(existingChannelData?.tenant);
|
|
1280
1234
|
let channelData = existingChannelData ? { ...existingChannelData } : void 0;
|
|
1281
1235
|
if (ref.tenantId) {
|
|
1282
1236
|
channelData ??= {};
|
|
@@ -1315,6 +1269,8 @@ async function sendMSTeamsActivityWithReference(app, source, activity, options)
|
|
|
1315
1269
|
};
|
|
1316
1270
|
}
|
|
1317
1271
|
const res = isTargeted && activities.createTargeted ? await activities.createTargeted(activityWithRef) : await activities.create(activityWithRef);
|
|
1272
|
+
const conversationId = ref.conversation.id.split(";")[0] ?? ref.conversation.id;
|
|
1273
|
+
if (res.id && activityWithRef.type === "message" && ref.conversation.conversationType === "channel" && ref.conversation.id === conversationId) recordMSTeamsSentMessage(conversationId, res.id, ref.bot.id);
|
|
1318
1274
|
return {
|
|
1319
1275
|
...activityWithRef,
|
|
1320
1276
|
...res
|
|
@@ -1341,9 +1297,6 @@ const MSTEAMS_MAX_MEDIA_BYTES = 104857600;
|
|
|
1341
1297
|
* Files >= 4MB use consent flow; smaller images can use inline base64.
|
|
1342
1298
|
*/
|
|
1343
1299
|
const FILE_CONSENT_THRESHOLD_BYTES = 4194304;
|
|
1344
|
-
function normalizeConversationId(rawId) {
|
|
1345
|
-
return rawId.split(";")[0] ?? rawId;
|
|
1346
|
-
}
|
|
1347
1300
|
function buildConversationReference(ref) {
|
|
1348
1301
|
const conversationId = ref.conversation?.id?.trim();
|
|
1349
1302
|
if (!conversationId) throw new Error("Invalid stored reference: missing conversation.id");
|
|
@@ -1361,7 +1314,7 @@ function buildConversationReference(ref) {
|
|
|
1361
1314
|
} : user,
|
|
1362
1315
|
agent,
|
|
1363
1316
|
conversation: {
|
|
1364
|
-
id:
|
|
1317
|
+
id: require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId),
|
|
1365
1318
|
conversationType: ref.conversation?.conversationType,
|
|
1366
1319
|
tenantId
|
|
1367
1320
|
},
|
|
@@ -1374,17 +1327,11 @@ function buildConversationReference(ref) {
|
|
|
1374
1327
|
}
|
|
1375
1328
|
function pushTextMessages(out, text, opts) {
|
|
1376
1329
|
if (!text) return;
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
out.push({ text: trimmed });
|
|
1382
|
-
}
|
|
1383
|
-
return;
|
|
1330
|
+
const chunks = opts.chunkText ? require_runtime.getMSTeamsRuntime().channel.text.chunkMarkdownTextWithMode(text, opts.chunkLimit, opts.chunkMode) : [text];
|
|
1331
|
+
for (const chunk of chunks) {
|
|
1332
|
+
const trimmed = chunk.trim();
|
|
1333
|
+
if (trimmed && !(0, openclaw_plugin_sdk_reply_chunking.isSilentReplyText)(trimmed, openclaw_plugin_sdk_reply_chunking.SILENT_REPLY_TOKEN)) out.push({ text: trimmed });
|
|
1384
1334
|
}
|
|
1385
|
-
const trimmed = text.trim();
|
|
1386
|
-
if (!trimmed || (0, openclaw_plugin_sdk_reply_chunking.isSilentReplyText)(trimmed, openclaw_plugin_sdk_reply_chunking.SILENT_REPLY_TOKEN)) return;
|
|
1387
|
-
out.push({ text: trimmed });
|
|
1388
1335
|
}
|
|
1389
1336
|
function clampMs(value, maxMs) {
|
|
1390
1337
|
if (!Number.isFinite(value) || value < 0) return 0;
|
|
@@ -1399,9 +1346,9 @@ function resolveRetryOptions(retry) {
|
|
|
1399
1346
|
};
|
|
1400
1347
|
return {
|
|
1401
1348
|
enabled: true,
|
|
1402
|
-
maxAttempts: Math.max(1, retry
|
|
1403
|
-
baseDelayMs: Math.max(0, retry
|
|
1404
|
-
maxDelayMs: Math.max(0, retry
|
|
1349
|
+
maxAttempts: Math.max(1, retry.maxAttempts ?? 3),
|
|
1350
|
+
baseDelayMs: Math.max(0, retry.baseDelayMs ?? 250),
|
|
1351
|
+
maxDelayMs: Math.max(0, retry.maxDelayMs ?? 1e4)
|
|
1405
1352
|
};
|
|
1406
1353
|
}
|
|
1407
1354
|
function computeRetryDelayMs(attempt, classification, opts) {
|
|
@@ -1421,37 +1368,20 @@ function renderReplyPayloadsToMessages(replies, options) {
|
|
|
1421
1368
|
for (const payload of replies) {
|
|
1422
1369
|
const reply = (0, openclaw_plugin_sdk_reply_payload.resolveSendableOutboundReplyParts)(payload, { text: formatMSTeamsMarkdown(payload.text ?? "", tableMode) });
|
|
1423
1370
|
if (!reply.hasContent) continue;
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1371
|
+
const [firstMedia, ...remainingMedia] = reply.mediaUrls;
|
|
1372
|
+
if (mediaMode === "inline" && firstMedia) {
|
|
1373
|
+
out.push({
|
|
1374
|
+
text: reply.text || void 0,
|
|
1375
|
+
mediaUrl: firstMedia
|
|
1429
1376
|
});
|
|
1430
|
-
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
const firstMedia = reply.mediaUrls[0];
|
|
1434
|
-
if (firstMedia) {
|
|
1435
|
-
out.push({
|
|
1436
|
-
text: reply.text || void 0,
|
|
1437
|
-
mediaUrl: firstMedia
|
|
1438
|
-
});
|
|
1439
|
-
for (let i = 1; i < reply.mediaUrls.length; i++) if (reply.mediaUrls[i]) out.push({ mediaUrl: reply.mediaUrls[i] });
|
|
1440
|
-
} else pushTextMessages(out, reply.text, {
|
|
1377
|
+
out.push(...remainingMedia.map((mediaUrl) => ({ mediaUrl })));
|
|
1378
|
+
} else {
|
|
1379
|
+
pushTextMessages(out, reply.text, {
|
|
1441
1380
|
chunkText,
|
|
1442
1381
|
chunkLimit,
|
|
1443
1382
|
chunkMode
|
|
1444
1383
|
});
|
|
1445
|
-
|
|
1446
|
-
}
|
|
1447
|
-
pushTextMessages(out, reply.text, {
|
|
1448
|
-
chunkText,
|
|
1449
|
-
chunkLimit,
|
|
1450
|
-
chunkMode
|
|
1451
|
-
});
|
|
1452
|
-
for (const mediaUrl of reply.mediaUrls) {
|
|
1453
|
-
if (!mediaUrl) continue;
|
|
1454
|
-
out.push({ mediaUrl });
|
|
1384
|
+
out.push(...reply.mediaUrls.map((mediaUrl) => ({ mediaUrl })));
|
|
1455
1385
|
}
|
|
1456
1386
|
}
|
|
1457
1387
|
return out;
|
|
@@ -1614,9 +1544,8 @@ async function sendMSTeamsMessages(params) {
|
|
|
1614
1544
|
fellBack: false
|
|
1615
1545
|
}),
|
|
1616
1546
|
onRevoked: async () => {
|
|
1617
|
-
const remaining = messages.slice(idx);
|
|
1618
1547
|
return {
|
|
1619
|
-
ids:
|
|
1548
|
+
ids: await sendProactively(messages.slice(idx), idx, resolvedThreadId),
|
|
1620
1549
|
fellBack: true
|
|
1621
1550
|
};
|
|
1622
1551
|
}
|
|
@@ -1629,6 +1558,423 @@ async function sendMSTeamsMessages(params) {
|
|
|
1629
1558
|
return await sendProactively(messages, 0);
|
|
1630
1559
|
}
|
|
1631
1560
|
//#endregion
|
|
1561
|
+
//#region extensions/msteams/src/polls.ts
|
|
1562
|
+
const MSTEAMS_POLLS_NAMESPACE = "polls";
|
|
1563
|
+
const MSTEAMS_POLL_VOTE_BUCKETS_NAMESPACE = "poll-vote-buckets";
|
|
1564
|
+
const MSTEAMS_MAX_POLLS = 1e3;
|
|
1565
|
+
const MSTEAMS_SQLITE_MAX_POLL_ROWS = 2e3;
|
|
1566
|
+
const MSTEAMS_POLL_VOTE_BUCKET_COUNT = 32;
|
|
1567
|
+
const MSTEAMS_MAX_POLL_VOTE_BUCKET_ROWS = 1001 * MSTEAMS_POLL_VOTE_BUCKET_COUNT;
|
|
1568
|
+
const MSTEAMS_POLL_TTL_MS = 2592e6;
|
|
1569
|
+
function normalizeChoiceValue(value) {
|
|
1570
|
+
if (typeof value === "string") {
|
|
1571
|
+
const trimmed = value.trim();
|
|
1572
|
+
return trimmed ? trimmed : null;
|
|
1573
|
+
}
|
|
1574
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
1575
|
+
return null;
|
|
1576
|
+
}
|
|
1577
|
+
function extractSelections(value) {
|
|
1578
|
+
if (Array.isArray(value)) return value.map(normalizeChoiceValue).filter((entry) => Boolean(entry));
|
|
1579
|
+
const normalized = normalizeChoiceValue(value);
|
|
1580
|
+
if (!normalized) return [];
|
|
1581
|
+
if (normalized.includes(",")) return (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeStringEntries)(normalized.split(","));
|
|
1582
|
+
return [normalized];
|
|
1583
|
+
}
|
|
1584
|
+
function readNestedValue(value, keys) {
|
|
1585
|
+
let current = value;
|
|
1586
|
+
for (const key of keys) {
|
|
1587
|
+
if (!(0, openclaw_plugin_sdk_string_coerce_runtime.isRecord)(current)) return;
|
|
1588
|
+
current = current[key];
|
|
1589
|
+
}
|
|
1590
|
+
return current;
|
|
1591
|
+
}
|
|
1592
|
+
function readNestedString(value, keys) {
|
|
1593
|
+
return (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(readNestedValue(value, keys));
|
|
1594
|
+
}
|
|
1595
|
+
function extractMSTeamsPollVote(activity) {
|
|
1596
|
+
const value = activity?.value;
|
|
1597
|
+
if (!value || !(0, openclaw_plugin_sdk_string_coerce_runtime.isRecord)(value)) return null;
|
|
1598
|
+
const pollId = readNestedString(value, ["openclawPollId"]) ?? readNestedString(value, ["pollId"]) ?? readNestedString(value, ["openclaw", "pollId"]) ?? readNestedString(value, [
|
|
1599
|
+
"openclaw",
|
|
1600
|
+
"poll",
|
|
1601
|
+
"id"
|
|
1602
|
+
]) ?? readNestedString(value, ["data", "openclawPollId"]) ?? readNestedString(value, ["data", "pollId"]) ?? readNestedString(value, [
|
|
1603
|
+
"data",
|
|
1604
|
+
"openclaw",
|
|
1605
|
+
"pollId"
|
|
1606
|
+
]) ?? readNestedString(value, [
|
|
1607
|
+
"action",
|
|
1608
|
+
"data",
|
|
1609
|
+
"openclawPollId"
|
|
1610
|
+
]) ?? readNestedString(value, [
|
|
1611
|
+
"action",
|
|
1612
|
+
"data",
|
|
1613
|
+
"pollId"
|
|
1614
|
+
]);
|
|
1615
|
+
if (!pollId) return null;
|
|
1616
|
+
const selections = [
|
|
1617
|
+
value.choices,
|
|
1618
|
+
readNestedValue(value, ["data", "choices"]),
|
|
1619
|
+
readNestedValue(value, [
|
|
1620
|
+
"action",
|
|
1621
|
+
"data",
|
|
1622
|
+
"choices"
|
|
1623
|
+
])
|
|
1624
|
+
].map(extractSelections).find((choices) => choices.length > 0) ?? [];
|
|
1625
|
+
if (selections.length === 0) return null;
|
|
1626
|
+
return {
|
|
1627
|
+
pollId,
|
|
1628
|
+
selections
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
function buildMSTeamsPollCard(params) {
|
|
1632
|
+
const pollId = params.pollId ?? node_crypto.default.randomUUID();
|
|
1633
|
+
const maxSelections = typeof params.maxSelections === "number" && params.maxSelections > 1 ? Math.floor(params.maxSelections) : 1;
|
|
1634
|
+
const cappedMaxSelections = Math.min(Math.max(1, maxSelections), params.options.length);
|
|
1635
|
+
const choices = params.options.map((option, index) => ({
|
|
1636
|
+
title: option,
|
|
1637
|
+
value: String(index)
|
|
1638
|
+
}));
|
|
1639
|
+
const hint = cappedMaxSelections > 1 ? `Select up to ${cappedMaxSelections} option${cappedMaxSelections === 1 ? "" : "s"}.` : "Select one option.";
|
|
1640
|
+
const card = {
|
|
1641
|
+
type: "AdaptiveCard",
|
|
1642
|
+
version: "1.5",
|
|
1643
|
+
body: [
|
|
1644
|
+
{
|
|
1645
|
+
type: "TextBlock",
|
|
1646
|
+
text: params.question,
|
|
1647
|
+
wrap: true,
|
|
1648
|
+
weight: "Bolder",
|
|
1649
|
+
size: "Medium"
|
|
1650
|
+
},
|
|
1651
|
+
{
|
|
1652
|
+
type: "Input.ChoiceSet",
|
|
1653
|
+
id: "choices",
|
|
1654
|
+
isMultiSelect: cappedMaxSelections > 1,
|
|
1655
|
+
style: "expanded",
|
|
1656
|
+
choices
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
type: "TextBlock",
|
|
1660
|
+
text: hint,
|
|
1661
|
+
wrap: true,
|
|
1662
|
+
isSubtle: true,
|
|
1663
|
+
spacing: "Small"
|
|
1664
|
+
}
|
|
1665
|
+
],
|
|
1666
|
+
actions: [{
|
|
1667
|
+
type: "Action.Execute",
|
|
1668
|
+
title: "Vote",
|
|
1669
|
+
verb: "openclaw.poll.vote",
|
|
1670
|
+
data: {
|
|
1671
|
+
openclawPollId: pollId,
|
|
1672
|
+
pollId
|
|
1673
|
+
}
|
|
1674
|
+
}]
|
|
1675
|
+
};
|
|
1676
|
+
const fallbackLines = [`Poll: ${params.question}`, ...params.options.map((option, index) => `${index + 1}. ${option}`)];
|
|
1677
|
+
return {
|
|
1678
|
+
pollId,
|
|
1679
|
+
question: params.question,
|
|
1680
|
+
options: params.options,
|
|
1681
|
+
maxSelections: cappedMaxSelections,
|
|
1682
|
+
card,
|
|
1683
|
+
fallbackText: fallbackLines.join("\n")
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
function createPollStateStore(params) {
|
|
1687
|
+
return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
|
|
1688
|
+
namespace: MSTEAMS_POLLS_NAMESPACE,
|
|
1689
|
+
maxEntries: MSTEAMS_SQLITE_MAX_POLL_ROWS,
|
|
1690
|
+
env: resolveMSTeamsSqliteStateEnv(params)
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
function createPollVoteBucketStateStore(params) {
|
|
1694
|
+
return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
|
|
1695
|
+
namespace: MSTEAMS_POLL_VOTE_BUCKETS_NAMESPACE,
|
|
1696
|
+
maxEntries: MSTEAMS_MAX_POLL_VOTE_BUCKET_ROWS,
|
|
1697
|
+
env: resolveMSTeamsSqliteStateEnv(params)
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
function isPollExpired(poll) {
|
|
1701
|
+
const cutoff = Date.now() - MSTEAMS_POLL_TTL_MS;
|
|
1702
|
+
return !(((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(poll.updatedAt ?? poll.createdAt) ?? 0) >= cutoff);
|
|
1703
|
+
}
|
|
1704
|
+
function normalizeMSTeamsPollSelections(poll, selections) {
|
|
1705
|
+
const maxSelections = Math.max(1, poll.maxSelections);
|
|
1706
|
+
const mapped = selections.map((entry) => (0, openclaw_plugin_sdk_number_runtime.parseStrictNonNegativeInteger)(entry)).filter((value) => value !== void 0).filter((value) => value >= 0 && value < poll.options.length).map((value) => String(value));
|
|
1707
|
+
return (0, openclaw_plugin_sdk_string_coerce_runtime.uniqueStrings)(mapped).slice(0, maxSelections);
|
|
1708
|
+
}
|
|
1709
|
+
function hashMSTeamsPollVote(pollId, voterId) {
|
|
1710
|
+
return node_crypto.default.createHash("sha256").update(pollId).update("\0").update(voterId).digest("hex");
|
|
1711
|
+
}
|
|
1712
|
+
function buildMSTeamsPollStateKey(pollId) {
|
|
1713
|
+
return node_crypto.default.createHash("sha256").update(pollId).digest("hex");
|
|
1714
|
+
}
|
|
1715
|
+
function selectMSTeamsPollVoteBucket(pollId, voterId) {
|
|
1716
|
+
const bucket = Number.parseInt(hashMSTeamsPollVote(pollId, voterId).slice(0, 8), 16);
|
|
1717
|
+
return String(bucket % MSTEAMS_POLL_VOTE_BUCKET_COUNT).padStart(4, "0");
|
|
1718
|
+
}
|
|
1719
|
+
function buildMSTeamsPollVoteBucketKey(pollId, bucket) {
|
|
1720
|
+
return `${buildMSTeamsPollStateKey(pollId)}:${bucket}`;
|
|
1721
|
+
}
|
|
1722
|
+
function createMSTeamsPollStoreState(params) {
|
|
1723
|
+
const pollStore = createPollStateStore(params);
|
|
1724
|
+
const voteBucketStore = createPollVoteBucketStateStore(params);
|
|
1725
|
+
const readPollVotes = async (pollId) => {
|
|
1726
|
+
const votes = {};
|
|
1727
|
+
for (const row of await voteBucketStore.entries()) if (row.value.pollId === pollId) Object.assign(votes, row.value.votes);
|
|
1728
|
+
return votes;
|
|
1729
|
+
};
|
|
1730
|
+
const deletePollVotes = async (pollId, cachedRows) => {
|
|
1731
|
+
for (const row of cachedRows?.values() ?? await voteBucketStore.entries()) if (row.value.pollId === pollId) {
|
|
1732
|
+
await voteBucketStore.delete(row.key);
|
|
1733
|
+
cachedRows?.delete(row.key);
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
const registerPollVotes = async (pollId, votes, updatedAt) => {
|
|
1737
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1738
|
+
for (const [voterId, selections] of Object.entries(votes)) {
|
|
1739
|
+
const bucket = selectMSTeamsPollVoteBucket(pollId, voterId);
|
|
1740
|
+
const bucketVotes = buckets.get(bucket) ?? {};
|
|
1741
|
+
bucketVotes[voterId] = selections;
|
|
1742
|
+
buckets.set(bucket, bucketVotes);
|
|
1743
|
+
}
|
|
1744
|
+
for (const [bucket, bucketVotes] of buckets) {
|
|
1745
|
+
const key = buildMSTeamsPollVoteBucketKey(pollId, bucket);
|
|
1746
|
+
const existing = await voteBucketStore.lookup(key);
|
|
1747
|
+
await voteBucketStore.register(key, toPluginJsonValue({
|
|
1748
|
+
pollId,
|
|
1749
|
+
bucket,
|
|
1750
|
+
votes: {
|
|
1751
|
+
...bucketVotes,
|
|
1752
|
+
...existing?.votes
|
|
1753
|
+
},
|
|
1754
|
+
updatedAt
|
|
1755
|
+
}));
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
const registerPollVote = async (pollId, voterId, selections, updatedAt) => {
|
|
1759
|
+
const bucket = selectMSTeamsPollVoteBucket(pollId, voterId);
|
|
1760
|
+
const key = buildMSTeamsPollVoteBucketKey(pollId, bucket);
|
|
1761
|
+
const existing = await voteBucketStore.lookup(key);
|
|
1762
|
+
await voteBucketStore.register(key, toPluginJsonValue({
|
|
1763
|
+
pollId,
|
|
1764
|
+
bucket,
|
|
1765
|
+
votes: {
|
|
1766
|
+
...existing?.votes,
|
|
1767
|
+
[voterId]: selections
|
|
1768
|
+
},
|
|
1769
|
+
updatedAt
|
|
1770
|
+
}));
|
|
1771
|
+
};
|
|
1772
|
+
const reconstructPoll = async (metadata) => {
|
|
1773
|
+
return {
|
|
1774
|
+
...metadata,
|
|
1775
|
+
votes: await readPollVotes(metadata.id)
|
|
1776
|
+
};
|
|
1777
|
+
};
|
|
1778
|
+
const prunePollStoreToLimit = async () => {
|
|
1779
|
+
let voteRows;
|
|
1780
|
+
const deletePrunedPollVotes = async (pollId) => {
|
|
1781
|
+
voteRows ??= new Map((await voteBucketStore.entries()).map((row) => [row.key, row]));
|
|
1782
|
+
await deletePollVotes(pollId, voteRows);
|
|
1783
|
+
};
|
|
1784
|
+
const rows = [];
|
|
1785
|
+
for (const row of await pollStore.entries()) {
|
|
1786
|
+
if (isPollExpired(row.value)) {
|
|
1787
|
+
await pollStore.delete(row.key);
|
|
1788
|
+
await deletePrunedPollVotes(row.value.id);
|
|
1789
|
+
continue;
|
|
1790
|
+
}
|
|
1791
|
+
rows.push(row);
|
|
1792
|
+
}
|
|
1793
|
+
if (rows.length <= MSTEAMS_MAX_POLLS) return;
|
|
1794
|
+
const sorted = rows.toSorted((a, b) => {
|
|
1795
|
+
return ((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(a.value.updatedAt ?? a.value.createdAt) ?? 0) - ((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(b.value.updatedAt ?? b.value.createdAt) ?? 0) || a.key.localeCompare(b.key);
|
|
1796
|
+
});
|
|
1797
|
+
for (const row of sorted.slice(0, rows.length - MSTEAMS_MAX_POLLS)) {
|
|
1798
|
+
await pollStore.delete(row.key);
|
|
1799
|
+
await deletePrunedPollVotes(row.value.id);
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
const createPoll = async (poll) => {
|
|
1803
|
+
await withMSTeamsSqliteMutationLock(params, MSTEAMS_POLLS_NAMESPACE, async () => {
|
|
1804
|
+
const { votes, ...metadata } = poll;
|
|
1805
|
+
await pollStore.register(buildMSTeamsPollStateKey(poll.id), toPluginJsonValue(metadata));
|
|
1806
|
+
await deletePollVotes(poll.id);
|
|
1807
|
+
await registerPollVotes(poll.id, votes, poll.updatedAt ?? poll.createdAt);
|
|
1808
|
+
await prunePollStoreToLimit();
|
|
1809
|
+
});
|
|
1810
|
+
};
|
|
1811
|
+
const getPoll = async (pollId) => {
|
|
1812
|
+
const poll = await pollStore.lookup(buildMSTeamsPollStateKey(pollId));
|
|
1813
|
+
if (!poll) return null;
|
|
1814
|
+
if (isPollExpired(poll)) return null;
|
|
1815
|
+
return await reconstructPoll(poll);
|
|
1816
|
+
};
|
|
1817
|
+
const recordVote = async (vote) => {
|
|
1818
|
+
return await withMSTeamsSqliteMutationLock(params, MSTEAMS_POLLS_NAMESPACE, async () => {
|
|
1819
|
+
const pollKey = buildMSTeamsPollStateKey(vote.pollId);
|
|
1820
|
+
const poll = await pollStore.lookup(pollKey);
|
|
1821
|
+
if (!poll) return null;
|
|
1822
|
+
if (isPollExpired(poll)) {
|
|
1823
|
+
await pollStore.delete(pollKey);
|
|
1824
|
+
await deletePollVotes(vote.pollId);
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1827
|
+
const normalized = normalizeMSTeamsPollSelections(await reconstructPoll(poll), vote.selections);
|
|
1828
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1829
|
+
poll.updatedAt = updatedAt;
|
|
1830
|
+
await pollStore.register(pollKey, toPluginJsonValue(poll));
|
|
1831
|
+
await registerPollVote(vote.pollId, vote.voterId, normalized, updatedAt);
|
|
1832
|
+
await prunePollStoreToLimit();
|
|
1833
|
+
return await reconstructPoll(poll);
|
|
1834
|
+
});
|
|
1835
|
+
};
|
|
1836
|
+
return {
|
|
1837
|
+
createPoll,
|
|
1838
|
+
getPoll,
|
|
1839
|
+
recordVote
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
//#endregion
|
|
1843
|
+
//#region extensions/msteams/src/conversation-store-helpers.ts
|
|
1844
|
+
function toConversationStoreEntries(entries) {
|
|
1845
|
+
return Array.from(entries, ([conversationId, reference]) => ({
|
|
1846
|
+
conversationId,
|
|
1847
|
+
reference
|
|
1848
|
+
}));
|
|
1849
|
+
}
|
|
1850
|
+
function mergeStoredConversationReference(existing, incoming, nowIso) {
|
|
1851
|
+
return {
|
|
1852
|
+
...existing?.timezone && !incoming.timezone ? { timezone: existing.timezone } : {},
|
|
1853
|
+
...existing?.tenantId && !incoming.tenantId ? { tenantId: existing.tenantId } : {},
|
|
1854
|
+
...existing?.aadObjectId && !incoming.aadObjectId ? { aadObjectId: existing.aadObjectId } : {},
|
|
1855
|
+
...incoming,
|
|
1856
|
+
lastSeenAt: nowIso
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
function findPreferredDmConversationByUserId(entries, id) {
|
|
1860
|
+
const target = id.trim();
|
|
1861
|
+
if (!target) return null;
|
|
1862
|
+
const personalMatches = [];
|
|
1863
|
+
const unknownTypeMatches = [];
|
|
1864
|
+
for (const entry of entries) {
|
|
1865
|
+
if (entry.reference.user?.aadObjectId !== target && entry.reference.user?.id !== target) continue;
|
|
1866
|
+
const convType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(entry.reference.conversation?.conversationType ?? "");
|
|
1867
|
+
if (convType === "personal") personalMatches.push(entry);
|
|
1868
|
+
else if (convType === "channel" || convType === "groupchat") {} else unknownTypeMatches.push(entry);
|
|
1869
|
+
}
|
|
1870
|
+
const candidates = personalMatches.length > 0 ? personalMatches : unknownTypeMatches;
|
|
1871
|
+
if (candidates.length === 0) return null;
|
|
1872
|
+
if (candidates.length > 1) candidates.sort((a, b) => ((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(b.reference.lastSeenAt) ?? 0) - ((0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(a.reference.lastSeenAt) ?? 0));
|
|
1873
|
+
return candidates[0] ?? null;
|
|
1874
|
+
}
|
|
1875
|
+
//#endregion
|
|
1876
|
+
//#region extensions/msteams/src/conversation-store-state.ts
|
|
1877
|
+
const MSTEAMS_CONVERSATIONS_NAMESPACE = "conversations";
|
|
1878
|
+
const MSTEAMS_MAX_CONVERSATIONS = 1e3;
|
|
1879
|
+
const MSTEAMS_SQLITE_MAX_CONVERSATION_ROWS = 2e3;
|
|
1880
|
+
const MSTEAMS_CONVERSATION_TTL_MS = 31536e6;
|
|
1881
|
+
const CONVERSATION_MUTATION_KEY = "conversations";
|
|
1882
|
+
function createConversationStateStore(params) {
|
|
1883
|
+
return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
|
|
1884
|
+
namespace: MSTEAMS_CONVERSATIONS_NAMESPACE,
|
|
1885
|
+
maxEntries: MSTEAMS_SQLITE_MAX_CONVERSATION_ROWS,
|
|
1886
|
+
env: resolveMSTeamsSqliteStateEnv(params)
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
function buildMSTeamsConversationStateKey(conversationId) {
|
|
1890
|
+
return node_crypto.default.createHash("sha256").update(conversationId).digest("hex");
|
|
1891
|
+
}
|
|
1892
|
+
function prepareMSTeamsConversationReferenceForStorage(conversationId, reference) {
|
|
1893
|
+
return {
|
|
1894
|
+
...reference,
|
|
1895
|
+
conversation: {
|
|
1896
|
+
...reference.conversation,
|
|
1897
|
+
id: conversationId
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
function getStoredConversationId(reference) {
|
|
1902
|
+
const rawId = reference.conversation?.id;
|
|
1903
|
+
return rawId ? require_resolve_allowlist.normalizeMSTeamsConversationId(rawId) : null;
|
|
1904
|
+
}
|
|
1905
|
+
function createMSTeamsConversationStoreState(params) {
|
|
1906
|
+
const ttlMs = params?.ttlMs ?? MSTEAMS_CONVERSATION_TTL_MS;
|
|
1907
|
+
const conversationStore = createConversationStateStore(params);
|
|
1908
|
+
const isExpired = (reference) => {
|
|
1909
|
+
const lastSeenAt = (0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(reference.lastSeenAt);
|
|
1910
|
+
return lastSeenAt != null && Date.now() - lastSeenAt > ttlMs;
|
|
1911
|
+
};
|
|
1912
|
+
const lookupStored = async (conversationId) => {
|
|
1913
|
+
const normalizedId = require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId);
|
|
1914
|
+
const value = await conversationStore.lookup(buildMSTeamsConversationStateKey(normalizedId));
|
|
1915
|
+
if (!value) return null;
|
|
1916
|
+
if (isExpired(value)) return null;
|
|
1917
|
+
return value;
|
|
1918
|
+
};
|
|
1919
|
+
const entries = async () => {
|
|
1920
|
+
const rows = await conversationStore.entries();
|
|
1921
|
+
const kept = [];
|
|
1922
|
+
for (const row of rows) {
|
|
1923
|
+
if (isExpired(row.value)) continue;
|
|
1924
|
+
const conversationId = getStoredConversationId(row.value);
|
|
1925
|
+
if (conversationId) kept.push([conversationId, row.value]);
|
|
1926
|
+
}
|
|
1927
|
+
return kept;
|
|
1928
|
+
};
|
|
1929
|
+
const register = async (conversationId, reference) => {
|
|
1930
|
+
const normalizedId = require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId);
|
|
1931
|
+
await conversationStore.register(buildMSTeamsConversationStateKey(normalizedId), toPluginJsonValue(prepareMSTeamsConversationReferenceForStorage(normalizedId, reference)));
|
|
1932
|
+
const rows = [];
|
|
1933
|
+
for (const row of await conversationStore.entries()) {
|
|
1934
|
+
if (isExpired(row.value)) {
|
|
1935
|
+
await conversationStore.delete(row.key);
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
rows.push(row);
|
|
1939
|
+
}
|
|
1940
|
+
if (rows.length <= MSTEAMS_MAX_CONVERSATIONS) return;
|
|
1941
|
+
const sorted = rows.toSorted((a, b) => {
|
|
1942
|
+
const aTs = (0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(a.value.lastSeenAt) ?? 0;
|
|
1943
|
+
const bTs = (0, openclaw_plugin_sdk_number_runtime.parseDateStringTimestampMs)(b.value.lastSeenAt) ?? 0;
|
|
1944
|
+
const aId = getStoredConversationId(a.value) ?? a.key;
|
|
1945
|
+
const bId = getStoredConversationId(b.value) ?? b.key;
|
|
1946
|
+
return aTs - bTs || aId.localeCompare(bId);
|
|
1947
|
+
});
|
|
1948
|
+
for (const row of sorted.slice(0, rows.length - MSTEAMS_MAX_CONVERSATIONS)) await conversationStore.delete(row.key);
|
|
1949
|
+
};
|
|
1950
|
+
const list = async () => {
|
|
1951
|
+
return toConversationStoreEntries(await entries());
|
|
1952
|
+
};
|
|
1953
|
+
const findPreferredDmByUserId = async (id) => {
|
|
1954
|
+
return findPreferredDmConversationByUserId(await list(), id);
|
|
1955
|
+
};
|
|
1956
|
+
const upsert = async (conversationId, reference) => {
|
|
1957
|
+
const normalizedId = require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId);
|
|
1958
|
+
await withMSTeamsSqliteMutationLock(params, CONVERSATION_MUTATION_KEY, async () => {
|
|
1959
|
+
const existing = await lookupStored(normalizedId);
|
|
1960
|
+
await register(normalizedId, mergeStoredConversationReference(existing ?? void 0, reference, (/* @__PURE__ */ new Date()).toISOString()));
|
|
1961
|
+
});
|
|
1962
|
+
};
|
|
1963
|
+
const remove = async (conversationId) => {
|
|
1964
|
+
const normalizedId = require_resolve_allowlist.normalizeMSTeamsConversationId(conversationId);
|
|
1965
|
+
return await withMSTeamsSqliteMutationLock(params, CONVERSATION_MUTATION_KEY, async () => {
|
|
1966
|
+
return await conversationStore.delete(buildMSTeamsConversationStateKey(normalizedId));
|
|
1967
|
+
});
|
|
1968
|
+
};
|
|
1969
|
+
return {
|
|
1970
|
+
upsert,
|
|
1971
|
+
get: lookupStored,
|
|
1972
|
+
list,
|
|
1973
|
+
remove,
|
|
1974
|
+
findPreferredDmByUserId
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
//#endregion
|
|
1632
1978
|
Object.defineProperty(exports, "buildConversationReference", {
|
|
1633
1979
|
enumerable: true,
|
|
1634
1980
|
get: function() {
|
|
@@ -1641,18 +1987,42 @@ Object.defineProperty(exports, "buildFileInfoCard", {
|
|
|
1641
1987
|
return buildFileInfoCard;
|
|
1642
1988
|
}
|
|
1643
1989
|
});
|
|
1990
|
+
Object.defineProperty(exports, "buildMSTeamsAdaptiveCardActivity", {
|
|
1991
|
+
enumerable: true,
|
|
1992
|
+
get: function() {
|
|
1993
|
+
return buildMSTeamsAdaptiveCardActivity;
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1644
1996
|
Object.defineProperty(exports, "buildMSTeamsMessageActivity", {
|
|
1645
1997
|
enumerable: true,
|
|
1646
1998
|
get: function() {
|
|
1647
1999
|
return buildMSTeamsMessageActivity;
|
|
1648
2000
|
}
|
|
1649
2001
|
});
|
|
2002
|
+
Object.defineProperty(exports, "buildMSTeamsPollCard", {
|
|
2003
|
+
enumerable: true,
|
|
2004
|
+
get: function() {
|
|
2005
|
+
return buildMSTeamsPollCard;
|
|
2006
|
+
}
|
|
2007
|
+
});
|
|
1650
2008
|
Object.defineProperty(exports, "buildTeamsFileInfoCard", {
|
|
1651
2009
|
enumerable: true,
|
|
1652
2010
|
get: function() {
|
|
1653
2011
|
return buildTeamsFileInfoCard;
|
|
1654
2012
|
}
|
|
1655
2013
|
});
|
|
2014
|
+
Object.defineProperty(exports, "createMSTeamsConversationStoreState", {
|
|
2015
|
+
enumerable: true,
|
|
2016
|
+
get: function() {
|
|
2017
|
+
return createMSTeamsConversationStoreState;
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
Object.defineProperty(exports, "createMSTeamsPollStoreState", {
|
|
2021
|
+
enumerable: true,
|
|
2022
|
+
get: function() {
|
|
2023
|
+
return createMSTeamsPollStoreState;
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
1656
2026
|
Object.defineProperty(exports, "deleteMSTeamsActivityWithReference", {
|
|
1657
2027
|
enumerable: true,
|
|
1658
2028
|
get: function() {
|
|
@@ -1665,6 +2035,12 @@ Object.defineProperty(exports, "extractFilename", {
|
|
|
1665
2035
|
return extractFilename;
|
|
1666
2036
|
}
|
|
1667
2037
|
});
|
|
2038
|
+
Object.defineProperty(exports, "extractMSTeamsPollVote", {
|
|
2039
|
+
enumerable: true,
|
|
2040
|
+
get: function() {
|
|
2041
|
+
return extractMSTeamsPollVote;
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
1668
2044
|
Object.defineProperty(exports, "extractMessageId", {
|
|
1669
2045
|
enumerable: true,
|
|
1670
2046
|
get: function() {
|
|
@@ -1707,6 +2083,12 @@ Object.defineProperty(exports, "prepareFileConsentActivityFs", {
|
|
|
1707
2083
|
return prepareFileConsentActivityFs;
|
|
1708
2084
|
}
|
|
1709
2085
|
});
|
|
2086
|
+
Object.defineProperty(exports, "recordMSTeamsSentMessage", {
|
|
2087
|
+
enumerable: true,
|
|
2088
|
+
get: function() {
|
|
2089
|
+
return recordMSTeamsSentMessage;
|
|
2090
|
+
}
|
|
2091
|
+
});
|
|
1710
2092
|
Object.defineProperty(exports, "removePendingUpload", {
|
|
1711
2093
|
enumerable: true,
|
|
1712
2094
|
get: function() {
|
|
@@ -1737,6 +2119,12 @@ Object.defineProperty(exports, "requiresFileConsent", {
|
|
|
1737
2119
|
return requiresFileConsent;
|
|
1738
2120
|
}
|
|
1739
2121
|
});
|
|
2122
|
+
Object.defineProperty(exports, "resolveMSTeamsSqliteStateEnv", {
|
|
2123
|
+
enumerable: true,
|
|
2124
|
+
get: function() {
|
|
2125
|
+
return resolveMSTeamsSqliteStateEnv;
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
1740
2128
|
Object.defineProperty(exports, "sendMSTeamsActivityWithReference", {
|
|
1741
2129
|
enumerable: true,
|
|
1742
2130
|
get: function() {
|
|
@@ -1761,6 +2149,12 @@ Object.defineProperty(exports, "setPendingUploadActivityIdFs", {
|
|
|
1761
2149
|
return setPendingUploadActivityIdFs;
|
|
1762
2150
|
}
|
|
1763
2151
|
});
|
|
2152
|
+
Object.defineProperty(exports, "toPluginJsonValue", {
|
|
2153
|
+
enumerable: true,
|
|
2154
|
+
get: function() {
|
|
2155
|
+
return toPluginJsonValue;
|
|
2156
|
+
}
|
|
2157
|
+
});
|
|
1764
2158
|
Object.defineProperty(exports, "updateMSTeamsActivityWithReference", {
|
|
1765
2159
|
enumerable: true,
|
|
1766
2160
|
get: function() {
|
|
@@ -1779,6 +2173,18 @@ Object.defineProperty(exports, "uploadToConsentUrl", {
|
|
|
1779
2173
|
return uploadToConsentUrl;
|
|
1780
2174
|
}
|
|
1781
2175
|
});
|
|
2176
|
+
Object.defineProperty(exports, "wasMSTeamsMessageSentWithPersistence", {
|
|
2177
|
+
enumerable: true,
|
|
2178
|
+
get: function() {
|
|
2179
|
+
return wasMSTeamsMessageSentWithPersistence;
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
2182
|
+
Object.defineProperty(exports, "withMSTeamsSqliteMutationLock", {
|
|
2183
|
+
enumerable: true,
|
|
2184
|
+
get: function() {
|
|
2185
|
+
return withMSTeamsSqliteMutationLock;
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
1782
2188
|
Object.defineProperty(exports, "withRevokedProxyFallback", {
|
|
1783
2189
|
enumerable: true,
|
|
1784
2190
|
get: function() {
|