@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 CHANGED
@@ -1,3 +1,3 @@
1
- import { i as msteamsSetupAdapter, n as openDelegatedOAuthUrl, r as createMSTeamsSetupWizardBase, t as msteamsSetupWizard } from "./setup-surface-C9IApOv3.js";
2
- import { t as msteamsPlugin } from "./channel-2_L55KDI.js";
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-DZGI_mqq.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-C9IApOv3.js";
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-CxxY1xk6.js"), "msTeamsChannelRuntime");
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-D_rcW2Zm.js");
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-2_L55KDI.js";
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-DZGI_mqq.js";
3
- import { n as MSTEAMS_PRESENTATION_CAPABILITIES, r as buildMSTeamsPresentationCard } from "./channel-2_L55KDI.js";
4
- import { S as createMSTeamsConversationStoreFs, a as sendMessageMSTeams, b as createMSTeamsPollStoreFs, i as sendAdaptiveCardMSTeams, n as deleteMessageMSTeams, o as sendPollMSTeams, r as editMessageMSTeams, t as probeMSTeams } from "./probe-BoUA5GpA.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-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 createMSTeamsConversationStoreFs().findPreferredDmByUserId(cleaned);
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 = resolveOutboundSendDep(deps, "msteams") ?? ((to, text, opts) => sendMessageMSTeams({
584
+ const send = resolveMSTeamsMediaSend({
568
585
  cfg,
569
- to,
570
- text,
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 = resolveOutboundSendDep(deps, "msteams") ?? ((to, text) => sendMessageMSTeams({
600
+ const send = resolveMSTeamsTextSend({
588
601
  cfg,
589
- to,
590
- text
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 (resolveOutboundSendDep(deps, "msteams") ?? ((to, text) => sendMessageMSTeams({
614
+ return await resolveMSTeamsTextSend({
603
615
  cfg,
604
- to,
605
- text
606
- })))(to, text);
616
+ deps
617
+ })(to, text);
607
618
  },
608
619
  sendMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, mediaReadFile, deps }) => {
609
- return await (resolveOutboundSendDep(deps, "msteams") ?? ((to, text, opts) => sendMessageMSTeams({
620
+ return await resolveMSTeamsMediaSend({
610
621
  cfg,
611
- to,
612
- text,
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 createMSTeamsPollStoreFs().createPoll({
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 > Date.now()) return tokens.accessToken;
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-DZGI_mqq.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-HUt6we-U.js";
3
3
  import { c as createMSTeamsHttpError } from "./oauth.token-BKzEFepQ.js";
4
- import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel-2_L55KDI.js";
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/conversation-store-fs.ts
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
- function pruneToLimit$2(conversations) {
96
- const entries = Object.entries(conversations);
97
- if (entries.length <= MAX_CONVERSATIONS) return conversations;
98
- entries.sort((a, b) => {
99
- return (parseStoredConversationTimestamp(a[1].lastSeenAt) ?? 0) - (parseStoredConversationTimestamp(b[1].lastSeenAt) ?? 0);
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 pruneExpired$2(conversations, nowMs, ttlMs) {
105
- let removed = false;
106
- const kept = {};
107
- for (const [conversationId, reference] of Object.entries(conversations)) {
108
- const lastSeenAt = parseStoredConversationTimestamp(reference.lastSeenAt);
109
- if (lastSeenAt != null && nowMs - lastSeenAt > ttlMs) {
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 createMSTeamsConversationStoreFs(params) {
121
- const ttlMs = params?.ttlMs ?? CONVERSATION_TTL_MS;
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
- const empty = {
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
- const readStore = async () => {
134
- const { value } = await readJsonFile(filePath, empty);
135
- if (value.version !== 1 || !value.conversations || typeof value.conversations !== "object" || Array.isArray(value.conversations)) return empty;
136
- const nowMs = Date.now();
137
- const pruned = pruneExpired$2(value.conversations, nowMs, ttlMs).conversations;
138
- return {
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: pruneToLimit$2(pruned)
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
- const store = await readStore();
145
- return toConversationStoreEntries(Object.entries(store.conversations));
278
+ return toConversationStoreEntries(await entries());
146
279
  };
147
280
  const get = async (conversationId) => {
148
- return (await readStore()).conversations[normalizeStoredConversationId(conversationId)] ?? null;
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 withFileLock$1(filePath, empty, async () => {
156
- const store = await readStore();
157
- store.conversations[normalizedId] = mergeStoredConversationReference(store.conversations[normalizedId], reference, (/* @__PURE__ */ new Date()).toISOString());
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 withFileLock$1(filePath, empty, async () => {
167
- const store = await readStore();
168
- if (!(normalizedId in store.conversations)) return false;
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 pruneToLimit$1(polls) {
318
- const entries = Object.entries(polls);
319
- if (entries.length <= MAX_POLLS) return polls;
320
- entries.sort((a, b) => {
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
- const keep = entries.slice(entries.length - MAX_POLLS);
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 createMSTeamsPollStoreFs(params) {
332
- const filePath = resolveMSTeamsStorePath({
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
- const empty = {
340
- version: 1,
341
- polls: {}
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 readStore = async () => {
344
- const { value } = await readJsonFile(filePath, empty);
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
- version: 1,
347
- polls: pruneToLimit$1(pruneExpired$1(value.polls ?? {}))
563
+ ...metadata,
564
+ votes: await readPollVotes(metadata.id)
348
565
  };
349
566
  };
350
- const writeStore = async (data) => {
351
- await writeJsonFile(filePath, data);
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 withFileLock$1(filePath, empty, async () => {
355
- const data = await readStore();
356
- data.polls[poll.id] = poll;
357
- await writeStore({
358
- version: 1,
359
- polls: pruneToLimit$1(data.polls)
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) => await withFileLock$1(filePath, empty, async () => {
364
- return (await readStore()).polls[pollId] ?? null;
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
- const normalized = normalizeMSTeamsPollSelections(poll, params.selections);
371
- poll.votes[params.voterId] = normalized;
372
- poll.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
373
- data.polls[poll.id] = poll;
374
- await writeStore({
375
- version: 1,
376
- polls: pruneToLimit$1(data.polls)
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
- return poll;
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 = createMSTeamsConversationStoreFs();
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 <= Date.now();
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, createMSTeamsConversationStoreFs as S, writeJsonFile as T, buildFileInfoCard as _, sendMessageMSTeams as a, createMSTeamsPollStoreFs 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 };
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 };
@@ -1,5 +1,5 @@
1
- import { y as resolveMSTeamsCredentials } from "./errors-DZGI_mqq.js";
2
- import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-C9IApOv3.js";
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-DZGI_mqq.js";
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-DZGI_mqq.js";
3
- import { d as resolveMSTeamsUserAllowlist, u as resolveMSTeamsChannelAllowlist } from "./setup-surface-C9IApOv3.js";
4
- import { a as resolveMSTeamsReplyPolicy, i as resolveMSTeamsAllowlistMatch, o as resolveMSTeamsRouteConfig } from "./channel-2_L55KDI.js";
5
- import { C as readJsonFile, S as createMSTeamsConversationStoreFs, T as writeJsonFile, _ as buildFileInfoCard, b as createMSTeamsPollStoreFs, 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-BoUA5GpA.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-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 && cached.expiresAt > Date.now()) return cached.groupId;
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
- teamGroupIdCache.set(conversationTeamId, {
1362
+ const expiresAt = resolveTeamGroupIdCacheExpiresAt();
1363
+ if (expiresAt !== void 0) teamGroupIdCache.set(conversationTeamId, {
1353
1364
  groupId,
1354
- expiresAt: Date.now() + CACHE_TTL_MS
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
- if (cached && cached.expiresAt > now) {
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
- touchLru(parentCache, key, {
1460
+ const expiresAt = resolveParentCacheExpiresAt(Date.now());
1461
+ if (expiresAt !== void 0) touchLru(parentCache, key, {
1444
1462
  message,
1445
- expiresAt: now + PARENT_CACHE_TTL_MS
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 ?? createMSTeamsConversationStoreFs();
3702
- const pollStore = opts.pollStore ?? createMSTeamsPollStoreFs();
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();
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/msteams",
3
- "version": "2026.5.28",
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.28",
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.28"
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.28",
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.28"
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.28"
54
+ "pluginApi": ">=2026.5.30-beta.1"
55
55
  },
56
56
  "build": {
57
- "openclawVersion": "2026.5.28"
57
+ "openclawVersion": "2026.5.30-beta.1"
58
58
  },
59
59
  "release": {
60
60
  "publishToClawHub": true,