@openclaw/msteams 2026.5.28 → 2026.5.30-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-I5mMZ1Ju.js} +4 -4
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-CxxY1xk6.js → channel.runtime-Cg8XWuDW.js} +34 -27
- package/dist/{errors-DZGI_mqq.js → errors-HUt6we-U.js} +2 -2
- package/dist/{probe-BoUA5GpA.js → probe-MGBfnHCR.js} +365 -98
- package/dist/setup-plugin-api.js +2 -2
- package/dist/{setup-surface-C9IApOv3.js → setup-surface-BLAlJzog.js} +1 -1
- package/dist/{src-D_rcW2Zm.js → src-hH0j_Pt1.js} +32 -14
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
package/dist/api.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as msteamsSetupAdapter, n as openDelegatedOAuthUrl, r as createMSTeamsSetupWizardBase, t as msteamsSetupWizard } from "./setup-surface-
|
|
2
|
-
import { t as msteamsPlugin } from "./channel-
|
|
1
|
+
import { i as msteamsSetupAdapter, n as openDelegatedOAuthUrl, r as createMSTeamsSetupWizardBase, t as msteamsSetupWizard } from "./setup-surface-BLAlJzog.js";
|
|
2
|
+
import { t as msteamsPlugin } from "./channel-I5mMZ1Ju.js";
|
|
3
3
|
export { createMSTeamsSetupWizardBase, msteamsPlugin, msteamsSetupAdapter, msteamsSetupWizard, openDelegatedOAuthUrl };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { O as resolveNestedAllowlistDecision, S as normalizeChannelSlug, T as resolveChannelEntryMatchWithFallback, _ as isDangerousNameMatchingEnabled, i as buildChannelKeyCandidates, k as resolveToolsBySender, o as buildProbeChannelStatusSummary, r as PAIRING_APPROVED_MESSAGE, s as chunkTextForOutbound, t as DEFAULT_ACCOUNT_ID, u as createDefaultChannelRuntimeState, w as resolveAllowlistMatchSimple } from "./runtime-api-BlvMnDKz.js";
|
|
2
|
-
import { y as resolveMSTeamsCredentials } from "./errors-
|
|
3
|
-
import { a as looksLikeMSTeamsTargetId, c as parseMSTeamsConversationId, d as resolveMSTeamsUserAllowlist, i as msteamsSetupAdapter, l as parseMSTeamsTeamChannelInput, o as normalizeMSTeamsMessagingTarget, s as normalizeMSTeamsUserInput, t as msteamsSetupWizard, u as resolveMSTeamsChannelAllowlist } from "./setup-surface-
|
|
2
|
+
import { y as resolveMSTeamsCredentials } from "./errors-HUt6we-U.js";
|
|
3
|
+
import { a as looksLikeMSTeamsTargetId, c as parseMSTeamsConversationId, d as resolveMSTeamsUserAllowlist, i as msteamsSetupAdapter, l as parseMSTeamsTeamChannelInput, o as normalizeMSTeamsMessagingTarget, s as normalizeMSTeamsUserInput, t as msteamsSetupWizard, u as resolveMSTeamsChannelAllowlist } from "./setup-surface-BLAlJzog.js";
|
|
4
4
|
import { t as MSTeamsChannelConfigSchema } from "./config-schema-BL4qQZiA.js";
|
|
5
5
|
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
6
6
|
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
|
@@ -330,7 +330,7 @@ const collectMSTeamsSecurityWarnings = createAllowlistProviderGroupPolicyWarning
|
|
|
330
330
|
resolveGroupPolicy: ({ cfg }) => cfg.channels?.msteams?.groupPolicy,
|
|
331
331
|
collect: ({ groupPolicy }) => groupPolicy === "open" ? ["- MS Teams groups: groupPolicy=\"open\" allows any member to trigger (mention-gated). Set channels.msteams.groupPolicy=\"allowlist\" + channels.msteams.groupAllowFrom to restrict senders."] : []
|
|
332
332
|
});
|
|
333
|
-
const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-
|
|
333
|
+
const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-Cg8XWuDW.js"), "msTeamsChannelRuntime");
|
|
334
334
|
const resolveMSTeamsChannelConfig = (cfg) => ({
|
|
335
335
|
allowFrom: cfg.channels?.msteams?.allowFrom,
|
|
336
336
|
defaultTo: cfg.channels?.msteams?.defaultTo
|
|
@@ -1116,7 +1116,7 @@ const msteamsPlugin = createChatChannelPlugin({
|
|
|
1116
1116
|
})
|
|
1117
1117
|
}),
|
|
1118
1118
|
gateway: { startAccount: async (ctx) => {
|
|
1119
|
-
const { monitorMSTeamsProvider } = await import("./src-
|
|
1119
|
+
const { monitorMSTeamsProvider } = await import("./src-hH0j_Pt1.js");
|
|
1120
1120
|
const port = ctx.cfg.channels?.msteams?.webhook?.port ?? 3978;
|
|
1121
1121
|
ctx.setStatus({
|
|
1122
1122
|
accountId: ctx.accountId,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as msteamsPlugin } from "./channel-
|
|
1
|
+
import { t as msteamsPlugin } from "./channel-I5mMZ1Ju.js";
|
|
2
2
|
export { msteamsPlugin };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { C as normalizeStringEntries$1, s as chunkTextForOutbound } from "./runtime-api-BlvMnDKz.js";
|
|
2
|
-
import { a as searchGraphUsers, c as fetchGraphAbsoluteUrl, d as listTeamsByName, f as normalizeQuery, g as resolveGraphToken, h as postGraphJson, l as fetchGraphJson, m as postGraphBetaJson, o as deleteGraphRequest, p as patchGraphJson, s as escapeOData, u as listChannelsForTeam } from "./errors-
|
|
3
|
-
import { n as MSTEAMS_PRESENTATION_CAPABILITIES, r as buildMSTeamsPresentationCard } from "./channel-
|
|
4
|
-
import { S as
|
|
2
|
+
import { a as searchGraphUsers, c as fetchGraphAbsoluteUrl, d as listTeamsByName, f as normalizeQuery, g as resolveGraphToken, h as postGraphJson, l as fetchGraphJson, m as postGraphBetaJson, o as deleteGraphRequest, p as patchGraphJson, s as escapeOData, u as listChannelsForTeam } from "./errors-HUt6we-U.js";
|
|
3
|
+
import { n as MSTEAMS_PRESENTATION_CAPABILITIES, r as buildMSTeamsPresentationCard } from "./channel-I5mMZ1Ju.js";
|
|
4
|
+
import { S as createMSTeamsConversationStoreState, a as sendMessageMSTeams, b as createMSTeamsPollStoreState, i as sendAdaptiveCardMSTeams, n as deleteMessageMSTeams, o as sendPollMSTeams, r as editMessageMSTeams, t as probeMSTeams } from "./probe-MGBfnHCR.js";
|
|
5
5
|
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-outbound";
|
|
6
6
|
import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
7
|
import { resolvePayloadMediaUrls, resolveTextChunksWithFallback, sendPayloadMediaSequence } from "openclaw/plugin-sdk/reply-payload";
|
|
@@ -96,7 +96,7 @@ async function resolveGraphConversationId(to) {
|
|
|
96
96
|
const isUserTarget = /^user:/i.test(trimmed);
|
|
97
97
|
const cleaned = stripTargetPrefix(trimmed);
|
|
98
98
|
if (!isUserTarget) return cleaned;
|
|
99
|
-
const found = await
|
|
99
|
+
const found = await createMSTeamsConversationStoreState().findPreferredDmByUserId(cleaned);
|
|
100
100
|
if (!found) throw new Error(`No conversation found for user:${cleaned}. The bot must receive a message from this user before Graph API operations work.`);
|
|
101
101
|
if (found.reference.graphChatId) return found.reference.graphChatId;
|
|
102
102
|
if (found.conversationId.startsWith("19:")) return found.conversationId;
|
|
@@ -500,6 +500,23 @@ function asObjectRecord(value) {
|
|
|
500
500
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
501
501
|
}
|
|
502
502
|
const MSTEAMS_TEXT_CHUNK_LIMIT = 4e3;
|
|
503
|
+
function resolveMSTeamsTextSend(params) {
|
|
504
|
+
return resolveOutboundSendDep(params.deps, "msteams") ?? ((to, text) => sendMessageMSTeams({
|
|
505
|
+
cfg: params.cfg,
|
|
506
|
+
to,
|
|
507
|
+
text
|
|
508
|
+
}));
|
|
509
|
+
}
|
|
510
|
+
function resolveMSTeamsMediaSend(params) {
|
|
511
|
+
return resolveOutboundSendDep(params.deps, "msteams") ?? ((to, text, opts) => sendMessageMSTeams({
|
|
512
|
+
cfg: params.cfg,
|
|
513
|
+
to,
|
|
514
|
+
text,
|
|
515
|
+
mediaUrl: opts?.mediaUrl,
|
|
516
|
+
mediaLocalRoots: opts?.mediaLocalRoots,
|
|
517
|
+
mediaReadFile: opts?.mediaReadFile
|
|
518
|
+
}));
|
|
519
|
+
}
|
|
503
520
|
//#endregion
|
|
504
521
|
//#region extensions/msteams/src/channel.runtime.ts
|
|
505
522
|
const msTeamsChannelRuntime = {
|
|
@@ -564,14 +581,10 @@ const msTeamsChannelRuntime = {
|
|
|
564
581
|
mediaUrl: payload.mediaUrl ?? mediaUrl
|
|
565
582
|
}));
|
|
566
583
|
if (mediaUrls.length > 0) {
|
|
567
|
-
const send =
|
|
584
|
+
const send = resolveMSTeamsMediaSend({
|
|
568
585
|
cfg,
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
mediaUrl: opts?.mediaUrl,
|
|
572
|
-
mediaLocalRoots: opts?.mediaLocalRoots,
|
|
573
|
-
mediaReadFile: opts?.mediaReadFile
|
|
574
|
-
}));
|
|
586
|
+
deps
|
|
587
|
+
});
|
|
575
588
|
const result = await sendPayloadMediaSequence({
|
|
576
589
|
text,
|
|
577
590
|
mediaUrls,
|
|
@@ -584,11 +597,10 @@ const msTeamsChannelRuntime = {
|
|
|
584
597
|
if (result) return attachChannelToResult("msteams", result);
|
|
585
598
|
}
|
|
586
599
|
if (text.trim()) {
|
|
587
|
-
const send =
|
|
600
|
+
const send = resolveMSTeamsTextSend({
|
|
588
601
|
cfg,
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}));
|
|
602
|
+
deps
|
|
603
|
+
});
|
|
592
604
|
const chunks = resolveTextChunksWithFallback(text, chunkTextForOutbound(text, MSTEAMS_TEXT_CHUNK_LIMIT));
|
|
593
605
|
let result;
|
|
594
606
|
for (const chunk of chunks) result = await send(to, chunk);
|
|
@@ -599,21 +611,16 @@ const msTeamsChannelRuntime = {
|
|
|
599
611
|
...createAttachedChannelResultAdapter({
|
|
600
612
|
channel: "msteams",
|
|
601
613
|
sendText: async ({ cfg, to, text, deps }) => {
|
|
602
|
-
return await (
|
|
614
|
+
return await resolveMSTeamsTextSend({
|
|
603
615
|
cfg,
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
})))(to, text);
|
|
616
|
+
deps
|
|
617
|
+
})(to, text);
|
|
607
618
|
},
|
|
608
619
|
sendMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, mediaReadFile, deps }) => {
|
|
609
|
-
return await (
|
|
620
|
+
return await resolveMSTeamsMediaSend({
|
|
610
621
|
cfg,
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
mediaUrl: opts?.mediaUrl,
|
|
614
|
-
mediaLocalRoots: opts?.mediaLocalRoots,
|
|
615
|
-
mediaReadFile: opts?.mediaReadFile
|
|
616
|
-
})))(to, text, {
|
|
622
|
+
deps
|
|
623
|
+
})(to, text, {
|
|
617
624
|
mediaUrl,
|
|
618
625
|
mediaLocalRoots,
|
|
619
626
|
mediaReadFile
|
|
@@ -628,7 +635,7 @@ const msTeamsChannelRuntime = {
|
|
|
628
635
|
options: poll.options,
|
|
629
636
|
maxSelections
|
|
630
637
|
});
|
|
631
|
-
await
|
|
638
|
+
await createMSTeamsPollStoreState().createPoll({
|
|
632
639
|
id: result.pollId,
|
|
633
640
|
question: poll.question,
|
|
634
641
|
options: poll.options,
|
|
@@ -10,8 +10,8 @@ import { buildHostnameAllowlistPolicyFromSuffixAllowlist, isHttpsUrlAllowedByHos
|
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
11
|
import { readFileSync } from "node:fs";
|
|
12
12
|
import path, { basename, dirname } from "node:path";
|
|
13
|
+
import { asFiniteNumberInRange, isFutureDateTimestampMs, parseStrictFiniteNumber } from "openclaw/plugin-sdk/number-runtime";
|
|
13
14
|
import { privateFileStoreSync } from "openclaw/plugin-sdk/security-runtime";
|
|
14
|
-
import { asFiniteNumberInRange, parseStrictFiniteNumber } from "openclaw/plugin-sdk/number-runtime";
|
|
15
15
|
import { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
16
16
|
//#region extensions/msteams/src/attachments/shared.ts
|
|
17
17
|
const IMAGE_EXT_RE = /\.(avif|bmp|gif|heic|heif|jpe?g|png|tiff?|webp)$/i;
|
|
@@ -807,7 +807,7 @@ function saveDelegatedTokens(tokens) {
|
|
|
807
807
|
async function resolveDelegatedAccessToken(params) {
|
|
808
808
|
const tokens = loadDelegatedTokens();
|
|
809
809
|
if (!tokens) return;
|
|
810
|
-
if (tokens.expiresAt
|
|
810
|
+
if (isFutureDateTimestampMs(tokens.expiresAt)) return tokens.accessToken;
|
|
811
811
|
try {
|
|
812
812
|
const refreshed = await refreshMSTeamsDelegatedTokens({
|
|
813
813
|
tenantId: params.tenantId,
|
|
@@ -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-HUt6we-U.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-I5mMZ1Ju.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 { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
15
|
-
import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
|
|
16
15
|
import crypto from "node:crypto";
|
|
16
|
+
import fs from "node:fs/promises";
|
|
17
|
+
import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
|
|
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,212 @@ 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
|
+
return JSON.parse(JSON.stringify(value));
|
|
110
|
+
}
|
|
111
|
+
function resolveMSTeamsSqliteStateDir(options) {
|
|
112
|
+
return resolveStateDirOverride(options) ?? getMSTeamsRuntime().state.resolveStateDir(options?.env ?? process.env, options?.homedir);
|
|
113
|
+
}
|
|
114
|
+
const sqliteMutationLocks = /* @__PURE__ */ new Map();
|
|
115
|
+
async function withProcessMutationLock(lockPath, fn) {
|
|
116
|
+
const previous = sqliteMutationLocks.get(lockPath) ?? Promise.resolve();
|
|
117
|
+
let release = () => {};
|
|
118
|
+
const next = new Promise((resolve) => {
|
|
119
|
+
release = resolve;
|
|
120
|
+
});
|
|
121
|
+
const chained = previous.then(() => next, () => next);
|
|
122
|
+
sqliteMutationLocks.set(lockPath, chained);
|
|
123
|
+
await previous.catch(() => void 0);
|
|
124
|
+
try {
|
|
125
|
+
return await fn();
|
|
126
|
+
} finally {
|
|
127
|
+
release();
|
|
128
|
+
if (sqliteMutationLocks.get(lockPath) === chained) sqliteMutationLocks.delete(lockPath);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function withMSTeamsSqliteMutationLock(options, lockFilename, fn) {
|
|
132
|
+
const lockPath = path.join(resolveMSTeamsSqliteStateDir(options), lockFilename);
|
|
133
|
+
return await withProcessMutationLock(lockPath, async () => {
|
|
134
|
+
return await withFileLock$1(lockPath, { version: 1 }, fn);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region extensions/msteams/src/conversation-store-state.ts
|
|
92
139
|
const STORE_FILENAME$2 = "msteams-conversations.json";
|
|
140
|
+
const CONVERSATIONS_NAMESPACE = "conversations";
|
|
141
|
+
const CONVERSATION_MIGRATIONS_NAMESPACE = "conversation-migrations";
|
|
142
|
+
const LEGACY_JSON_MIGRATION_KEY = "msteams-conversations-json-v1";
|
|
93
143
|
const MAX_CONVERSATIONS = 1e3;
|
|
144
|
+
const SQLITE_MAX_CONVERSATION_ROWS = 2e3;
|
|
94
145
|
const CONVERSATION_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
146
|
+
const CONVERSATION_LOCK_FILENAME = "msteams-conversations.sqlite.lock";
|
|
147
|
+
function createConversationStateStore(params) {
|
|
148
|
+
return getMSTeamsRuntime().state.openKeyedStore({
|
|
149
|
+
namespace: CONVERSATIONS_NAMESPACE,
|
|
150
|
+
maxEntries: SQLITE_MAX_CONVERSATION_ROWS,
|
|
151
|
+
env: resolveMSTeamsSqliteStateEnv(params)
|
|
100
152
|
});
|
|
101
|
-
const keep = entries.slice(entries.length - MAX_CONVERSATIONS);
|
|
102
|
-
return Object.fromEntries(keep);
|
|
103
153
|
}
|
|
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
|
-
};
|
|
154
|
+
function createConversationMigrationStore(params) {
|
|
155
|
+
return getMSTeamsRuntime().state.openKeyedStore({
|
|
156
|
+
namespace: CONVERSATION_MIGRATIONS_NAMESPACE,
|
|
157
|
+
maxEntries: 100,
|
|
158
|
+
env: resolveMSTeamsSqliteStateEnv(params)
|
|
159
|
+
});
|
|
119
160
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
const filePath = resolveMSTeamsStorePath({
|
|
161
|
+
function resolveLegacyStorePath(params) {
|
|
162
|
+
return resolveMSTeamsStorePath({
|
|
123
163
|
filename: STORE_FILENAME$2,
|
|
124
164
|
env: params?.env,
|
|
125
165
|
homedir: params?.homedir,
|
|
126
166
|
stateDir: params?.stateDir,
|
|
127
167
|
storePath: params?.storePath
|
|
128
168
|
});
|
|
129
|
-
|
|
169
|
+
}
|
|
170
|
+
function normalizeLegacyStore(value) {
|
|
171
|
+
if (value.version !== 1 || !value.conversations || typeof value.conversations !== "object" || Array.isArray(value.conversations)) return {
|
|
130
172
|
version: 1,
|
|
131
173
|
conversations: {}
|
|
132
174
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
function buildConversationStateKey(conversationId) {
|
|
178
|
+
return crypto.createHash("sha256").update(conversationId).digest("hex");
|
|
179
|
+
}
|
|
180
|
+
function prepareConversationReferenceForStorage(conversationId, reference) {
|
|
181
|
+
return {
|
|
182
|
+
...reference,
|
|
183
|
+
conversation: {
|
|
184
|
+
...reference.conversation,
|
|
185
|
+
id: conversationId
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function getStoredConversationId(reference) {
|
|
190
|
+
const rawId = reference.conversation?.id;
|
|
191
|
+
return rawId ? normalizeStoredConversationId(rawId) : null;
|
|
192
|
+
}
|
|
193
|
+
function createMSTeamsConversationStoreState(params) {
|
|
194
|
+
const ttlMs = params?.ttlMs ?? CONVERSATION_TTL_MS;
|
|
195
|
+
const conversationStore = createConversationStateStore(params);
|
|
196
|
+
const migrationStore = createConversationMigrationStore(params);
|
|
197
|
+
const legacyStorePath = resolveLegacyStorePath(params);
|
|
198
|
+
let legacyImportPromise = null;
|
|
199
|
+
const isExpired = (reference) => {
|
|
200
|
+
const lastSeenAt = parseStoredConversationTimestamp(reference.lastSeenAt);
|
|
201
|
+
return lastSeenAt != null && Date.now() - lastSeenAt > ttlMs;
|
|
202
|
+
};
|
|
203
|
+
const selectRetainedConversations = (conversations) => {
|
|
204
|
+
const retained = Object.entries(conversations).filter(([, reference]) => !isExpired(reference));
|
|
205
|
+
if (retained.length <= MAX_CONVERSATIONS) return retained;
|
|
206
|
+
retained.sort((a, b) => {
|
|
207
|
+
return (parseStoredConversationTimestamp(a[1].lastSeenAt) ?? 0) - (parseStoredConversationTimestamp(b[1].lastSeenAt) ?? 0) || a[0].localeCompare(b[0]);
|
|
208
|
+
});
|
|
209
|
+
return retained.slice(retained.length - MAX_CONVERSATIONS);
|
|
210
|
+
};
|
|
211
|
+
const importLegacyStore = async () => {
|
|
212
|
+
if (await migrationStore.lookup(LEGACY_JSON_MIGRATION_KEY)) return;
|
|
213
|
+
const { value, exists } = await readJsonFile(legacyStorePath, {
|
|
139
214
|
version: 1,
|
|
140
|
-
conversations:
|
|
141
|
-
};
|
|
215
|
+
conversations: {}
|
|
216
|
+
});
|
|
217
|
+
if (!exists) {
|
|
218
|
+
await migrationStore.register(LEGACY_JSON_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const legacy = normalizeLegacyStore(value);
|
|
222
|
+
for (const [rawConversationId, reference] of selectRetainedConversations(legacy.conversations)) {
|
|
223
|
+
const conversationId = normalizeStoredConversationId(rawConversationId);
|
|
224
|
+
if (!conversationId) continue;
|
|
225
|
+
await conversationStore.registerIfAbsent(buildConversationStateKey(conversationId), toPluginJsonValue(prepareConversationReferenceForStorage(conversationId, reference)));
|
|
226
|
+
}
|
|
227
|
+
await migrationStore.register(LEGACY_JSON_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
228
|
+
await fs.rm(legacyStorePath, { force: true }).catch(() => {});
|
|
229
|
+
};
|
|
230
|
+
const ensureLegacyImported = async () => {
|
|
231
|
+
legacyImportPromise ??= withMSTeamsSqliteMutationLock(params, CONVERSATION_LOCK_FILENAME, importLegacyStore);
|
|
232
|
+
await legacyImportPromise;
|
|
233
|
+
};
|
|
234
|
+
const lookupStored = async (conversationId) => {
|
|
235
|
+
const normalizedId = normalizeStoredConversationId(conversationId);
|
|
236
|
+
const value = await conversationStore.lookup(buildConversationStateKey(normalizedId));
|
|
237
|
+
if (!value) return null;
|
|
238
|
+
if (isExpired(value)) return null;
|
|
239
|
+
return value;
|
|
240
|
+
};
|
|
241
|
+
const entries = async () => {
|
|
242
|
+
await ensureLegacyImported();
|
|
243
|
+
const rows = await conversationStore.entries();
|
|
244
|
+
const kept = [];
|
|
245
|
+
for (const row of rows) {
|
|
246
|
+
if (isExpired(row.value)) continue;
|
|
247
|
+
const conversationId = getStoredConversationId(row.value);
|
|
248
|
+
if (conversationId) kept.push([conversationId, row.value]);
|
|
249
|
+
}
|
|
250
|
+
return kept;
|
|
251
|
+
};
|
|
252
|
+
const lookup = async (conversationId) => {
|
|
253
|
+
await ensureLegacyImported();
|
|
254
|
+
return await lookupStored(conversationId);
|
|
255
|
+
};
|
|
256
|
+
const register = async (conversationId, reference) => {
|
|
257
|
+
const normalizedId = normalizeStoredConversationId(conversationId);
|
|
258
|
+
await conversationStore.register(buildConversationStateKey(normalizedId), toPluginJsonValue(prepareConversationReferenceForStorage(normalizedId, reference)));
|
|
259
|
+
const rows = [];
|
|
260
|
+
for (const row of await conversationStore.entries()) {
|
|
261
|
+
if (isExpired(row.value)) {
|
|
262
|
+
await conversationStore.delete(row.key);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
rows.push(row);
|
|
266
|
+
}
|
|
267
|
+
if (rows.length <= MAX_CONVERSATIONS) return;
|
|
268
|
+
const sorted = rows.toSorted((a, b) => {
|
|
269
|
+
const aTs = parseStoredConversationTimestamp(a.value.lastSeenAt) ?? 0;
|
|
270
|
+
const bTs = parseStoredConversationTimestamp(b.value.lastSeenAt) ?? 0;
|
|
271
|
+
const aId = getStoredConversationId(a.value) ?? a.key;
|
|
272
|
+
const bId = getStoredConversationId(b.value) ?? b.key;
|
|
273
|
+
return aTs - bTs || aId.localeCompare(bId);
|
|
274
|
+
});
|
|
275
|
+
for (const row of sorted.slice(0, rows.length - MAX_CONVERSATIONS)) await conversationStore.delete(row.key);
|
|
142
276
|
};
|
|
143
277
|
const list = async () => {
|
|
144
|
-
|
|
145
|
-
return toConversationStoreEntries(Object.entries(store.conversations));
|
|
278
|
+
return toConversationStoreEntries(await entries());
|
|
146
279
|
};
|
|
147
280
|
const get = async (conversationId) => {
|
|
148
|
-
return
|
|
281
|
+
return await lookup(conversationId);
|
|
149
282
|
};
|
|
150
283
|
const findPreferredDmByUserId = async (id) => {
|
|
151
284
|
return findPreferredDmConversationByUserId(await list(), id);
|
|
152
285
|
};
|
|
153
286
|
const upsert = async (conversationId, reference) => {
|
|
154
287
|
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);
|
|
288
|
+
await withMSTeamsSqliteMutationLock(params, CONVERSATION_LOCK_FILENAME, async () => {
|
|
289
|
+
await importLegacyStore();
|
|
290
|
+
await register(normalizedId, mergeStoredConversationReference(await lookupStored(normalizedId) ?? void 0, reference, (/* @__PURE__ */ new Date()).toISOString()));
|
|
162
291
|
});
|
|
163
292
|
};
|
|
164
293
|
const remove = async (conversationId) => {
|
|
165
294
|
const normalizedId = normalizeStoredConversationId(conversationId);
|
|
166
|
-
return await
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
delete store.conversations[normalizedId];
|
|
170
|
-
await writeJsonFile(filePath, store);
|
|
171
|
-
return true;
|
|
295
|
+
return await withMSTeamsSqliteMutationLock(params, CONVERSATION_LOCK_FILENAME, async () => {
|
|
296
|
+
await importLegacyStore();
|
|
297
|
+
return await conversationStore.delete(buildConversationStateKey(normalizedId));
|
|
172
298
|
});
|
|
173
299
|
};
|
|
174
300
|
return {
|
|
@@ -183,8 +309,16 @@ function createMSTeamsConversationStoreFs(params) {
|
|
|
183
309
|
//#endregion
|
|
184
310
|
//#region extensions/msteams/src/polls.ts
|
|
185
311
|
const STORE_FILENAME$1 = "msteams-polls.json";
|
|
312
|
+
const POLLS_NAMESPACE = "polls";
|
|
313
|
+
const POLL_VOTE_BUCKETS_NAMESPACE = "poll-vote-buckets";
|
|
314
|
+
const POLL_MIGRATIONS_NAMESPACE = "poll-migrations";
|
|
315
|
+
const LEGACY_POLLS_MIGRATION_KEY = "msteams-polls-json-v1";
|
|
186
316
|
const MAX_POLLS = 1e3;
|
|
317
|
+
const SQLITE_MAX_POLL_ROWS = 2e3;
|
|
318
|
+
const POLL_VOTE_BUCKET_COUNT = 32;
|
|
319
|
+
const MAX_POLL_VOTE_BUCKET_ROWS = 1001 * POLL_VOTE_BUCKET_COUNT;
|
|
187
320
|
const POLL_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
321
|
+
const POLL_LOCK_FILENAME = "msteams-polls.sqlite.lock";
|
|
188
322
|
function normalizeChoiceValue(value) {
|
|
189
323
|
if (typeof value === "string") {
|
|
190
324
|
const trimmed = value.trim();
|
|
@@ -302,6 +436,27 @@ function buildMSTeamsPollCard(params) {
|
|
|
302
436
|
fallbackText: fallbackLines.join("\n")
|
|
303
437
|
};
|
|
304
438
|
}
|
|
439
|
+
function createPollStateStore(params) {
|
|
440
|
+
return getMSTeamsRuntime().state.openKeyedStore({
|
|
441
|
+
namespace: POLLS_NAMESPACE,
|
|
442
|
+
maxEntries: SQLITE_MAX_POLL_ROWS,
|
|
443
|
+
env: resolveMSTeamsSqliteStateEnv(params)
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function createPollVoteBucketStateStore(params) {
|
|
447
|
+
return getMSTeamsRuntime().state.openKeyedStore({
|
|
448
|
+
namespace: POLL_VOTE_BUCKETS_NAMESPACE,
|
|
449
|
+
maxEntries: MAX_POLL_VOTE_BUCKET_ROWS,
|
|
450
|
+
env: resolveMSTeamsSqliteStateEnv(params)
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
function createPollMigrationStore(params) {
|
|
454
|
+
return getMSTeamsRuntime().state.openKeyedStore({
|
|
455
|
+
namespace: POLL_MIGRATIONS_NAMESPACE,
|
|
456
|
+
maxEntries: 100,
|
|
457
|
+
env: resolveMSTeamsSqliteStateEnv(params)
|
|
458
|
+
});
|
|
459
|
+
}
|
|
305
460
|
function parseTimestamp(value) {
|
|
306
461
|
if (!value) return null;
|
|
307
462
|
const parsed = Date.parse(value);
|
|
@@ -314,69 +469,181 @@ function pruneExpired$1(polls) {
|
|
|
314
469
|
});
|
|
315
470
|
return Object.fromEntries(entries);
|
|
316
471
|
}
|
|
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);
|
|
472
|
+
function selectRetainedPolls(polls) {
|
|
473
|
+
const retained = Object.entries(pruneExpired$1(polls));
|
|
474
|
+
if (retained.length <= MAX_POLLS) return retained;
|
|
475
|
+
retained.sort((a, b) => {
|
|
476
|
+
return (parseTimestamp(a[1].updatedAt ?? a[1].createdAt) ?? 0) - (parseTimestamp(b[1].updatedAt ?? b[1].createdAt) ?? 0) || a[0].localeCompare(b[0]);
|
|
322
477
|
});
|
|
323
|
-
|
|
324
|
-
return Object.fromEntries(keep);
|
|
478
|
+
return retained.slice(retained.length - MAX_POLLS);
|
|
325
479
|
}
|
|
326
480
|
function normalizeMSTeamsPollSelections(poll, selections) {
|
|
327
481
|
const maxSelections = Math.max(1, poll.maxSelections);
|
|
328
482
|
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
483
|
return uniqueStrings(maxSelections > 1 ? mapped.slice(0, maxSelections) : mapped.slice(0, 1));
|
|
330
484
|
}
|
|
331
|
-
function
|
|
332
|
-
const
|
|
485
|
+
function createMSTeamsPollStoreState(params) {
|
|
486
|
+
const pollStore = createPollStateStore(params);
|
|
487
|
+
const voteBucketStore = createPollVoteBucketStateStore(params);
|
|
488
|
+
const migrationStore = createPollMigrationStore(params);
|
|
489
|
+
const legacyStorePath = resolveMSTeamsStorePath({
|
|
333
490
|
filename: STORE_FILENAME$1,
|
|
334
491
|
env: params?.env,
|
|
335
492
|
homedir: params?.homedir,
|
|
336
493
|
stateDir: params?.stateDir,
|
|
337
494
|
storePath: params?.storePath
|
|
338
495
|
});
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
496
|
+
let legacyImportPromise = null;
|
|
497
|
+
const splitPoll = (poll) => {
|
|
498
|
+
const { votes, ...metadata } = poll;
|
|
499
|
+
return {
|
|
500
|
+
metadata,
|
|
501
|
+
votes
|
|
502
|
+
};
|
|
503
|
+
};
|
|
504
|
+
const hashVote = (pollId, voterId) => {
|
|
505
|
+
return crypto.createHash("sha256").update(pollId).update("\0").update(voterId).digest("hex");
|
|
506
|
+
};
|
|
507
|
+
const buildPollStateKey = (pollId) => {
|
|
508
|
+
return crypto.createHash("sha256").update(pollId).digest("hex");
|
|
509
|
+
};
|
|
510
|
+
const selectVoteBucket = (pollId, voterId) => {
|
|
511
|
+
const bucket = Number.parseInt(hashVote(pollId, voterId).slice(0, 8), 16);
|
|
512
|
+
return String(bucket % POLL_VOTE_BUCKET_COUNT).padStart(4, "0");
|
|
513
|
+
};
|
|
514
|
+
const buildVoteBucketKey = (pollId, bucket) => {
|
|
515
|
+
return `${crypto.createHash("sha256").update(pollId).digest("hex")}:${bucket}`;
|
|
516
|
+
};
|
|
517
|
+
const readPollVotes = async (pollId) => {
|
|
518
|
+
const votes = {};
|
|
519
|
+
for (const row of await voteBucketStore.entries()) if (row.value.pollId === pollId) Object.assign(votes, row.value.votes);
|
|
520
|
+
return votes;
|
|
521
|
+
};
|
|
522
|
+
const deletePollVotes = async (pollId) => {
|
|
523
|
+
for (const row of await voteBucketStore.entries()) if (row.value.pollId === pollId) await voteBucketStore.delete(row.key);
|
|
342
524
|
};
|
|
343
|
-
const
|
|
344
|
-
const
|
|
525
|
+
const registerPollVotes = async (pollId, votes, updatedAt) => {
|
|
526
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const [voterId, selections] of Object.entries(votes)) {
|
|
528
|
+
const bucket = selectVoteBucket(pollId, voterId);
|
|
529
|
+
const bucketVotes = buckets.get(bucket) ?? {};
|
|
530
|
+
bucketVotes[voterId] = selections;
|
|
531
|
+
buckets.set(bucket, bucketVotes);
|
|
532
|
+
}
|
|
533
|
+
for (const [bucket, bucketVotes] of buckets) {
|
|
534
|
+
const key = buildVoteBucketKey(pollId, bucket);
|
|
535
|
+
const existing = await voteBucketStore.lookup(key);
|
|
536
|
+
await voteBucketStore.register(key, toPluginJsonValue({
|
|
537
|
+
pollId,
|
|
538
|
+
bucket,
|
|
539
|
+
votes: {
|
|
540
|
+
...bucketVotes,
|
|
541
|
+
...existing?.votes
|
|
542
|
+
},
|
|
543
|
+
updatedAt
|
|
544
|
+
}));
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
const registerPollVote = async (pollId, voterId, selections, updatedAt) => {
|
|
548
|
+
const bucket = selectVoteBucket(pollId, voterId);
|
|
549
|
+
const key = buildVoteBucketKey(pollId, bucket);
|
|
550
|
+
const existing = await voteBucketStore.lookup(key);
|
|
551
|
+
await voteBucketStore.register(key, toPluginJsonValue({
|
|
552
|
+
pollId,
|
|
553
|
+
bucket,
|
|
554
|
+
votes: {
|
|
555
|
+
...existing?.votes,
|
|
556
|
+
[voterId]: selections
|
|
557
|
+
},
|
|
558
|
+
updatedAt
|
|
559
|
+
}));
|
|
560
|
+
};
|
|
561
|
+
const reconstructPoll = async (metadata) => {
|
|
345
562
|
return {
|
|
346
|
-
|
|
347
|
-
|
|
563
|
+
...metadata,
|
|
564
|
+
votes: await readPollVotes(metadata.id)
|
|
348
565
|
};
|
|
349
566
|
};
|
|
350
|
-
const
|
|
351
|
-
await
|
|
567
|
+
const importLegacyStore = async () => {
|
|
568
|
+
if (await migrationStore.lookup(LEGACY_POLLS_MIGRATION_KEY)) return;
|
|
569
|
+
const { value, exists } = await readJsonFile(legacyStorePath, {
|
|
570
|
+
version: 1,
|
|
571
|
+
polls: {}
|
|
572
|
+
});
|
|
573
|
+
if (!exists) {
|
|
574
|
+
await migrationStore.register(LEGACY_POLLS_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const legacyPolls = value.version === 1 && value.polls && typeof value.polls === "object" && !Array.isArray(value.polls) ? value.polls : {};
|
|
578
|
+
for (const [pollId, poll] of selectRetainedPolls(legacyPolls)) {
|
|
579
|
+
if (!pollId) continue;
|
|
580
|
+
const { metadata, votes } = splitPoll(poll);
|
|
581
|
+
await pollStore.registerIfAbsent(buildPollStateKey(pollId), toPluginJsonValue(metadata));
|
|
582
|
+
await registerPollVotes(pollId, votes, poll.updatedAt ?? poll.createdAt);
|
|
583
|
+
}
|
|
584
|
+
await migrationStore.register(LEGACY_POLLS_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
585
|
+
await fs.rm(legacyStorePath, { force: true }).catch(() => {});
|
|
586
|
+
};
|
|
587
|
+
const ensureLegacyImported = async () => {
|
|
588
|
+
legacyImportPromise ??= withMSTeamsSqliteMutationLock(params, POLL_LOCK_FILENAME, importLegacyStore);
|
|
589
|
+
await legacyImportPromise;
|
|
590
|
+
};
|
|
591
|
+
const prunePollStoreToLimit = async () => {
|
|
592
|
+
const rows = [];
|
|
593
|
+
for (const row of await pollStore.entries()) {
|
|
594
|
+
if (!pruneExpired$1({ [row.key]: row.value })[row.key]) {
|
|
595
|
+
await pollStore.delete(row.key);
|
|
596
|
+
await deletePollVotes(row.value.id);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
rows.push(row);
|
|
600
|
+
}
|
|
601
|
+
if (rows.length <= MAX_POLLS) return;
|
|
602
|
+
const sorted = rows.toSorted((a, b) => {
|
|
603
|
+
return (parseTimestamp(a.value.updatedAt ?? a.value.createdAt) ?? 0) - (parseTimestamp(b.value.updatedAt ?? b.value.createdAt) ?? 0) || a.key.localeCompare(b.key);
|
|
604
|
+
});
|
|
605
|
+
for (const row of sorted.slice(0, rows.length - MAX_POLLS)) {
|
|
606
|
+
await pollStore.delete(row.key);
|
|
607
|
+
await deletePollVotes(row.value.id);
|
|
608
|
+
}
|
|
352
609
|
};
|
|
353
610
|
const createPoll = async (poll) => {
|
|
354
|
-
await
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
await
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
611
|
+
await withMSTeamsSqliteMutationLock(params, POLL_LOCK_FILENAME, async () => {
|
|
612
|
+
await importLegacyStore();
|
|
613
|
+
const { metadata, votes } = splitPoll(poll);
|
|
614
|
+
await pollStore.register(buildPollStateKey(poll.id), toPluginJsonValue(metadata));
|
|
615
|
+
await deletePollVotes(poll.id);
|
|
616
|
+
await registerPollVotes(poll.id, votes, poll.updatedAt ?? poll.createdAt);
|
|
617
|
+
await prunePollStoreToLimit();
|
|
361
618
|
});
|
|
362
619
|
};
|
|
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];
|
|
620
|
+
const getPoll = async (pollId) => {
|
|
621
|
+
await ensureLegacyImported();
|
|
622
|
+
const poll = await pollStore.lookup(buildPollStateKey(pollId));
|
|
369
623
|
if (!poll) return null;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
await
|
|
375
|
-
|
|
376
|
-
|
|
624
|
+
if (!pruneExpired$1({ [pollId]: poll })[pollId]) return null;
|
|
625
|
+
return await reconstructPoll(poll);
|
|
626
|
+
};
|
|
627
|
+
const recordVote = async (vote) => {
|
|
628
|
+
return await withMSTeamsSqliteMutationLock(params, POLL_LOCK_FILENAME, async () => {
|
|
629
|
+
await importLegacyStore();
|
|
630
|
+
const pollKey = buildPollStateKey(vote.pollId);
|
|
631
|
+
const poll = await pollStore.lookup(pollKey);
|
|
632
|
+
if (!poll) return null;
|
|
633
|
+
if (!pruneExpired$1({ [vote.pollId]: poll })[vote.pollId]) {
|
|
634
|
+
await pollStore.delete(pollKey);
|
|
635
|
+
await deletePollVotes(vote.pollId);
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
const normalized = normalizeMSTeamsPollSelections(await reconstructPoll(poll), vote.selections);
|
|
639
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
640
|
+
poll.updatedAt = updatedAt;
|
|
641
|
+
await pollStore.register(pollKey, toPluginJsonValue(poll));
|
|
642
|
+
await registerPollVote(vote.pollId, vote.voterId, normalized, updatedAt);
|
|
643
|
+
await prunePollStoreToLimit();
|
|
644
|
+
return await reconstructPoll(poll);
|
|
377
645
|
});
|
|
378
|
-
|
|
379
|
-
});
|
|
646
|
+
};
|
|
380
647
|
return {
|
|
381
648
|
createPoll,
|
|
382
649
|
getPoll,
|
|
@@ -1693,7 +1960,7 @@ async function resolveMSTeamsSendContext(params) {
|
|
|
1693
1960
|
if (!msteamsCfg?.enabled) throw new Error("msteams provider is not enabled");
|
|
1694
1961
|
const creds = resolveMSTeamsCredentials(msteamsCfg);
|
|
1695
1962
|
if (!creds) throw new Error("msteams credentials not configured");
|
|
1696
|
-
const store =
|
|
1963
|
+
const store = createMSTeamsConversationStoreState();
|
|
1697
1964
|
const recipient = parseRecipient(params.to);
|
|
1698
1965
|
const found = await findConversationReference({
|
|
1699
1966
|
...recipient,
|
|
@@ -2268,7 +2535,7 @@ async function probeMSTeams(cfg) {
|
|
|
2268
2535
|
if (cfg?.delegatedAuth?.enabled) try {
|
|
2269
2536
|
const tokens = loadDelegatedTokens();
|
|
2270
2537
|
if (tokens) {
|
|
2271
|
-
const isExpired = tokens.expiresAt
|
|
2538
|
+
const isExpired = !isFutureDateTimestampMs(tokens.expiresAt);
|
|
2272
2539
|
delegatedAuth = {
|
|
2273
2540
|
ok: !isExpired,
|
|
2274
2541
|
scopes: tokens.scopes,
|
|
@@ -2300,4 +2567,4 @@ async function probeMSTeams(cfg) {
|
|
|
2300
2567
|
}
|
|
2301
2568
|
}
|
|
2302
2569
|
//#endregion
|
|
2303
|
-
export { readJsonFile as C,
|
|
2570
|
+
export { readJsonFile as C, createMSTeamsConversationStoreState as S, writeJsonFile 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, withFileLock$1 as w, extractMSTeamsPollVote as x, uploadToConsentUrl as y };
|
package/dist/setup-plugin-api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { y as resolveMSTeamsCredentials } from "./errors-
|
|
2
|
-
import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-
|
|
1
|
+
import { y as resolveMSTeamsCredentials } from "./errors-HUt6we-U.js";
|
|
2
|
+
import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-BLAlJzog.js";
|
|
3
3
|
import { t as MSTeamsChannelConfigSchema } from "./config-schema-BL4qQZiA.js";
|
|
4
4
|
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
5
5
|
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as normalizeSecretInputString, _ as hasConfiguredMSTeamsCredentials, a as searchGraphUsers, b as saveDelegatedTokens, d as listTeamsByName, f as normalizeQuery, g as resolveGraphToken, r as formatUnknownError, u as listChannelsForTeam, y as resolveMSTeamsCredentials } from "./errors-
|
|
1
|
+
import { S as normalizeSecretInputString, _ as hasConfiguredMSTeamsCredentials, a as searchGraphUsers, b as saveDelegatedTokens, d as listTeamsByName, f as normalizeQuery, g as resolveGraphToken, r as formatUnknownError, u as listChannelsForTeam, y as resolveMSTeamsCredentials } from "./errors-HUt6we-U.js";
|
|
2
2
|
import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/allow-from";
|
|
3
3
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
4
|
import { DEFAULT_ACCOUNT_ID, createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicy, createTopLevelChannelGroupPolicySetter, mergeAllowFromEntries, splitSetupEntries } from "openclaw/plugin-sdk/setup";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { A as summarizeMapping, D as resolveDefaultGroupPolicy, E as resolveChannelMediaMaxBytes, M as getMSTeamsRuntime, N as getOptionalMSTeamsRuntime, _ as isDangerousNameMatchingEnabled, a as buildMediaPayload, b as logTypingFailure, c as createChannelMessageReplyPipeline, f as dispatchReplyFromConfigWithSettledDispatcher$1, l as createChannelPairingController, n as DEFAULT_WEBHOOK_MAX_BODY_BYTES, t as DEFAULT_ACCOUNT_ID, v as keepHttpServerTaskAlive, x as mergeAllowlist } from "./runtime-api-BlvMnDKz.js";
|
|
2
|
-
import { $ as safeFetchWithPolicy, B as estimateBase64DecodedBytes, E as loadMSTeamsSdkWithAuth, F as ATTACHMENT_TAG_RE, G as isLikelyImageAttachment, H as extractInlineImageCandidates, I as GRAPH_ROOT, J as normalizeContentType, K as isRecord$1, L as IMG_SRC_RE, M as tryNormalizeBotFrameworkServiceUrl, N as resolveMSTeamsSdkCloudOptions, O as ensureUserAgentHeader, Q as resolveRequestUrl, R as applyAuthorizationHeaderForUrl, T as createMSTeamsTokenProvider, U as inferPlaceholder, V as extractHtmlFromAttachment, W as isDownloadableAttachment, X as resolveAttachmentFetchPolicy, Y as readNestedString, Z as resolveMediaSsrfPolicy, et as safeHostForUrl, l as fetchGraphJson, n as formatMSTeamsSendErrorHint, q as isUrlAllowed, r as formatUnknownError, t as classifyMSTeamsSendError, tt as tryBuildGraphSharesUrlForSharedLink, w as createMSTeamsExpressAdapter, x as resolveMSTeamsStorePath, y as resolveMSTeamsCredentials, z as encodeGraphShareId } from "./errors-
|
|
3
|
-
import { d as resolveMSTeamsUserAllowlist, u as resolveMSTeamsChannelAllowlist } from "./setup-surface-
|
|
4
|
-
import { a as resolveMSTeamsReplyPolicy, i as resolveMSTeamsAllowlistMatch, o as resolveMSTeamsRouteConfig } from "./channel-
|
|
5
|
-
import { C as readJsonFile, S as
|
|
2
|
+
import { $ as safeFetchWithPolicy, B as estimateBase64DecodedBytes, E as loadMSTeamsSdkWithAuth, F as ATTACHMENT_TAG_RE, G as isLikelyImageAttachment, H as extractInlineImageCandidates, I as GRAPH_ROOT, J as normalizeContentType, K as isRecord$1, L as IMG_SRC_RE, M as tryNormalizeBotFrameworkServiceUrl, N as resolveMSTeamsSdkCloudOptions, O as ensureUserAgentHeader, Q as resolveRequestUrl, R as applyAuthorizationHeaderForUrl, T as createMSTeamsTokenProvider, U as inferPlaceholder, V as extractHtmlFromAttachment, W as isDownloadableAttachment, X as resolveAttachmentFetchPolicy, Y as readNestedString, Z as resolveMediaSsrfPolicy, et as safeHostForUrl, l as fetchGraphJson, n as formatMSTeamsSendErrorHint, q as isUrlAllowed, r as formatUnknownError, t as classifyMSTeamsSendError, tt as tryBuildGraphSharesUrlForSharedLink, w as createMSTeamsExpressAdapter, x as resolveMSTeamsStorePath, y as resolveMSTeamsCredentials, z as encodeGraphShareId } from "./errors-HUt6we-U.js";
|
|
3
|
+
import { d as resolveMSTeamsUserAllowlist, u as resolveMSTeamsChannelAllowlist } from "./setup-surface-BLAlJzog.js";
|
|
4
|
+
import { a as resolveMSTeamsReplyPolicy, i as resolveMSTeamsAllowlistMatch, o as resolveMSTeamsRouteConfig } from "./channel-I5mMZ1Ju.js";
|
|
5
|
+
import { C as readJsonFile, S as createMSTeamsConversationStoreState, T as writeJsonFile, _ as buildFileInfoCard, b as createMSTeamsPollStoreState, c as renderReplyPayloadsToMessages, d as withRevokedProxyFallback, f as resolveGraphChatId, g as removePendingUploadFs, h as getPendingUploadFs, l as sendMSTeamsMessages, m as removePendingUpload, p as getPendingUpload, s as buildConversationReference, u as sendMSTeamsActivityWithReference, v as parseFileConsentInvoke, w as withFileLock, x as extractMSTeamsPollVote, y as uploadToConsentUrl } from "./probe-MGBfnHCR.js";
|
|
6
6
|
import { formatAllowlistMatchMeta } from "openclaw/plugin-sdk/allow-from";
|
|
7
7
|
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelProgressDraftGate, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, normalizeChannelProgressDraftLineIdentity, resolveChannelPreviewStreamMode, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
|
|
8
8
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
@@ -10,10 +10,11 @@ import { parseMediaContentLength, saveResponseMedia } from "openclaw/plugin-sdk/
|
|
|
10
10
|
import { buildChannelInboundEventContext, dispatchReplyFromConfigWithSettledDispatcher, hasFinalInboundReplyDispatch, logInboundDrop, resolveInboundMentionDecision, resolveInboundReplyDispatchCounts, resolveInboundSessionEnvelopeContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
11
11
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
12
12
|
import path from "node:path";
|
|
13
|
+
import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
13
14
|
import { appendRegularFile } from "openclaw/plugin-sdk/security-runtime";
|
|
15
|
+
import fs from "node:fs/promises";
|
|
14
16
|
import { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
|
|
15
17
|
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
|
16
|
-
import fs from "node:fs/promises";
|
|
17
18
|
import { channelIngressRoutes, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
18
19
|
import { filterSupplementalContextItems, resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
19
20
|
import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
@@ -1327,6 +1328,10 @@ function buildMSTeamsMediaPayload(mediaList) {
|
|
|
1327
1328
|
//#region extensions/msteams/src/graph-thread.ts
|
|
1328
1329
|
const teamGroupIdCache = /* @__PURE__ */ new Map();
|
|
1329
1330
|
const CACHE_TTL_MS = 600 * 1e3;
|
|
1331
|
+
function resolveTeamGroupIdCacheExpiresAt(nowRaw = Date.now()) {
|
|
1332
|
+
const now = asDateTimestampMs(nowRaw);
|
|
1333
|
+
return now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(CACHE_TTL_MS, { nowMs: now });
|
|
1334
|
+
}
|
|
1330
1335
|
/**
|
|
1331
1336
|
* Strip HTML tags from Teams message content, preserving @mention display names.
|
|
1332
1337
|
* Teams wraps mentions in <at>Name</at> tags.
|
|
@@ -1343,15 +1348,21 @@ function stripHtmlFromTeamsMessage(html) {
|
|
|
1343
1348
|
*/
|
|
1344
1349
|
async function resolveTeamGroupId(token, conversationTeamId) {
|
|
1345
1350
|
const cached = teamGroupIdCache.get(conversationTeamId);
|
|
1346
|
-
if (cached
|
|
1351
|
+
if (cached) {
|
|
1352
|
+
const now = asDateTimestampMs(Date.now());
|
|
1353
|
+
const expiresAt = asDateTimestampMs(cached.expiresAt);
|
|
1354
|
+
if (now !== void 0 && expiresAt !== void 0 && expiresAt > now) return cached.groupId;
|
|
1355
|
+
teamGroupIdCache.delete(conversationTeamId);
|
|
1356
|
+
}
|
|
1347
1357
|
try {
|
|
1348
1358
|
const groupId = (await fetchGraphJson({
|
|
1349
1359
|
token,
|
|
1350
1360
|
path: `/teams/${encodeURIComponent(conversationTeamId)}?$select=id`
|
|
1351
1361
|
})).id ?? conversationTeamId;
|
|
1352
|
-
|
|
1362
|
+
const expiresAt = resolveTeamGroupIdCacheExpiresAt();
|
|
1363
|
+
if (expiresAt !== void 0) teamGroupIdCache.set(conversationTeamId, {
|
|
1353
1364
|
groupId,
|
|
1354
|
-
expiresAt
|
|
1365
|
+
expiresAt
|
|
1355
1366
|
});
|
|
1356
1367
|
return groupId;
|
|
1357
1368
|
} catch {
|
|
@@ -1424,6 +1435,10 @@ function touchLru(map, key, value, max) {
|
|
|
1424
1435
|
function buildParentCacheKey(groupId, channelId, parentId) {
|
|
1425
1436
|
return `${groupId}\u0000${channelId}\u0000${parentId}`;
|
|
1426
1437
|
}
|
|
1438
|
+
function resolveParentCacheExpiresAt(nowRaw) {
|
|
1439
|
+
const nowMs = asDateTimestampMs(nowRaw);
|
|
1440
|
+
return nowMs === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(PARENT_CACHE_TTL_MS, { nowMs });
|
|
1441
|
+
}
|
|
1427
1442
|
/**
|
|
1428
1443
|
* Fetch a channel parent message with an LRU+TTL cache.
|
|
1429
1444
|
*
|
|
@@ -1432,17 +1447,20 @@ function buildParentCacheKey(groupId, channelId, parentId) {
|
|
|
1432
1447
|
*/
|
|
1433
1448
|
async function fetchParentMessageCached(token, groupId, channelId, parentId, fetchParent = fetchChannelMessage) {
|
|
1434
1449
|
const key = buildParentCacheKey(groupId, channelId, parentId);
|
|
1435
|
-
const now = Date.now();
|
|
1450
|
+
const now = asDateTimestampMs(Date.now());
|
|
1436
1451
|
const cached = parentCache.get(key);
|
|
1437
|
-
|
|
1452
|
+
const cachedExpiresAt = cached ? asDateTimestampMs(cached.expiresAt) : void 0;
|
|
1453
|
+
if (cached && now !== void 0 && cachedExpiresAt !== void 0 && cachedExpiresAt > now) {
|
|
1438
1454
|
parentCache.delete(key);
|
|
1439
1455
|
parentCache.set(key, cached);
|
|
1440
1456
|
return cached.message;
|
|
1441
1457
|
}
|
|
1458
|
+
if (cached) parentCache.delete(key);
|
|
1442
1459
|
const message = await fetchParent(token, groupId, channelId, parentId);
|
|
1443
|
-
|
|
1460
|
+
const expiresAt = resolveParentCacheExpiresAt(Date.now());
|
|
1461
|
+
if (expiresAt !== void 0) touchLru(parentCache, key, {
|
|
1444
1462
|
message,
|
|
1445
|
-
expiresAt
|
|
1463
|
+
expiresAt
|
|
1446
1464
|
}, PARENT_CACHE_MAX);
|
|
1447
1465
|
return message;
|
|
1448
1466
|
}
|
|
@@ -3698,8 +3716,8 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3698
3716
|
const MB = 1024 * 1024;
|
|
3699
3717
|
const agentDefaults = cfg.agents?.defaults;
|
|
3700
3718
|
const mediaMaxBytes = typeof agentDefaults?.mediaMaxMb === "number" && agentDefaults.mediaMaxMb > 0 ? Math.floor(agentDefaults.mediaMaxMb * MB) : 8 * MB;
|
|
3701
|
-
const conversationStore = opts.conversationStore ??
|
|
3702
|
-
const pollStore = opts.pollStore ??
|
|
3719
|
+
const conversationStore = opts.conversationStore ?? createMSTeamsConversationStoreState();
|
|
3720
|
+
const pollStore = opts.pollStore ?? createMSTeamsPollStoreState();
|
|
3703
3721
|
log.info(`starting provider (port ${port})`);
|
|
3704
3722
|
const express = await import("express");
|
|
3705
3723
|
const expressApp = express.default();
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/msteams",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.30-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/msteams",
|
|
9
|
-
"version": "2026.5.
|
|
9
|
+
"version": "2026.5.30-beta.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@azure/identity": "4.13.1",
|
|
12
12
|
"@microsoft/teams.api": "2.0.12",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"typebox": "1.1.38"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"openclaw": ">=2026.5.
|
|
18
|
+
"openclaw": ">=2026.5.30-beta.1"
|
|
19
19
|
},
|
|
20
20
|
"peerDependenciesMeta": {
|
|
21
21
|
"openclaw": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/msteams",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.30-beta.1",
|
|
4
4
|
"description": "OpenClaw Microsoft Teams channel plugin for bot conversations.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"typebox": "1.1.38"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"openclaw": ">=2026.5.
|
|
18
|
+
"openclaw": ">=2026.5.30-beta.1"
|
|
19
19
|
},
|
|
20
20
|
"peerDependenciesMeta": {
|
|
21
21
|
"openclaw": {
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"minHostVersion": ">=2026.4.10"
|
|
52
52
|
},
|
|
53
53
|
"compat": {
|
|
54
|
-
"pluginApi": ">=2026.5.
|
|
54
|
+
"pluginApi": ">=2026.5.30-beta.1"
|
|
55
55
|
},
|
|
56
56
|
"build": {
|
|
57
|
-
"openclawVersion": "2026.5.
|
|
57
|
+
"openclawVersion": "2026.5.30-beta.1"
|
|
58
58
|
},
|
|
59
59
|
"release": {
|
|
60
60
|
"publishToClawHub": true,
|