@openclaw/msteams 2026.6.1 → 2026.6.5-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-DGTz8Mlf.js";
2
- import { t as msteamsPlugin } from "./channel-Dhw8AvTl.js";
1
+ import { t as msteamsPlugin } from "./channel--oT3fyD5.js";
2
+ import { c as msteamsSetupAdapter, n as openDelegatedOAuthUrl, s as createMSTeamsSetupWizardBase, t as msteamsSetupWizard } from "./setup-surface-Dik4VU7f.js";
3
3
  export { createMSTeamsSetupWizardBase, msteamsPlugin, msteamsSetupAdapter, msteamsSetupWizard, openDelegatedOAuthUrl };
@@ -1,7 +1,7 @@
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-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-DGTz8Mlf.js";
1
+ import { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE, buildChannelKeyCandidates, buildProbeChannelStatusSummary, chunkTextForOutbound, createDefaultChannelRuntimeState, isDangerousNameMatchingEnabled, normalizeChannelSlug, resolveAllowlistMatchSimple, resolveChannelEntryMatchWithFallback, resolveNestedAllowlistDecision, resolveToolsBySender } from "./runtime-api.js";
2
+ import { C as resolveMSTeamsCredentials, a as parseMSTeamsTeamChannelInput, c as resolveMSTeamsUserAllowlist, i as parseMSTeamsConversationId, n as normalizeMSTeamsMessagingTarget, r as normalizeMSTeamsUserInput, s as resolveMSTeamsChannelAllowlist, t as looksLikeMSTeamsTargetId } from "./resolve-allowlist-BzIUWmMm.js";
4
3
  import { t as MSTeamsChannelConfigSchema } from "./config-schema-BL4qQZiA.js";
4
+ import { c as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-Dik4VU7f.js";
5
5
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
6
6
  import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
7
7
  import { createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
@@ -331,7 +331,7 @@ const collectMSTeamsSecurityWarnings = createAllowlistProviderGroupPolicyWarning
331
331
  resolveGroupPolicy: ({ cfg }) => cfg.channels?.msteams?.groupPolicy,
332
332
  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."] : []
333
333
  });
334
- const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-t52N99bX.js"), "msTeamsChannelRuntime");
334
+ const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-KNuY2Oaw.js"), "msTeamsChannelRuntime");
335
335
  const resolveMSTeamsChannelConfig = (cfg) => ({
336
336
  allowFrom: cfg.channels?.msteams?.allowFrom,
337
337
  defaultTo: cfg.channels?.msteams?.defaultTo
@@ -1117,7 +1117,7 @@ const msteamsPlugin = createChatChannelPlugin({
1117
1117
  })
1118
1118
  }),
1119
1119
  gateway: { startAccount: async (ctx) => {
1120
- const { monitorMSTeamsProvider } = await import("./src-DZ76sgTp.js");
1120
+ const { monitorMSTeamsProvider } = await import("./src-CLsoqMj1.js");
1121
1121
  const port = ctx.cfg.channels?.msteams?.webhook?.port ?? 3978;
1122
1122
  ctx.setStatus({
1123
1123
  accountId: ctx.accountId,
@@ -1,2 +1,2 @@
1
- import { t as msteamsPlugin } from "./channel-Dhw8AvTl.js";
1
+ import { t as msteamsPlugin } from "./channel--oT3fyD5.js";
2
2
  export { msteamsPlugin };
@@ -1,7 +1,8 @@
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-Dpn8B05h.js";
3
- import { n as MSTEAMS_PRESENTATION_CAPABILITIES, r as buildMSTeamsPresentationCard } from "./channel-Dhw8AvTl.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-DYb-0Hmx.js";
1
+ import { chunkTextForOutbound, normalizeStringEntries as normalizeStringEntries$1 } from "./runtime-api.js";
2
+ import { _ as patchGraphJson, b as resolveGraphToken, d as escapeOData, f as fetchGraphAbsoluteUrl, g as normalizeQuery, h as listTeamsByName, l as searchGraphUsers, m as listChannelsForTeam, p as fetchGraphJson, u as deleteGraphRequest, v as postGraphBetaJson, y as postGraphJson } from "./resolve-allowlist-BzIUWmMm.js";
3
+ import { n as MSTEAMS_PRESENTATION_CAPABILITIES, r as buildMSTeamsPresentationCard } from "./channel--oT3fyD5.js";
4
+ import { l as createMSTeamsPollStoreState, v as createMSTeamsConversationStoreState } from "./polls-C1VgSvKE.js";
5
+ import { a as sendMessageMSTeams, i as sendAdaptiveCardMSTeams, n as deleteMessageMSTeams, o as sendPollMSTeams, r as editMessageMSTeams, t as probeMSTeams } from "./probe-C-rWMb-m.js";
5
6
  import { resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-outbound";
6
7
  import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
7
8
  import { resolvePayloadMediaUrls, resolveTextChunksWithFallback, sendPayloadMediaSequence } from "openclaw/plugin-sdk/reply-payload";
@@ -0,0 +1,35 @@
1
+ import { C as resolveMSTeamsCredentials, n as normalizeMSTeamsMessagingTarget } from "./resolve-allowlist-BzIUWmMm.js";
2
+ import { listDirectoryEntriesFromSources } from "openclaw/plugin-sdk/directory-runtime";
3
+ const msteamsDirectoryContractPlugin = {
4
+ id: "msteams",
5
+ directory: {
6
+ self: async ({ cfg }) => {
7
+ const creds = resolveMSTeamsCredentials(cfg.channels?.msteams);
8
+ return creds ? {
9
+ kind: "user",
10
+ id: creds.appId,
11
+ name: creds.appId
12
+ } : null;
13
+ },
14
+ listPeers: async ({ cfg, query, limit }) => listDirectoryEntriesFromSources({
15
+ kind: "user",
16
+ sources: [cfg.channels?.msteams?.allowFrom ?? [], Object.keys(cfg.channels?.msteams?.dms ?? {})],
17
+ query,
18
+ limit,
19
+ normalizeId: (raw) => {
20
+ const normalized = normalizeMSTeamsMessagingTarget(raw) ?? raw;
21
+ const lowered = normalized.toLowerCase();
22
+ return lowered.startsWith("user:") || lowered.startsWith("conversation:") ? normalized : `user:${normalized}`;
23
+ }
24
+ }),
25
+ listGroups: async ({ cfg, query, limit }) => listDirectoryEntriesFromSources({
26
+ kind: "group",
27
+ sources: [Object.values(cfg.channels?.msteams?.teams ?? {}).flatMap((team) => Object.keys(team.channels ?? {}))],
28
+ query,
29
+ limit,
30
+ normalizeId: (raw) => `conversation:${raw.replace(/^conversation:/i, "").trim()}`
31
+ })
32
+ }
33
+ };
34
+ //#endregion
35
+ export { msteamsDirectoryContractPlugin };
@@ -1,3 +1,5 @@
1
+ import { T as normalizeStoredConversationId, _ as buildMSTeamsConversationStateKey, a as MSTEAMS_SQLITE_MAX_POLL_ROWS, b as prepareMSTeamsConversationReferenceForStorage, c as buildMSTeamsPollVoteBucketKey, d as selectMSTeamsPollVoteBucket, f as selectRetainedMSTeamsPolls, g as MSTEAMS_SQLITE_MAX_CONVERSATION_ROWS, h as MSTEAMS_CONVERSATIONS_NAMESPACE, i as MSTEAMS_POLL_VOTE_BUCKETS_NAMESPACE, m as MSTEAMS_CONVERSATIONS_LEGACY_FILENAME, n as MSTEAMS_POLLS_LEGACY_FILENAME, p as splitMSTeamsPoll, r as MSTEAMS_POLLS_NAMESPACE, s as buildMSTeamsPollStateKey, t as MSTEAMS_MAX_POLL_VOTE_BUCKET_ROWS, x as selectRetainedMSTeamsConversations, y as normalizeMSTeamsLegacyConversationStore } from "./polls-C1VgSvKE.js";
2
+ import { a as isMSTeamsSsoStoreData, n as MSTEAMS_SSO_TOKENS_LEGACY_FILENAME, o as makeMSTeamsSsoTokenStoreKey, r as MSTEAMS_SSO_TOKENS_NAMESPACE, s as normalizeMSTeamsSsoStoredToken, t as MSTEAMS_MAX_SSO_TOKENS } from "./sso-token-store-BYZaKr82.js";
1
3
  import path from "node:path";
2
4
  import crypto from "node:crypto";
3
5
  import fs from "node:fs/promises";
@@ -5,6 +7,7 @@ import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
5
7
  //#region extensions/msteams/doctor-contract-api.ts
6
8
  const LEARNINGS_NAMESPACE = "feedback-learnings";
7
9
  const MAX_LEARNING_ENTRIES = 1e4;
10
+ const MSTEAMS_PLUGIN_ID = "Microsoft Teams";
8
11
  function encodeSessionKey(sessionKey) {
9
12
  return Buffer.from(sessionKey, "utf8").toString("base64url");
10
13
  }
@@ -60,6 +63,59 @@ async function fileExists(filePath) {
60
63
  return false;
61
64
  }
62
65
  }
66
+ function resolveStateFilePath(stateDir, filename) {
67
+ return path.join(stateDir, filename);
68
+ }
69
+ async function readLegacyJsonFile(filePath, parse) {
70
+ try {
71
+ return parse(JSON.parse(await fs.readFile(filePath, "utf8")));
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ function isRecord(value) {
77
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
78
+ }
79
+ function isStringArray(value) {
80
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
81
+ }
82
+ function parseLegacyConversationStore(value) {
83
+ if (!isRecord(value) || value.version !== 1 || !isRecord(value.conversations)) return null;
84
+ return normalizeMSTeamsLegacyConversationStore({
85
+ version: 1,
86
+ conversations: value.conversations
87
+ });
88
+ }
89
+ function parseLegacyPoll(value) {
90
+ if (!isRecord(value)) return null;
91
+ const votes = isRecord(value.votes) ? value.votes : null;
92
+ if (typeof value.id !== "string" || !value.id || typeof value.question !== "string" || !value.question || !isStringArray(value.options) || typeof value.maxSelections !== "number" || !Number.isFinite(value.maxSelections) || typeof value.createdAt !== "string" || !votes) return null;
93
+ const normalizedVotes = {};
94
+ for (const [voterId, selections] of Object.entries(votes)) if (typeof voterId === "string" && isStringArray(selections)) normalizedVotes[voterId] = selections;
95
+ return {
96
+ id: value.id,
97
+ question: value.question,
98
+ options: value.options,
99
+ maxSelections: value.maxSelections,
100
+ createdAt: value.createdAt,
101
+ ...typeof value.updatedAt === "string" ? { updatedAt: value.updatedAt } : {},
102
+ ...typeof value.conversationId === "string" ? { conversationId: value.conversationId } : {},
103
+ ...typeof value.messageId === "string" ? { messageId: value.messageId } : {},
104
+ votes: normalizedVotes
105
+ };
106
+ }
107
+ function parseLegacyPollStore(value) {
108
+ if (!isRecord(value) || value.version !== 1 || !isRecord(value.polls)) return null;
109
+ const polls = {};
110
+ for (const [pollId, poll] of Object.entries(value.polls)) {
111
+ const parsed = parseLegacyPoll(poll);
112
+ if (parsed) polls[pollId] = parsed;
113
+ }
114
+ return {
115
+ version: 1,
116
+ polls
117
+ };
118
+ }
63
119
  async function listLegacyLearningFiles(storePath) {
64
120
  let entries;
65
121
  try {
@@ -92,15 +148,16 @@ async function listLegacyLearningFiles(storePath) {
92
148
  }
93
149
  async function archiveLegacySource(params) {
94
150
  const archivedPath = `${params.filePath}.migrated`;
151
+ const label = params.label ?? "Microsoft Teams feedback-learning";
95
152
  if (await fileExists(archivedPath)) {
96
- params.warnings.push(`Left migrated Microsoft Teams feedback-learning source in place because ${archivedPath} already exists`);
153
+ params.warnings.push(`Left migrated ${label} source in place because ${archivedPath} already exists`);
97
154
  return;
98
155
  }
99
156
  try {
100
157
  await fs.rename(params.filePath, archivedPath);
101
- params.changes.push(`Archived Microsoft Teams feedback-learning legacy source -> ${archivedPath}`);
158
+ params.changes.push(`Archived ${label} legacy source -> ${archivedPath}`);
102
159
  } catch (err) {
103
- params.warnings.push(`Failed archiving Microsoft Teams feedback-learning legacy source: ${String(err)}`);
160
+ params.warnings.push(`Failed archiving ${label} legacy source: ${String(err)}`);
104
161
  }
105
162
  }
106
163
  function mergeLearnings(legacy, existing) {
@@ -113,59 +170,212 @@ function mergeLearnings(legacy, existing) {
113
170
  }
114
171
  return merged.slice(-10);
115
172
  }
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`);
173
+ const stateMigrations = [
174
+ {
175
+ id: "msteams-conversations-json-to-plugin-state",
176
+ label: "Microsoft Teams conversations",
177
+ async detectLegacyState(params) {
178
+ const state = await readLegacyJsonFile(resolveStateFilePath(params.stateDir, MSTEAMS_CONVERSATIONS_LEGACY_FILENAME), parseLegacyConversationStore);
179
+ if (!state || Object.keys(state.conversations).length === 0) return null;
180
+ return { preview: [`- ${MSTEAMS_PLUGIN_ID} conversations: ${Object.keys(state.conversations).length} entries -> plugin state (${MSTEAMS_CONVERSATIONS_NAMESPACE})`] };
181
+ },
182
+ async migrateLegacyState(params) {
183
+ const changes = [];
184
+ const warnings = [];
185
+ const filePath = resolveStateFilePath(params.stateDir, MSTEAMS_CONVERSATIONS_LEGACY_FILENAME);
186
+ const state = await readLegacyJsonFile(filePath, parseLegacyConversationStore);
187
+ if (!state) return {
188
+ changes,
189
+ warnings
190
+ };
191
+ const store = params.context.openPluginStateKeyedStore({
192
+ namespace: MSTEAMS_CONVERSATIONS_NAMESPACE,
193
+ maxEntries: MSTEAMS_SQLITE_MAX_CONVERSATION_ROWS
194
+ });
195
+ let imported = 0;
196
+ for (const [rawConversationId, reference] of selectRetainedMSTeamsConversations(state.conversations)) {
197
+ const conversationId = normalizeStoredConversationId(rawConversationId);
198
+ if (!conversationId) continue;
199
+ if (await store.registerIfAbsent(buildMSTeamsConversationStateKey(conversationId), prepareMSTeamsConversationReferenceForStorage(conversationId, reference))) imported++;
200
+ }
201
+ changes.push(`Migrated ${imported} ${MSTEAMS_PLUGIN_ID} conversation ${imported === 1 ? "entry" : "entries"} -> plugin state`);
202
+ await archiveLegacySource({
203
+ filePath,
204
+ label: `${MSTEAMS_PLUGIN_ID} conversation`,
205
+ changes,
206
+ warnings
207
+ });
138
208
  return {
139
209
  changes,
140
210
  warnings
141
211
  };
142
212
  }
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;
213
+ },
214
+ {
215
+ id: "msteams-polls-json-to-plugin-state",
216
+ label: "Microsoft Teams polls",
217
+ async detectLegacyState(params) {
218
+ const state = await readLegacyJsonFile(resolveStateFilePath(params.stateDir, MSTEAMS_POLLS_LEGACY_FILENAME), parseLegacyPollStore);
219
+ if (!state || Object.keys(state.polls).length === 0) return null;
220
+ return { preview: [`- ${MSTEAMS_PLUGIN_ID} polls: ${Object.keys(state.polls).length} entries -> plugin state (${MSTEAMS_POLLS_NAMESPACE})`] };
221
+ },
222
+ async migrateLegacyState(params) {
223
+ const changes = [];
224
+ const warnings = [];
225
+ const filePath = resolveStateFilePath(params.stateDir, MSTEAMS_POLLS_LEGACY_FILENAME);
226
+ const state = await readLegacyJsonFile(filePath, parseLegacyPollStore);
227
+ if (!state) return {
228
+ changes,
229
+ warnings
230
+ };
231
+ const pollStore = params.context.openPluginStateKeyedStore({
232
+ namespace: MSTEAMS_POLLS_NAMESPACE,
233
+ maxEntries: MSTEAMS_SQLITE_MAX_POLL_ROWS
234
+ });
235
+ const voteBucketStore = params.context.openPluginStateKeyedStore({
236
+ namespace: MSTEAMS_POLL_VOTE_BUCKETS_NAMESPACE,
237
+ maxEntries: MSTEAMS_MAX_POLL_VOTE_BUCKET_ROWS
238
+ });
239
+ let imported = 0;
240
+ for (const [pollId, poll] of selectRetainedMSTeamsPolls(state.polls)) {
241
+ const { metadata, votes } = splitMSTeamsPoll(poll);
242
+ const didImportPoll = await pollStore.registerIfAbsent(buildMSTeamsPollStateKey(pollId), metadata);
243
+ const buckets = /* @__PURE__ */ new Map();
244
+ for (const [voterId, selections] of Object.entries(votes)) {
245
+ const bucket = selectMSTeamsPollVoteBucket(pollId, voterId);
246
+ const bucketVotes = buckets.get(bucket) ?? {};
247
+ bucketVotes[voterId] = selections;
248
+ buckets.set(bucket, bucketVotes);
249
+ }
250
+ let importedVoteBucket = false;
251
+ for (const [bucket, bucketVotes] of buckets) {
252
+ const key = buildMSTeamsPollVoteBucketKey(pollId, bucket);
253
+ const existing = await voteBucketStore.lookup(key);
254
+ await voteBucketStore.register(key, {
255
+ pollId,
256
+ bucket,
257
+ votes: {
258
+ ...bucketVotes,
259
+ ...existing?.votes
260
+ },
261
+ updatedAt: poll.updatedAt ?? poll.createdAt
262
+ });
263
+ importedVoteBucket = true;
264
+ }
265
+ if (didImportPoll || importedVoteBucket) imported++;
148
266
  }
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()
267
+ changes.push(`Migrated ${imported} ${MSTEAMS_PLUGIN_ID} poll ${imported === 1 ? "entry" : "entries"} -> plugin state`);
268
+ await archiveLegacySource({
269
+ filePath,
270
+ label: `${MSTEAMS_PLUGIN_ID} poll`,
271
+ changes,
272
+ warnings
273
+ });
274
+ return {
275
+ changes,
276
+ warnings
277
+ };
278
+ }
279
+ },
280
+ {
281
+ id: "msteams-sso-tokens-json-to-plugin-state",
282
+ label: "Microsoft Teams SSO tokens",
283
+ async detectLegacyState(params) {
284
+ const state = await readLegacyJsonFile(resolveStateFilePath(params.stateDir, MSTEAMS_SSO_TOKENS_LEGACY_FILENAME), (value) => isMSTeamsSsoStoreData(value) ? value : null);
285
+ if (!state || Object.keys(state.tokens).length === 0) return null;
286
+ return { preview: [`- ${MSTEAMS_PLUGIN_ID} SSO tokens: ${Object.keys(state.tokens).length} entries -> plugin state (${MSTEAMS_SSO_TOKENS_NAMESPACE})`] };
287
+ },
288
+ async migrateLegacyState(params) {
289
+ const changes = [];
290
+ const warnings = [];
291
+ const filePath = resolveStateFilePath(params.stateDir, MSTEAMS_SSO_TOKENS_LEGACY_FILENAME);
292
+ const state = await readLegacyJsonFile(filePath, (value) => isMSTeamsSsoStoreData(value) ? value : null);
293
+ if (!state) return {
294
+ changes,
295
+ warnings
296
+ };
297
+ const store = params.context.openPluginStateKeyedStore({
298
+ namespace: MSTEAMS_SSO_TOKENS_NAMESPACE,
299
+ maxEntries: MSTEAMS_MAX_SSO_TOKENS
155
300
  });
156
- imported++;
301
+ let imported = 0;
302
+ let skipped = 0;
303
+ for (const token of Object.values(state.tokens)) {
304
+ const normalized = normalizeMSTeamsSsoStoredToken(token);
305
+ if (!normalized) {
306
+ skipped++;
307
+ continue;
308
+ }
309
+ if (await store.registerIfAbsent(makeMSTeamsSsoTokenStoreKey(normalized.connectionName, normalized.userId), normalized)) imported++;
310
+ }
311
+ changes.push(`Migrated ${imported} ${MSTEAMS_PLUGIN_ID} SSO token ${imported === 1 ? "entry" : "entries"} -> plugin state`);
312
+ if (skipped > 0) warnings.push(`Skipped ${skipped} malformed ${MSTEAMS_PLUGIN_ID} SSO token ${skipped === 1 ? "entry" : "entries"} during migration`);
157
313
  await archiveLegacySource({
158
- filePath: file.filePath,
314
+ filePath,
315
+ label: `${MSTEAMS_PLUGIN_ID} SSO-token`,
316
+ changes,
317
+ warnings
318
+ });
319
+ return {
159
320
  changes,
160
321
  warnings
322
+ };
323
+ }
324
+ },
325
+ {
326
+ id: "msteams-feedback-learnings-json-to-plugin-state",
327
+ label: "Microsoft Teams feedback learnings",
328
+ async detectLegacyState(params) {
329
+ const files = (await Promise.all(listCandidateStorePaths(params).map((storePath) => listLegacyLearningFiles(storePath)))).flat();
330
+ if (files.length === 0) return null;
331
+ return { preview: [`- Microsoft Teams feedback learnings: ${files.length} ${files.length === 1 ? "file" : "files"} -> plugin state (${LEARNINGS_NAMESPACE})`] };
332
+ },
333
+ async migrateLegacyState(params) {
334
+ const changes = [];
335
+ const warnings = [];
336
+ const files = (await Promise.all(listCandidateStorePaths(params).map((storePath) => listLegacyLearningFiles(storePath)))).flat();
337
+ const store = params.context.openPluginStateKeyedStore({
338
+ namespace: LEARNINGS_NAMESPACE,
339
+ maxEntries: MAX_LEARNING_ENTRIES
161
340
  });
341
+ const existingEntries = await store.entries();
342
+ const existingKeys = new Set(existingEntries.map((entry) => entry.key));
343
+ const importableFiles = files.filter((file) => file.sessionKey);
344
+ const missingKeys = new Set(importableFiles.map((file) => learningStoreKey(file.storePath, file.sessionKey ?? "")).filter((key) => !existingKeys.has(key)));
345
+ if (missingKeys.size > MAX_LEARNING_ENTRIES - existingKeys.size) {
346
+ 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`);
347
+ return {
348
+ changes,
349
+ warnings
350
+ };
351
+ }
352
+ let imported = 0;
353
+ for (const file of files) {
354
+ if (!file.sessionKey) {
355
+ warnings.push(`Left Microsoft Teams feedback-learning source in place because its legacy filename cannot be mapped to a session key: ${file.filePath}`);
356
+ continue;
357
+ }
358
+ const key = learningStoreKey(file.storePath, file.sessionKey);
359
+ const existing = await store.lookup(key);
360
+ await store.register(key, {
361
+ sessionKey: existing?.sessionKey ?? file.sessionKey,
362
+ learnings: mergeLearnings(file.learnings, existing),
363
+ updatedAt: Date.now()
364
+ });
365
+ imported++;
366
+ await archiveLegacySource({
367
+ filePath: file.filePath,
368
+ changes,
369
+ warnings
370
+ });
371
+ }
372
+ if (imported > 0) changes.unshift(`Migrated ${imported} Microsoft Teams feedback-learning ${imported === 1 ? "entry" : "entries"} -> plugin state`);
373
+ return {
374
+ changes,
375
+ warnings
376
+ };
162
377
  }
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
378
  }
169
- }];
379
+ ];
170
380
  //#endregion
171
381
  export { stateMigrations };
@@ -1,4 +1,4 @@
1
- import { a as MSTEAMS_OAUTH_CALLBACK_PORT, i as MSTEAMS_OAUTH_CALLBACK_PATH, o as MSTEAMS_OAUTH_REDIRECT_URI, r as MSTEAMS_DEFAULT_DELEGATED_SCOPES, s as buildMSTeamsAuthEndpoint, t as exchangeMSTeamsCodeForTokens } from "./oauth.token-BKzEFepQ.js";
1
+ import { A as MSTEAMS_OAUTH_REDIRECT_URI, D as MSTEAMS_DEFAULT_DELEGATED_SCOPES, E as exchangeMSTeamsCodeForTokens, O as MSTEAMS_OAUTH_CALLBACK_PATH, j as buildMSTeamsAuthEndpoint, k as MSTEAMS_OAUTH_CALLBACK_PORT } from "./resolve-allowlist-BzIUWmMm.js";
2
2
  import { generateHexPkceVerifierChallenge } from "openclaw/plugin-sdk/provider-auth";
3
3
  import { generateOAuthState, parseOAuthCallbackInput, waitForLocalOAuthCallback } from "openclaw/plugin-sdk/provider-auth-runtime";
4
4
  import { isWSL2Sync } from "openclaw/plugin-sdk/runtime-env";