@openclaw/msteams 2026.5.28 → 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-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-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-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-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-CxxY1xk6.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-D_rcW2Zm.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-2_L55KDI.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-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-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";
@@ -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;
@@ -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
  /**
@@ -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,
@@ -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 };
@@ -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;
@@ -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;
@@ -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,