@openclaw/msteams 2026.5.30-beta.1 → 2026.5.31-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -1,3 +1,3 @@
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";
1
+ import { i as msteamsSetupAdapter, n as openDelegatedOAuthUrl, r as createMSTeamsSetupWizardBase, t as msteamsSetupWizard } from "./setup-surface-B7a1pD-K.js";
2
+ import { t as msteamsPlugin } from "./channel-CqRTzeBc.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-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";
2
+ import { y as resolveMSTeamsCredentials } from "./errors-Dpn8B05h.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-B7a1pD-K.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";
@@ -283,7 +283,7 @@ function buildMSTeamsPresentationCard(params) {
283
283
  //#endregion
284
284
  //#region extensions/msteams/src/session-route.ts
285
285
  function resolveMSTeamsOutboundSessionRoute(params) {
286
- let trimmed = stripChannelTargetPrefix(params.target, "msteams", "teams");
286
+ const trimmed = stripChannelTargetPrefix(params.target, "msteams", "teams");
287
287
  if (!trimmed) return null;
288
288
  const isUser = normalizeLowercaseStringOrEmpty(trimmed).startsWith("user:");
289
289
  const rawId = stripTargetKindPrefix(trimmed);
@@ -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-Cg8XWuDW.js"), "msTeamsChannelRuntime");
333
+ const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-C38epVZr.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-hH0j_Pt1.js");
1119
+ const { monitorMSTeamsProvider } = await import("./src-B6CFMcvV.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-I5mMZ1Ju.js";
1
+ import { t as msteamsPlugin } from "./channel-CqRTzeBc.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-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";
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-Dpn8B05h.js";
3
+ import { n as MSTEAMS_PRESENTATION_CAPABILITIES, r as buildMSTeamsPresentationCard } from "./channel-CqRTzeBc.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-DMNefdBZ.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";
@@ -473,7 +473,7 @@ async function listChannelsMSTeams(params) {
473
473
  description: ch.description,
474
474
  membershipType: ch.membershipType
475
475
  })),
476
- truncated: !!nextPath
476
+ truncated: Boolean(nextPath)
477
477
  };
478
478
  }
479
479
  /**
@@ -0,0 +1,171 @@
1
+ import path from "node:path";
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs/promises";
4
+ import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
5
+ //#region extensions/msteams/doctor-contract-api.ts
6
+ const LEARNINGS_NAMESPACE = "feedback-learnings";
7
+ const MAX_LEARNING_ENTRIES = 1e4;
8
+ function encodeSessionKey(sessionKey) {
9
+ return Buffer.from(sessionKey, "utf8").toString("base64url");
10
+ }
11
+ function learningStoreKey(storePath, sessionKey) {
12
+ return crypto.createHash("sha256").update(`${storePath}\0${sessionKey}`, "utf8").digest("hex");
13
+ }
14
+ function decodeSessionKey(fileStem) {
15
+ try {
16
+ const decoded = Buffer.from(fileStem, "base64url").toString("utf8");
17
+ return encodeSessionKey(decoded) === fileStem && decoded.trim() ? decoded : null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function resolveLearningSessionKey(fileStem) {
23
+ return decodeSessionKey(fileStem);
24
+ }
25
+ function legacySanitizeSessionKey(sessionKey) {
26
+ return sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
27
+ }
28
+ async function listKnownSessionKeys(storePath) {
29
+ const candidates = [storePath, path.join(storePath, "sessions.json")];
30
+ for (const candidate of candidates) try {
31
+ const parsed = JSON.parse(await fs.readFile(candidate, "utf8"));
32
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
33
+ const sessions = parsed.sessions && typeof parsed.sessions === "object" && !Array.isArray(parsed.sessions) ? parsed.sessions : parsed;
34
+ return Object.keys(sessions).filter((key) => key.trim());
35
+ } catch {}
36
+ return [];
37
+ }
38
+ function resolveLegacySanitizedSessionKey(fileStem, knownSessionKeys) {
39
+ const matches = knownSessionKeys.filter((sessionKey) => legacySanitizeSessionKey(sessionKey) === fileStem);
40
+ return matches.length === 1 ? matches[0] : null;
41
+ }
42
+ function listAgentIds(config) {
43
+ const ids = new Set(["main"]);
44
+ for (const agent of config.agents?.list ?? []) if (typeof agent.id === "string" && agent.id.trim()) ids.add(agent.id.trim());
45
+ return [...ids];
46
+ }
47
+ function listCandidateStorePaths(params) {
48
+ const paths = /* @__PURE__ */ new Set();
49
+ paths.add(resolveStorePath(params.config.session?.store, { env: params.env }));
50
+ for (const agentId of listAgentIds(params.config)) paths.add(resolveStorePath(params.config.session?.store, {
51
+ agentId,
52
+ env: params.env
53
+ }));
54
+ return [...paths];
55
+ }
56
+ async function fileExists(filePath) {
57
+ try {
58
+ return (await fs.stat(filePath)).isFile();
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ async function listLegacyLearningFiles(storePath) {
64
+ let entries = [];
65
+ try {
66
+ entries = await fs.readdir(storePath, { withFileTypes: true });
67
+ } catch {
68
+ return [];
69
+ }
70
+ const suffix = ".learnings.json";
71
+ const knownSessionKeys = await listKnownSessionKeys(storePath);
72
+ const files = [];
73
+ for (const entry of entries) {
74
+ if (!entry.isFile() || !entry.name.endsWith(suffix)) continue;
75
+ const fileStem = entry.name.slice(0, -15);
76
+ const sessionKey = resolveLearningSessionKey(fileStem) ?? resolveLegacySanitizedSessionKey(fileStem, knownSessionKeys);
77
+ const filePath = path.join(storePath, entry.name);
78
+ try {
79
+ const parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
80
+ if (Array.isArray(parsed)) {
81
+ const learnings = parsed.filter((item) => typeof item === "string");
82
+ if (learnings.length > 0) files.push({
83
+ storePath,
84
+ sessionKey,
85
+ filePath,
86
+ learnings: learnings.slice(-10)
87
+ });
88
+ }
89
+ } catch {}
90
+ }
91
+ return files;
92
+ }
93
+ async function archiveLegacySource(params) {
94
+ const archivedPath = `${params.filePath}.migrated`;
95
+ if (await fileExists(archivedPath)) {
96
+ params.warnings.push(`Left migrated Microsoft Teams feedback-learning source in place because ${archivedPath} already exists`);
97
+ return;
98
+ }
99
+ try {
100
+ await fs.rename(params.filePath, archivedPath);
101
+ params.changes.push(`Archived Microsoft Teams feedback-learning legacy source -> ${archivedPath}`);
102
+ } catch (err) {
103
+ params.warnings.push(`Failed archiving Microsoft Teams feedback-learning legacy source: ${String(err)}`);
104
+ }
105
+ }
106
+ function mergeLearnings(legacy, existing) {
107
+ const seen = /* @__PURE__ */ new Set();
108
+ const merged = [];
109
+ for (const learning of [...legacy, ...existing?.learnings ?? []]) {
110
+ if (seen.has(learning)) continue;
111
+ seen.add(learning);
112
+ merged.push(learning);
113
+ }
114
+ return merged.slice(-10);
115
+ }
116
+ const stateMigrations = [{
117
+ id: "msteams-feedback-learnings-json-to-plugin-state",
118
+ label: "Microsoft Teams feedback learnings",
119
+ async detectLegacyState(params) {
120
+ const files = (await Promise.all(listCandidateStorePaths(params).map((storePath) => listLegacyLearningFiles(storePath)))).flat();
121
+ if (files.length === 0) return null;
122
+ return { preview: [`- Microsoft Teams feedback learnings: ${files.length} ${files.length === 1 ? "file" : "files"} -> plugin state (${LEARNINGS_NAMESPACE})`] };
123
+ },
124
+ async migrateLegacyState(params) {
125
+ const changes = [];
126
+ const warnings = [];
127
+ const files = (await Promise.all(listCandidateStorePaths(params).map((storePath) => listLegacyLearningFiles(storePath)))).flat();
128
+ const store = params.context.openPluginStateKeyedStore({
129
+ namespace: LEARNINGS_NAMESPACE,
130
+ maxEntries: MAX_LEARNING_ENTRIES
131
+ });
132
+ const existingEntries = await store.entries();
133
+ const existingKeys = new Set(existingEntries.map((entry) => entry.key));
134
+ const importableFiles = files.filter((file) => file.sessionKey);
135
+ const missingKeys = new Set(importableFiles.map((file) => learningStoreKey(file.storePath, file.sessionKey ?? "")).filter((key) => !existingKeys.has(key)));
136
+ if (missingKeys.size > MAX_LEARNING_ENTRIES - existingKeys.size) {
137
+ warnings.push(`Skipped Microsoft Teams feedback-learning migration because plugin state has room for ${MAX_LEARNING_ENTRIES - existingKeys.size} of ${missingKeys.size} missing entries; left legacy sources in place`);
138
+ return {
139
+ changes,
140
+ warnings
141
+ };
142
+ }
143
+ let imported = 0;
144
+ for (const file of files) {
145
+ if (!file.sessionKey) {
146
+ warnings.push(`Left Microsoft Teams feedback-learning source in place because its legacy filename cannot be mapped to a session key: ${file.filePath}`);
147
+ continue;
148
+ }
149
+ const key = learningStoreKey(file.storePath, file.sessionKey);
150
+ const existing = await store.lookup(key);
151
+ await store.register(key, {
152
+ sessionKey: existing?.sessionKey ?? file.sessionKey,
153
+ learnings: mergeLearnings(file.learnings, existing),
154
+ updatedAt: Date.now()
155
+ });
156
+ imported++;
157
+ await archiveLegacySource({
158
+ filePath: file.filePath,
159
+ changes,
160
+ warnings
161
+ });
162
+ }
163
+ if (imported > 0) changes.unshift(`Migrated ${imported} Microsoft Teams feedback-learning ${imported === 1 ? "entry" : "entries"} -> plugin state`);
164
+ return {
165
+ changes,
166
+ warnings
167
+ };
168
+ }
169
+ }];
170
+ //#endregion
171
+ export { stateMigrations };
@@ -296,7 +296,7 @@ function resolveAuthAllowedHosts(input) {
296
296
  }
297
297
  function isMockFetchFn(fetchFn) {
298
298
  const candidate = fetchFn;
299
- return Boolean(candidate.mock || Object.prototype.hasOwnProperty.call(candidate, "_isMockFunction"));
299
+ return Boolean(candidate.mock || Object.hasOwn(candidate, "_isMockFunction"));
300
300
  }
301
301
  function resolveGuardedFetchImpl(params) {
302
302
  if (!params.fetchFn) return;
@@ -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-HUt6we-U.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-Dpn8B05h.js";
3
3
  import { c as createMSTeamsHttpError } from "./oauth.token-BKzEFepQ.js";
4
- import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel-I5mMZ1Ju.js";
4
+ import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel-CqRTzeBc.js";
5
5
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
6
6
  import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
7
7
  import { withFileLock } from "openclaw/plugin-sdk/file-lock";
@@ -12,7 +12,7 @@ import { isPrivateIpAddress } from "openclaw/plugin-sdk/ssrf-policy";
12
12
  import path from "node:path";
13
13
  import { isFutureDateTimestampMs, parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
14
14
  import { pathExists } from "openclaw/plugin-sdk/security-runtime";
15
- import crypto from "node:crypto";
15
+ import crypto, { createHash } from "node:crypto";
16
16
  import fs from "node:fs/promises";
17
17
  import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
18
18
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
@@ -106,7 +106,8 @@ function resolveMSTeamsSqliteStateEnv(options) {
106
106
  };
107
107
  }
108
108
  function toPluginJsonValue(value) {
109
- return JSON.parse(JSON.stringify(value));
109
+ const serialized = JSON.stringify(value);
110
+ return JSON.parse(serialized);
110
111
  }
111
112
  function resolveMSTeamsSqliteStateDir(options) {
112
113
  return resolveStateDirOverride(options) ?? getMSTeamsRuntime().state.resolveStateDir(options?.env ?? process.env, options?.homedir);
@@ -786,31 +787,24 @@ async function uploadToConsentUrl(params) {
786
787
  }
787
788
  //#endregion
788
789
  //#region extensions/msteams/src/pending-uploads-fs.ts
789
- /**
790
- * Filesystem-backed pending upload store for the FileConsentCard flow.
791
- *
792
- * The CLI `message send --media` path runs in a different process from the
793
- * gateway's bot monitor that receives the `fileConsent/invoke` callback.
794
- * An in-memory `pending-uploads.ts` store cannot bridge those processes, so
795
- * when the user clicks "Allow" the monitor handler's lookup misses and the
796
- * user sees "card action not supported".
797
- *
798
- * This FS store persists pending uploads to a JSON file (with the file buffer
799
- * base64-encoded) so any process that shares the OpenClaw state dir can read
800
- * them back. The in-memory store in `pending-uploads.ts` is still the fast
801
- * path for same-process flows (for example the messenger reply path); this FS
802
- * store is a cross-process fallback.
803
- */
804
790
  /** TTL for persisted pending uploads (matches in-memory store). */
805
791
  const PENDING_UPLOAD_TTL_MS$1 = 300 * 1e3;
806
792
  /** Cap to avoid unbounded growth if a process crashes mid-flow. */
807
793
  const MAX_PENDING_UPLOADS = 100;
794
+ const MAX_CHUNKS_PER_UPLOAD = 3072;
795
+ const MAX_PENDING_UPLOAD_CHUNK_ROWS = 45e3;
796
+ const RAW_CHUNK_BYTES = 36 * 1024;
797
+ const PENDING_UPLOAD_META_MAX_ENTRIES = 200;
808
798
  const STORE_FILENAME = "msteams-pending-uploads.json";
799
+ const PENDING_UPLOAD_META_NAMESPACE = "pending-uploads";
800
+ const PENDING_UPLOAD_CHUNKS_NAMESPACE = "pending-upload-chunks";
801
+ const PENDING_UPLOAD_MIGRATIONS_NAMESPACE = "pending-upload-migrations";
802
+ const PENDING_UPLOAD_LOCK_FILENAME = "msteams-pending-uploads.sqlite.lock";
809
803
  const empty = {
810
804
  version: 1,
811
805
  uploads: {}
812
806
  };
813
- function resolveFilePath(options) {
807
+ function resolveLegacyFilePath(options) {
814
808
  return resolveMSTeamsStorePath({
815
809
  filename: STORE_FILENAME,
816
810
  env: options?.env,
@@ -819,6 +813,42 @@ function resolveFilePath(options) {
819
813
  storePath: options?.storePath
820
814
  });
821
815
  }
816
+ function createMetaStore(options) {
817
+ return getMSTeamsRuntime().state.openKeyedStore({
818
+ namespace: PENDING_UPLOAD_META_NAMESPACE,
819
+ maxEntries: PENDING_UPLOAD_META_MAX_ENTRIES,
820
+ env: resolveMSTeamsSqliteStateEnv(options)
821
+ });
822
+ }
823
+ function createChunkStore(options) {
824
+ return getMSTeamsRuntime().state.openKeyedStore({
825
+ namespace: PENDING_UPLOAD_CHUNKS_NAMESPACE,
826
+ maxEntries: MAX_PENDING_UPLOAD_CHUNK_ROWS,
827
+ env: resolveMSTeamsSqliteStateEnv(options)
828
+ });
829
+ }
830
+ function createMigrationStore(options) {
831
+ return getMSTeamsRuntime().state.openKeyedStore({
832
+ namespace: PENDING_UPLOAD_MIGRATIONS_NAMESPACE,
833
+ maxEntries: 100,
834
+ env: resolveMSTeamsSqliteStateEnv(options)
835
+ });
836
+ }
837
+ function buildUploadKey(id) {
838
+ return `upload:${createHash("sha256").update(id).digest("hex")}`;
839
+ }
840
+ function buildMetaKey(id) {
841
+ return `${buildUploadKey(id)}:meta`;
842
+ }
843
+ function buildChunkKey(id, index) {
844
+ return `${buildUploadKey(id)}:chunk:${String(index).padStart(4, "0")}`;
845
+ }
846
+ function buildMigrationKey(filePath) {
847
+ return `legacy-json:${createHash("sha256").update(filePath).digest("hex")}`;
848
+ }
849
+ function buildMigrationContentKey(filePath, value) {
850
+ return `legacy-json-content:${createHash("sha256").update(filePath).update("\0").update(JSON.stringify(value) ?? "undefined").digest("hex")}`;
851
+ }
822
852
  function pruneExpired(uploads, nowMs, ttlMs) {
823
853
  const kept = {};
824
854
  for (const [id, record] of Object.entries(uploads)) if (nowMs - record.createdAt <= ttlMs) kept[id] = record;
@@ -831,10 +861,10 @@ function pruneToLimit(uploads) {
831
861
  const keep = entries.slice(entries.length - MAX_PENDING_UPLOADS);
832
862
  return Object.fromEntries(keep);
833
863
  }
834
- function recordToUpload(record) {
864
+ function recordToUpload(record, buffer) {
835
865
  return {
836
866
  id: record.id,
837
- buffer: Buffer.from(record.bufferBase64, "base64"),
867
+ buffer,
838
868
  filename: record.filename,
839
869
  contentType: record.contentType,
840
870
  conversationId: record.conversationId,
@@ -847,17 +877,119 @@ function isValidStore(value) {
847
877
  const candidate = value;
848
878
  return candidate.version === 1 && typeof candidate.uploads === "object" && candidate.uploads !== null && !Array.isArray(candidate.uploads);
849
879
  }
850
- async function readStore(filePath, ttlMs) {
851
- const { value } = await readJsonFile(filePath, empty);
852
- if (!isValidStore(value)) return {
853
- version: 1,
854
- uploads: {}
855
- };
880
+ function normalizeLegacyUploadRecord(value) {
881
+ if (!value || typeof value !== "object") return null;
882
+ const record = value;
883
+ if (typeof record.id !== "string" || !record.id || typeof record.bufferBase64 !== "string" || typeof record.filename !== "string" || !record.filename || typeof record.conversationId !== "string" || !record.conversationId || typeof record.createdAt !== "number" || !Number.isFinite(record.createdAt)) return null;
856
884
  return {
857
- version: 1,
858
- uploads: pruneToLimit(pruneExpired(value.uploads, Date.now(), ttlMs))
885
+ id: record.id,
886
+ bufferBase64: record.bufferBase64,
887
+ filename: record.filename,
888
+ contentType: typeof record.contentType === "string" ? record.contentType : void 0,
889
+ conversationId: record.conversationId,
890
+ consentCardActivityId: typeof record.consentCardActivityId === "string" ? record.consentCardActivityId : void 0,
891
+ createdAt: record.createdAt
859
892
  };
860
893
  }
894
+ function normalizeLegacyUploads(value) {
895
+ if (!isValidStore(value)) return {};
896
+ const uploads = {};
897
+ for (const record of Object.values(value.uploads)) {
898
+ const normalized = normalizeLegacyUploadRecord(record);
899
+ if (normalized) uploads[normalized.id] = normalized;
900
+ }
901
+ return uploads;
902
+ }
903
+ async function deleteUploadRows(id, metaStore, chunkStore) {
904
+ const existing = await metaStore.lookup(buildMetaKey(id));
905
+ await metaStore.delete(buildMetaKey(id));
906
+ if (!existing) return;
907
+ const chunkCount = existing.chunkCount;
908
+ for (let index = 0; index < chunkCount; index += 1) await chunkStore.delete(buildChunkKey(id, index));
909
+ }
910
+ async function registerUploadRows(record, metaStore, chunkStore, ttlMs, overwrite) {
911
+ const buffer = Buffer.from(record.bufferBase64, "base64");
912
+ const chunkCount = Math.max(1, Math.ceil(buffer.byteLength / RAW_CHUNK_BYTES));
913
+ if (chunkCount > MAX_CHUNKS_PER_UPLOAD) throw new Error(`Microsoft Teams pending upload ${record.id} exceeds SQLite chunk limit (${chunkCount}/${MAX_CHUNKS_PER_UPLOAD})`);
914
+ if (overwrite) await deleteUploadRows(record.id, metaStore, chunkStore);
915
+ else if (await metaStore.lookup(buildMetaKey(record.id))) return;
916
+ await pruneUploadStore(metaStore, chunkStore, ttlMs, chunkCount);
917
+ for (let index = 0; index < chunkCount; index += 1) {
918
+ const chunk = buffer.subarray(index * RAW_CHUNK_BYTES, (index + 1) * RAW_CHUNK_BYTES);
919
+ await chunkStore.register(buildChunkKey(record.id, index), toPluginJsonValue({
920
+ id: record.id,
921
+ index,
922
+ dataBase64: chunk.toString("base64")
923
+ }));
924
+ }
925
+ await metaStore.register(buildMetaKey(record.id), toPluginJsonValue({
926
+ id: record.id,
927
+ filename: record.filename,
928
+ contentType: record.contentType,
929
+ conversationId: record.conversationId,
930
+ consentCardActivityId: record.consentCardActivityId,
931
+ createdAt: record.createdAt,
932
+ chunkCount,
933
+ byteLength: buffer.byteLength
934
+ }));
935
+ }
936
+ async function importLegacyStore(options, metaStore, chunkStore, ttlMs) {
937
+ const legacyFilePath = resolveLegacyFilePath(options);
938
+ const migrationStore = createMigrationStore(options);
939
+ const migrationKey = buildMigrationKey(legacyFilePath);
940
+ const imported = await migrationStore.lookup(migrationKey) !== void 0;
941
+ const { value, exists } = await readJsonFile(legacyFilePath, empty);
942
+ if (!exists) {
943
+ if (!imported) await migrationStore.register(migrationKey, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
944
+ return;
945
+ }
946
+ const contentKey = buildMigrationContentKey(legacyFilePath, value);
947
+ if (await migrationStore.lookup(contentKey)) return;
948
+ const legacy = pruneToLimit(pruneExpired(normalizeLegacyUploads(value), Date.now(), ttlMs));
949
+ for (const record of Object.values(legacy)) await registerUploadRows(record, metaStore, chunkStore, ttlMs, false);
950
+ await migrationStore.register(contentKey, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
951
+ if (!imported) await migrationStore.register(migrationKey, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
952
+ await fs.rm(legacyFilePath, { force: true }).catch(() => {});
953
+ }
954
+ async function withPendingUploadLock(options, run) {
955
+ return await withMSTeamsSqliteMutationLock(options, PENDING_UPLOAD_LOCK_FILENAME, run);
956
+ }
957
+ async function ensureLegacyImported(options, metaStore, chunkStore, ttlMs) {
958
+ await withPendingUploadLock(options, () => importLegacyStore(options, metaStore, chunkStore, ttlMs));
959
+ }
960
+ async function readUploadRows(id, metaStore, chunkStore) {
961
+ const meta = await metaStore.lookup(buildMetaKey(id));
962
+ if (!meta) return;
963
+ const chunks = [];
964
+ for (let index = 0; index < meta.chunkCount; index += 1) {
965
+ const chunk = await chunkStore.lookup(buildChunkKey(id, index));
966
+ if (!chunk || chunk.id !== id || chunk.index !== index) return;
967
+ chunks.push(Buffer.from(chunk.dataBase64, "base64"));
968
+ }
969
+ return recordToUpload(meta, Buffer.concat(chunks, meta.byteLength));
970
+ }
971
+ async function pruneUploadStore(metaStore, chunkStore, ttlMs, extraChunkRows = 0) {
972
+ const rows = await metaStore.entries();
973
+ const liveRows = [];
974
+ const now = Date.now();
975
+ let liveChunkRows = 0;
976
+ for (const row of rows) {
977
+ if (now - row.value.createdAt > ttlMs) {
978
+ await deleteUploadRows(row.value.id, metaStore, chunkStore);
979
+ continue;
980
+ }
981
+ liveChunkRows += row.value.chunkCount;
982
+ liveRows.push(row);
983
+ }
984
+ if (liveRows.length <= MAX_PENDING_UPLOADS && liveChunkRows + extraChunkRows <= MAX_PENDING_UPLOAD_CHUNK_ROWS) return;
985
+ const sorted = liveRows.toSorted((a, b) => a.value.createdAt - b.value.createdAt || a.value.id.localeCompare(b.value.id));
986
+ for (const row of sorted) {
987
+ if (liveRows.length <= MAX_PENDING_UPLOADS && liveChunkRows + extraChunkRows <= MAX_PENDING_UPLOAD_CHUNK_ROWS) break;
988
+ await deleteUploadRows(row.value.id, metaStore, chunkStore);
989
+ liveChunkRows -= row.value.chunkCount;
990
+ liveRows.pop();
991
+ }
992
+ }
861
993
  /**
862
994
  * Persist a pending upload record so another process can read it back.
863
995
  * Pass in the pre-generated id (same as the one placed in the consent card
@@ -865,10 +997,11 @@ async function readStore(filePath, ttlMs) {
865
997
  */
866
998
  async function storePendingUploadFs(upload, options) {
867
999
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
868
- const filePath = resolveFilePath(options);
869
- await withFileLock$1(filePath, empty, async () => {
870
- const store = await readStore(filePath, ttlMs);
871
- store.uploads[upload.id] = {
1000
+ const metaStore = createMetaStore(options);
1001
+ const chunkStore = createChunkStore(options);
1002
+ await withPendingUploadLock(options, async () => {
1003
+ await importLegacyStore(options, metaStore, chunkStore, ttlMs);
1004
+ await registerUploadRows({
872
1005
  id: upload.id,
873
1006
  bufferBase64: upload.buffer.toString("base64"),
874
1007
  filename: upload.filename,
@@ -876,9 +1009,8 @@ async function storePendingUploadFs(upload, options) {
876
1009
  conversationId: upload.conversationId,
877
1010
  consentCardActivityId: upload.consentCardActivityId,
878
1011
  createdAt: Date.now()
879
- };
880
- store.uploads = pruneToLimit(pruneExpired(store.uploads, Date.now(), ttlMs));
881
- await writeJsonFile(filePath, store);
1012
+ }, metaStore, chunkStore, ttlMs, true);
1013
+ await pruneUploadStore(metaStore, chunkStore, ttlMs);
882
1014
  });
883
1015
  }
884
1016
  /**
@@ -887,10 +1019,16 @@ async function storePendingUploadFs(upload, options) {
887
1019
  async function getPendingUploadFs(id, options) {
888
1020
  if (!id) return;
889
1021
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
890
- const record = (await readStore(resolveFilePath(options), ttlMs)).uploads[id];
891
- if (!record) return;
892
- if (Date.now() - record.createdAt > ttlMs) return;
893
- return recordToUpload(record);
1022
+ const metaStore = createMetaStore(options);
1023
+ const chunkStore = createChunkStore(options);
1024
+ await ensureLegacyImported(options, metaStore, chunkStore, ttlMs);
1025
+ const upload = await readUploadRows(id, metaStore, chunkStore);
1026
+ if (!upload) return;
1027
+ if (Date.now() - upload.createdAt > ttlMs) {
1028
+ await removePendingUploadFs(id, options);
1029
+ return;
1030
+ }
1031
+ return upload;
894
1032
  }
895
1033
  /**
896
1034
  * Remove a persisted pending upload (after successful upload or decline).
@@ -899,12 +1037,11 @@ async function getPendingUploadFs(id, options) {
899
1037
  async function removePendingUploadFs(id, options) {
900
1038
  if (!id) return;
901
1039
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
902
- const filePath = resolveFilePath(options);
903
- await withFileLock$1(filePath, empty, async () => {
904
- const store = await readStore(filePath, ttlMs);
905
- if (!(id in store.uploads)) return;
906
- delete store.uploads[id];
907
- await writeJsonFile(filePath, store);
1040
+ const metaStore = createMetaStore(options);
1041
+ const chunkStore = createChunkStore(options);
1042
+ await withPendingUploadLock(options, async () => {
1043
+ await importLegacyStore(options, metaStore, chunkStore, ttlMs);
1044
+ await deleteUploadRows(id, metaStore, chunkStore);
908
1045
  });
909
1046
  }
910
1047
  /**
@@ -913,13 +1050,16 @@ async function removePendingUploadFs(id, options) {
913
1050
  */
914
1051
  async function setPendingUploadActivityIdFs(id, activityId, options) {
915
1052
  const ttlMs = options?.ttlMs ?? PENDING_UPLOAD_TTL_MS$1;
916
- const filePath = resolveFilePath(options);
917
- await withFileLock$1(filePath, empty, async () => {
918
- const store = await readStore(filePath, ttlMs);
919
- const record = store.uploads[id];
920
- if (!record) return;
921
- record.consentCardActivityId = activityId;
922
- await writeJsonFile(filePath, store);
1053
+ const metaStore = createMetaStore(options);
1054
+ const chunkStore = createChunkStore(options);
1055
+ await withPendingUploadLock(options, async () => {
1056
+ await importLegacyStore(options, metaStore, chunkStore, ttlMs);
1057
+ const record = await metaStore.lookup(buildMetaKey(id));
1058
+ if (!record || Date.now() - record.createdAt > ttlMs) return;
1059
+ await metaStore.register(buildMetaKey(id), toPluginJsonValue({
1060
+ ...record,
1061
+ consentCardActivityId: activityId
1062
+ }));
923
1063
  });
924
1064
  }
925
1065
  //#endregion
@@ -2567,4 +2707,4 @@ async function probeMSTeams(cfg) {
2567
2707
  }
2568
2708
  }
2569
2709
  //#endregion
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 };
2710
+ export { resolveMSTeamsSqliteStateEnv as C, readJsonFile as E, createMSTeamsConversationStoreState as S, withMSTeamsSqliteMutationLock as T, buildFileInfoCard as _, sendMessageMSTeams as a, createMSTeamsPollStoreState as b, renderReplyPayloadsToMessages as c, withRevokedProxyFallback as d, resolveGraphChatId as f, removePendingUploadFs as g, getPendingUploadFs as h, sendAdaptiveCardMSTeams as i, sendMSTeamsMessages as l, removePendingUpload as m, deleteMessageMSTeams as n, sendPollMSTeams as o, getPendingUpload as p, editMessageMSTeams as r, buildConversationReference as s, probeMSTeams as t, sendMSTeamsActivityWithReference as u, parseFileConsentInvoke as v, toPluginJsonValue as w, extractMSTeamsPollVote as x, uploadToConsentUrl as y };
@@ -1,5 +1,5 @@
1
- import { y as resolveMSTeamsCredentials } from "./errors-HUt6we-U.js";
2
- import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-BLAlJzog.js";
1
+ import { y as resolveMSTeamsCredentials } from "./errors-Dpn8B05h.js";
2
+ import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-B7a1pD-K.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-HUt6we-U.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-Dpn8B05h.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";