@openclaw/msteams 2026.6.1-beta.3 → 2026.6.2-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-BwNnuHbh.js";
2
+ import { c as msteamsSetupAdapter, n as openDelegatedOAuthUrl, s as createMSTeamsSetupWizardBase, t as msteamsSetupWizard } from "./setup-surface-DdyMxq-W.js";
3
3
  export { createMSTeamsSetupWizardBase, msteamsPlugin, msteamsSetupAdapter, msteamsSetupWizard, openDelegatedOAuthUrl };
@@ -1,7 +1,7 @@
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-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";
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-C6VVTfbj.js";
4
3
  import { t as MSTeamsChannelConfigSchema } from "./config-schema-BL4qQZiA.js";
4
+ import { c as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-DdyMxq-W.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-JR5pBsop.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-_wVYF1sz.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-BwNnuHbh.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-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";
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-C6VVTfbj.js";
3
+ import { n as MSTEAMS_PRESENTATION_CAPABILITIES, r as buildMSTeamsPresentationCard } from "./channel-BwNnuHbh.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-bq_Uev4c.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";
@@ -0,0 +1,35 @@
1
+ import { C as resolveMSTeamsCredentials, n as normalizeMSTeamsMessagingTarget } from "./resolve-allowlist-C6VVTfbj.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,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_CALLBACK_PORT, D as exchangeMSTeamsCodeForTokens, M as buildMSTeamsAuthEndpoint, O as MSTEAMS_DEFAULT_DELEGATED_SCOPES, j as MSTEAMS_OAUTH_REDIRECT_URI, k as MSTEAMS_OAUTH_CALLBACK_PATH } from "./resolve-allowlist-C6VVTfbj.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";
@@ -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-Dpn8B05h.js";
3
- import { c as createMSTeamsHttpError } from "./oauth.token-BKzEFepQ.js";
4
- import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel-Dhw8AvTl.js";
2
+ import { B as isAllowedBotFrameworkServiceUrl, C as resolveMSTeamsCredentials, F as createMSTeamsTokenProvider, G as validateMSTeamsProactiveServiceUrlBoundary, I as loadMSTeamsSdkWithAuth, L as buildUserAgent, N as readAccessToken, S as loadDelegatedTokens, T as resolveMSTeamsStorePath, U as createMSTeamsHttpError, V as normalizeBotFrameworkServiceUrl, W as resolveMSTeamsSdkCloudOptions, z as describeBotFrameworkServiceUrlHost } from "./resolve-allowlist-C6VVTfbj.js";
3
+ import { a as resolveMSTeamsReplyPolicy, o as resolveMSTeamsRouteConfig } from "./channel-BwNnuHbh.js";
4
+ import { a as formatUnknownError, i as formatMSTeamsSendErrorHint, o as isRevokedProxyError, r as classifyMSTeamsSendError } from "./setup-surface-DdyMxq-W.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";
@@ -1,16 +1,16 @@
1
1
  import { M as getMSTeamsRuntime, h as fetchWithSsrFGuard$1 } from "./runtime-api-BlvMnDKz.js";
2
- import { c as createMSTeamsHttpError, n as refreshMSTeamsDelegatedTokens } from "./oauth.token-BKzEFepQ.js";
3
2
  import { createRequire } from "node:module";
4
- import { isRecord, isRecord as isRecord$1, normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/allow-from";
4
+ import { isRecord as isRecord$1, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
6
- import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
6
+ import { createProviderHttpError, readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
7
7
  import { Buffer } from "node:buffer";
8
8
  import { lookup } from "node:dns/promises";
9
9
  import { buildHostnameAllowlistPolicyFromSuffixAllowlist, isHttpsUrlAllowedByHostnameSuffixAllowlist, isPrivateIpAddress, normalizeHostnameSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
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
+ import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationSeconds } from "openclaw/plugin-sdk/number-runtime";
14
14
  import { privateFileStoreSync } from "openclaw/plugin-sdk/security-runtime";
15
15
  import { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
16
16
  //#region extensions/msteams/src/attachments/shared.ts
@@ -539,6 +539,11 @@ function validateMSTeamsProactiveServiceUrlBoundary(params) {
539
539
  if (isChinaBotFrameworkServiceUrl(stored.value)) throw new Error(`msteams proactive send blocked for ${params.conversationId}: stored conversation serviceUrl (${stored.value}) requires channels.msteams.cloud=China.`);
540
540
  if (stored.host !== PUBLIC_MSTEAMS_SERVICE_HOST) throw new Error(`msteams proactive send blocked for ${params.conversationId}: stored conversation serviceUrl (${stored.value}) is not a Microsoft Teams public-cloud Bot Connector endpoint. Set channels.msteams.cloud and channels.msteams.serviceUrl for the supported Teams cloud that owns this conversation.`);
541
541
  }
542
+ //#endregion
543
+ //#region extensions/msteams/src/http-error.ts
544
+ async function createMSTeamsHttpError(response, label, options) {
545
+ return await createProviderHttpError(response, label, options);
546
+ }
542
547
  const BOT_FRAMEWORK_SERVICE_URL_HOST_ALLOWLIST = normalizeHostnameSuffixAllowlist([
543
548
  "smba.trafficmanager.net",
544
549
  "smba.infra.gcc.teams.microsoft.com",
@@ -728,6 +733,129 @@ function readAccessToken(value) {
728
733
  return null;
729
734
  }
730
735
  //#endregion
736
+ //#region extensions/msteams/src/oauth.shared.ts
737
+ const MSTEAMS_OAUTH_REDIRECT_URI = "http://localhost:8086/oauth2callback";
738
+ const MSTEAMS_OAUTH_CALLBACK_PORT = 8086;
739
+ const MSTEAMS_OAUTH_CALLBACK_PATH = "/oauth2callback";
740
+ const MSTEAMS_DEFAULT_TOKEN_FETCH_TIMEOUT_MS = 1e4;
741
+ const MSTEAMS_DEFAULT_DELEGATED_SCOPES = [
742
+ "ChatMessage.Send",
743
+ "ChannelMessage.Send",
744
+ "Chat.ReadWrite",
745
+ "offline_access"
746
+ ];
747
+ function buildMSTeamsAuthEndpoint(tenantId) {
748
+ return `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/authorize`;
749
+ }
750
+ function buildMSTeamsTokenEndpoint(tenantId) {
751
+ return `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`;
752
+ }
753
+ //#endregion
754
+ //#region extensions/msteams/src/oauth.token.ts
755
+ /** Five-minute buffer subtracted from token expiry to avoid edge-case clock drift. */
756
+ const EXPIRY_BUFFER_MS = 300 * 1e3;
757
+ function createMSTeamsTokenBody(params) {
758
+ const body = new URLSearchParams({
759
+ client_id: params.clientId,
760
+ client_secret: params.clientSecret,
761
+ grant_type: params.grantType,
762
+ scope: [...params.scopes].join(" ")
763
+ });
764
+ for (const [key, value] of Object.entries(params.values ?? {})) body.set(key, value);
765
+ return body;
766
+ }
767
+ function resolveMSTeamsTokenExpiresAt(value) {
768
+ return resolveExpiresAtMsFromDurationSeconds(value, { bufferMs: EXPIRY_BUFFER_MS });
769
+ }
770
+ function parseMSTeamsTokenResponse(data, failureLabel) {
771
+ const expiresAt = resolveMSTeamsTokenExpiresAt(data.expires_in);
772
+ if (typeof data.access_token !== "string" || !data.access_token || expiresAt === void 0 || data.refresh_token !== void 0 && typeof data.refresh_token !== "string" || data.scope !== void 0 && typeof data.scope !== "string") throw new Error(`MSTeams ${failureLabel} failed: invalid token response fields`);
773
+ return {
774
+ access_token: data.access_token,
775
+ refresh_token: data.refresh_token,
776
+ expiresAt,
777
+ scope: data.scope
778
+ };
779
+ }
780
+ async function fetchMSTeamsTokens(params) {
781
+ const currentFetch = globalThis.fetch;
782
+ const { response, release } = await fetchWithSsrFGuard({
783
+ url: params.tokenUrl,
784
+ fetchImpl: async (input, guardedInit) => await currentFetch(input, guardedInit),
785
+ init: {
786
+ method: "POST",
787
+ headers: {
788
+ "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
789
+ Accept: "application/json"
790
+ },
791
+ body: params.body,
792
+ signal: AbortSignal.timeout(MSTEAMS_DEFAULT_TOKEN_FETCH_TIMEOUT_MS)
793
+ },
794
+ auditContext: params.auditContext
795
+ });
796
+ try {
797
+ if (!response.ok) throw await createMSTeamsHttpError(response, `MSTeams ${params.failureLabel} failed`);
798
+ return parseMSTeamsTokenResponse(await readProviderJsonResponse(response, `MSTeams ${params.failureLabel} failed`), params.failureLabel);
799
+ } finally {
800
+ await release();
801
+ }
802
+ }
803
+ async function requestMSTeamsDelegatedTokens(params) {
804
+ const scopes = params.scopes ?? MSTEAMS_DEFAULT_DELEGATED_SCOPES;
805
+ const body = createMSTeamsTokenBody({
806
+ clientId: params.clientId,
807
+ clientSecret: params.clientSecret,
808
+ grantType: params.grantType,
809
+ scopes,
810
+ values: params.values
811
+ });
812
+ const data = await fetchMSTeamsTokens({
813
+ tokenUrl: buildMSTeamsTokenEndpoint(params.tenantId),
814
+ body,
815
+ auditContext: params.auditContext,
816
+ failureLabel: params.failureLabel
817
+ });
818
+ return {
819
+ accessToken: data.access_token,
820
+ refreshToken: params.resolveRefreshToken(data),
821
+ expiresAt: data.expiresAt,
822
+ scopes: data.scope ? data.scope.split(" ") : [...scopes]
823
+ };
824
+ }
825
+ async function exchangeMSTeamsCodeForTokens(params) {
826
+ return await requestMSTeamsDelegatedTokens({
827
+ tenantId: params.tenantId,
828
+ clientId: params.clientId,
829
+ clientSecret: params.clientSecret,
830
+ grantType: "authorization_code",
831
+ scopes: params.scopes,
832
+ values: {
833
+ code: params.code,
834
+ redirect_uri: MSTEAMS_OAUTH_REDIRECT_URI,
835
+ code_verifier: params.verifier
836
+ },
837
+ auditContext: "msteams-oauth-token-exchange",
838
+ failureLabel: "token exchange",
839
+ resolveRefreshToken: (data) => {
840
+ if (!data.refresh_token) throw new Error("No refresh token received from Azure AD. Please try again.");
841
+ return data.refresh_token;
842
+ }
843
+ });
844
+ }
845
+ async function refreshMSTeamsDelegatedTokens(params) {
846
+ return await requestMSTeamsDelegatedTokens({
847
+ tenantId: params.tenantId,
848
+ clientId: params.clientId,
849
+ clientSecret: params.clientSecret,
850
+ grantType: "refresh_token",
851
+ scopes: params.scopes,
852
+ values: { refresh_token: params.refreshToken },
853
+ auditContext: "msteams-oauth-token-refresh",
854
+ failureLabel: "token refresh",
855
+ resolveRefreshToken: (data) => data.refresh_token ?? params.refreshToken
856
+ });
857
+ }
858
+ //#endregion
731
859
  //#region extensions/msteams/src/storage.ts
732
860
  function resolveMSTeamsStorePath(params) {
733
861
  if (params.storePath) return params.storePath;
@@ -1032,179 +1160,219 @@ async function searchGraphUsers(params) {
1032
1160
  })).value ?? [];
1033
1161
  }
1034
1162
  //#endregion
1035
- //#region extensions/msteams/src/errors.ts
1036
- const MAX_SAFE_RETRY_AFTER_SECONDS = Number.MAX_SAFE_INTEGER / 1e3;
1037
- function formatUnknownError(err) {
1038
- if (err instanceof Error) return err.message;
1039
- if (typeof err === "string") return err;
1040
- if (err === null) return "null";
1041
- if (err === void 0) return "undefined";
1042
- if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") return String(err);
1043
- if (typeof err === "symbol") return err.description ?? err.toString();
1044
- if (typeof err === "function") return err.name ? `[function ${err.name}]` : "[function]";
1045
- try {
1046
- return JSON.stringify(err) ?? "unknown error";
1047
- } catch {
1048
- return "unknown error";
1163
+ //#region extensions/msteams/src/resolve-allowlist.ts
1164
+ function stripProviderPrefix(raw) {
1165
+ return raw.replace(/^(msteams|teams):/i, "");
1166
+ }
1167
+ function normalizeMSTeamsMessagingTarget(raw) {
1168
+ let trimmed = raw.trim();
1169
+ if (!trimmed) return;
1170
+ trimmed = stripProviderPrefix(trimmed).trim();
1171
+ if (/^conversation:/i.test(trimmed)) {
1172
+ const id = trimmed.slice(13).trim();
1173
+ return id ? `conversation:${id}` : void 0;
1049
1174
  }
1050
- }
1051
- function extractStatusCode(err) {
1052
- if (!isRecord(err)) return null;
1053
- const parseStatusCode = (value) => {
1054
- if (typeof value === "number") return Number.isInteger(value) && value >= 100 && value <= 599 ? value : null;
1055
- if (typeof value === "string") {
1056
- const trimmed = value.trim();
1057
- if (!/^\d{3}$/.test(trimmed)) return null;
1058
- const parsed = Number(trimmed);
1059
- return parsed >= 100 && parsed <= 599 ? parsed : null;
1060
- }
1061
- return null;
1062
- };
1063
- const directStatus = parseStatusCode(err.statusCode ?? err.status);
1064
- if (directStatus !== null) return directStatus;
1065
- const response = err.response;
1066
- if (isRecord(response)) {
1067
- const responseStatus = parseStatusCode(response.status);
1068
- if (responseStatus !== null) return responseStatus;
1175
+ if (/^user:/i.test(trimmed)) {
1176
+ const id = trimmed.slice(5).trim();
1177
+ return id ? `user:${id}` : void 0;
1069
1178
  }
1070
- return null;
1179
+ return trimmed || void 0;
1071
1180
  }
1072
- function extractErrorCode(err) {
1073
- if (!isRecord(err)) return null;
1074
- const direct = err.code;
1075
- if (typeof direct === "string" && direct.trim()) return direct;
1076
- const response = err.response;
1077
- if (!isRecord(response)) return null;
1078
- const body = response.body;
1079
- if (isRecord(body)) {
1080
- const error = body.error;
1081
- if (isRecord(error) && typeof error.code === "string" && error.code.trim()) return error.code;
1082
- }
1083
- return null;
1181
+ function normalizeMSTeamsUserInput(raw) {
1182
+ return stripProviderPrefix(raw).replace(/^(user|conversation):/i, "").trim();
1084
1183
  }
1085
- function extractRetryAfterMs(err) {
1086
- if (!isRecord(err)) return null;
1087
- const directMs = asFiniteNumberInRange(err.retryAfterMs ?? err.retry_after_ms, {
1088
- min: 0,
1089
- max: Number.MAX_SAFE_INTEGER
1090
- });
1091
- if (directMs !== void 0) return directMs;
1092
- const retryAfter = err.retryAfter ?? err.retry_after;
1093
- const retryAfterSeconds = asFiniteNumberInRange(retryAfter, {
1094
- min: 0,
1095
- max: MAX_SAFE_RETRY_AFTER_SECONDS
1096
- });
1097
- if (retryAfterSeconds !== void 0) return retryAfterSeconds * 1e3;
1098
- if (typeof retryAfter === "string") {
1099
- const parsed = parseNonNegativeRetryAfterSeconds(retryAfter);
1100
- if (parsed !== void 0) return parsed * 1e3;
1101
- }
1102
- const response = err.response;
1103
- if (!isRecord(response)) return null;
1104
- const headers = response.headers;
1105
- if (!headers) return null;
1106
- if (isRecord(headers)) {
1107
- const raw = headers["retry-after"] ?? headers["Retry-After"];
1108
- if (typeof raw === "string") {
1109
- const parsed = parseNonNegativeRetryAfterSeconds(raw);
1110
- if (parsed !== void 0) return parsed * 1e3;
1111
- }
1112
- }
1113
- if (typeof headers === "object" && headers !== null && "get" in headers && typeof headers.get === "function") {
1114
- const raw = headers.get("retry-after");
1115
- if (raw) {
1116
- const parsed = parseNonNegativeRetryAfterSeconds(raw);
1117
- if (parsed !== void 0) return parsed * 1e3;
1118
- }
1119
- }
1120
- return null;
1121
- }
1122
- function parseNonNegativeRetryAfterSeconds(raw) {
1123
- const trimmed = raw.trim();
1124
- if (!/^\d+(?:\.\d+)?$/.test(trimmed)) return;
1125
- return asFiniteNumberInRange(parseStrictFiniteNumber(trimmed), {
1126
- min: 0,
1127
- max: MAX_SAFE_RETRY_AFTER_SECONDS
1128
- });
1184
+ function parseMSTeamsConversationId(raw) {
1185
+ const trimmed = stripProviderPrefix(raw).trim();
1186
+ if (!/^conversation:/i.test(trimmed)) return null;
1187
+ return trimmed.slice(13).trim();
1129
1188
  }
1130
1189
  /**
1131
- * Classify outbound send errors for safe retries and actionable logs.
1190
+ * Detect whether a raw target string looks like a Microsoft Teams conversation
1191
+ * or user id that cron announce delivery and other explicit-target paths can
1192
+ * forward verbatim to the channel adapter.
1132
1193
  *
1133
- * Important: We only mark errors as retryable when we have an explicit HTTP
1134
- * status code that indicates the message was not accepted (e.g. 429, 5xx).
1135
- * For transport-level errors where delivery is ambiguous, we prefer to avoid
1136
- * retries to reduce the chance of duplicate posts.
1194
+ * Accepts both prefixed and bare formats:
1195
+ * - `conversation:<id>` explicit conversation prefix
1196
+ * - `user:<aad-guid>` — user id (16+ hex chars, UUID-like)
1197
+ * - `19:abc@thread.tacv2` / `19:abc@thread.skype` channel / legacy group
1198
+ * - `19:{userId}_{appId}@unq.gbl.spaces` — Graph 1:1 chat thread format
1199
+ * - `a:1xxx` — Bot Framework personal (1:1) chat id
1200
+ * - `8:orgid:xxx` — Bot Framework org-scoped personal chat id
1201
+ * - `29:xxx` — Bot Framework user id
1202
+ *
1203
+ * Display-name user targets such as `user:John Smith` intentionally return
1204
+ * false so that the Graph API directory lookup still runs for them.
1137
1205
  */
1138
- function classifyMSTeamsSendError(err) {
1139
- const statusCode = extractStatusCode(err);
1140
- const retryAfterMs = extractRetryAfterMs(err);
1141
- const errorCode = extractErrorCode(err) ?? void 0;
1142
- if (statusCode === 401) return {
1143
- kind: "auth",
1144
- statusCode,
1145
- errorCode
1146
- };
1147
- if (statusCode === 403) {
1148
- if (errorCode === "ContentStreamNotAllowed") return {
1149
- kind: "permanent",
1150
- statusCode,
1151
- errorCode
1152
- };
1153
- return {
1154
- kind: "auth",
1155
- statusCode,
1156
- errorCode
1157
- };
1206
+ function looksLikeMSTeamsTargetId(raw) {
1207
+ const trimmed = raw.trim();
1208
+ if (!trimmed) return false;
1209
+ if (/^conversation:/i.test(trimmed)) return true;
1210
+ if (/^user:/i.test(trimmed)) {
1211
+ const id = trimmed.slice(5).trim();
1212
+ return /^[0-9a-fA-F-]{16,}$/.test(id);
1158
1213
  }
1159
- if (statusCode === 429) return {
1160
- kind: "throttled",
1161
- statusCode,
1162
- retryAfterMs: retryAfterMs ?? void 0,
1163
- errorCode
1164
- };
1165
- if (statusCode === 408 || statusCode != null && statusCode >= 500) return {
1166
- kind: "transient",
1167
- statusCode,
1168
- retryAfterMs: retryAfterMs ?? void 0,
1169
- errorCode
1170
- };
1171
- if (statusCode != null && statusCode >= 400) return {
1172
- kind: "permanent",
1173
- statusCode,
1174
- errorCode
1214
+ if (/^19:.+@thread\.(tacv2|skype)$/i.test(trimmed)) return true;
1215
+ if (/^19:.+@unq\.gbl\.spaces$/i.test(trimmed)) return true;
1216
+ if (/^a:1[A-Za-z0-9_-]+$/i.test(trimmed)) return true;
1217
+ if (/^8:orgid:[A-Za-z0-9-]+$/i.test(trimmed)) return true;
1218
+ if (/^29:[A-Za-z0-9_-]+$/i.test(trimmed)) return true;
1219
+ return /@thread\b/i.test(trimmed);
1220
+ }
1221
+ function normalizeMSTeamsTeamKey(raw) {
1222
+ return stripProviderPrefix(raw).replace(/^team:/i, "").trim() || void 0;
1223
+ }
1224
+ function normalizeMSTeamsChannelKey(raw) {
1225
+ return (raw?.trim().replace(/^#/, "").trim() ?? "") || void 0;
1226
+ }
1227
+ function normalizeMSTeamsConversationTargetId(raw) {
1228
+ const trimmed = stripProviderPrefix(raw).trim();
1229
+ return parseMSTeamsConversationId(trimmed) ?? trimmed;
1230
+ }
1231
+ function looksLikeMSTeamsThreadConversationId(raw) {
1232
+ const normalized = normalizeMSTeamsConversationTargetId(raw);
1233
+ return /^19:.+@thread\./i.test(normalized);
1234
+ }
1235
+ function parseMSTeamsTeamChannelInput(raw) {
1236
+ const trimmed = stripProviderPrefix(raw).trim();
1237
+ if (!trimmed) return {};
1238
+ const parts = trimmed.split("/");
1239
+ const team = normalizeMSTeamsTeamKey(parts[0] ?? "");
1240
+ const channel = parts.length > 1 ? normalizeMSTeamsChannelKey(parts.slice(1).join("/")) : void 0;
1241
+ return {
1242
+ ...team ? { team } : {},
1243
+ ...channel ? { channel } : {}
1175
1244
  };
1176
- if (statusCode == null) {
1177
- const networkCode = isRecord(err) && typeof err.code === "string" ? err.code : null;
1178
- if (networkCode === "ECONNREFUSED" || networkCode === "ENOTFOUND" || networkCode === "EHOSTUNREACH" || networkCode === "ETIMEDOUT" || networkCode === "ECONNRESET") return {
1179
- kind: "network",
1180
- errorCode: networkCode
1181
- };
1182
- }
1245
+ }
1246
+ function parseMSTeamsTeamEntry(raw) {
1247
+ const { team, channel } = parseMSTeamsTeamChannelInput(raw);
1248
+ if (!team) return null;
1183
1249
  return {
1184
- kind: "unknown",
1185
- statusCode: statusCode ?? void 0,
1186
- retryAfterMs: retryAfterMs ?? void 0,
1187
- errorCode
1250
+ teamKey: team,
1251
+ ...channel ? { channelKey: channel } : {}
1188
1252
  };
1189
1253
  }
1190
- /**
1191
- * Detect whether an error is caused by a revoked Proxy.
1192
- *
1193
- * The Bot Framework SDK wraps TurnContext in a Proxy that is revoked once the
1194
- * turn handler returns. Any later access (e.g. from a debounced callback)
1195
- * throws a TypeError whose message contains the distinctive "proxy that has
1196
- * been revoked" string.
1197
- */
1198
- function isRevokedProxyError(err) {
1199
- if (!(err instanceof TypeError)) return false;
1200
- return /proxy that has been revoked/i.test(err.message);
1254
+ async function resolveMSTeamsChannelAllowlist(params) {
1255
+ let tokenPromise;
1256
+ const getToken = () => {
1257
+ tokenPromise ??= resolveGraphToken(params.cfg);
1258
+ return tokenPromise;
1259
+ };
1260
+ return await mapAllowlistResolutionInputs({
1261
+ inputs: params.entries,
1262
+ mapInput: async (input) => {
1263
+ const { team, channel } = parseMSTeamsTeamChannelInput(input);
1264
+ if (!team) return {
1265
+ input,
1266
+ resolved: false
1267
+ };
1268
+ if (looksLikeMSTeamsThreadConversationId(team)) {
1269
+ const teamId = normalizeMSTeamsConversationTargetId(team);
1270
+ if (!channel) return {
1271
+ input,
1272
+ resolved: true,
1273
+ teamId,
1274
+ teamName: teamId
1275
+ };
1276
+ if (!looksLikeMSTeamsThreadConversationId(channel)) return {
1277
+ input,
1278
+ resolved: false,
1279
+ teamId,
1280
+ teamName: teamId,
1281
+ note: "channel id required for conversation-id team"
1282
+ };
1283
+ const channelId = normalizeMSTeamsConversationTargetId(channel);
1284
+ return {
1285
+ input,
1286
+ resolved: true,
1287
+ teamId,
1288
+ teamName: teamId,
1289
+ channelId,
1290
+ channelName: channelId
1291
+ };
1292
+ }
1293
+ const token = await getToken();
1294
+ const teams = /^[0-9a-fA-F-]{16,}$/.test(team) ? [{
1295
+ id: team,
1296
+ displayName: team
1297
+ }] : await listTeamsByName(token, team);
1298
+ if (teams.length === 0) return {
1299
+ input,
1300
+ resolved: false,
1301
+ note: "team not found"
1302
+ };
1303
+ const teamMatch = teams[0];
1304
+ const graphTeamId = teamMatch.id?.trim();
1305
+ const teamName = teamMatch.displayName?.trim() || team;
1306
+ if (!graphTeamId) return {
1307
+ input,
1308
+ resolved: false,
1309
+ note: "team id missing"
1310
+ };
1311
+ let teamChannels = [];
1312
+ try {
1313
+ teamChannels = await listChannelsForTeam(token, graphTeamId);
1314
+ } catch {}
1315
+ const teamId = teamChannels.find((ch) => normalizeOptionalLowercaseString(ch.displayName) === "general")?.id?.trim() || graphTeamId;
1316
+ if (!channel) return {
1317
+ input,
1318
+ resolved: true,
1319
+ teamId,
1320
+ teamName,
1321
+ note: teams.length > 1 ? "multiple teams; chose first" : void 0
1322
+ };
1323
+ const normalizedChannel = normalizeOptionalLowercaseString(channel);
1324
+ const channelMatch = teamChannels.find((item) => item.id === channel) ?? teamChannels.find((item) => normalizeOptionalLowercaseString(item.displayName) === normalizedChannel) ?? teamChannels.find((item) => normalizeLowercaseStringOrEmpty(item.displayName ?? "").includes(normalizedChannel ?? ""));
1325
+ if (!channelMatch?.id) return {
1326
+ input,
1327
+ resolved: false,
1328
+ note: "channel not found"
1329
+ };
1330
+ return {
1331
+ input,
1332
+ resolved: true,
1333
+ teamId,
1334
+ teamName,
1335
+ channelId: channelMatch.id,
1336
+ channelName: channelMatch.displayName ?? channel,
1337
+ note: teamChannels.length > 1 ? "multiple channels; chose first" : void 0
1338
+ };
1339
+ }
1340
+ });
1201
1341
  }
1202
- function formatMSTeamsSendErrorHint(classification) {
1203
- if (classification.kind === "auth") return "check msteams appId/appPassword/tenantId (or env vars MSTEAMS_APP_ID/MSTEAMS_APP_PASSWORD/MSTEAMS_TENANT_ID)";
1204
- if (classification.errorCode === "ContentStreamNotAllowed") return "Teams expired the content stream; stop streaming earlier and fall back to normal message delivery";
1205
- if (classification.kind === "throttled") return "Teams throttled the bot; backing off may help";
1206
- if (classification.kind === "transient") return "transient Teams/Bot Framework error; retry may succeed";
1207
- if (classification.kind === "network") return "transport-level failure sending reply to Teams Bot Connector (smba.trafficmanager.net) — check egress firewall rules allow outbound HTTPS to smba.trafficmanager.net";
1342
+ async function resolveMSTeamsUserAllowlist(params) {
1343
+ const token = await resolveGraphToken(params.cfg);
1344
+ return await mapAllowlistResolutionInputs({
1345
+ inputs: params.entries,
1346
+ mapInput: async (input) => {
1347
+ const query = normalizeQuery(normalizeMSTeamsUserInput(input));
1348
+ if (!query) return {
1349
+ input,
1350
+ resolved: false
1351
+ };
1352
+ if (/^[0-9a-fA-F-]{16,}$/.test(query)) return {
1353
+ input,
1354
+ resolved: true,
1355
+ id: query
1356
+ };
1357
+ const users = await searchGraphUsers({
1358
+ token,
1359
+ query,
1360
+ top: 10
1361
+ });
1362
+ const match = users[0];
1363
+ if (!match?.id) return {
1364
+ input,
1365
+ resolved: false
1366
+ };
1367
+ return {
1368
+ input,
1369
+ resolved: true,
1370
+ id: match.id,
1371
+ name: match.displayName ?? void 0,
1372
+ note: users.length > 1 ? "multiple matches; chose first" : void 0
1373
+ };
1374
+ }
1375
+ });
1208
1376
  }
1209
1377
  //#endregion
1210
- export { safeFetchWithPolicy as $, isAllowedBotFrameworkServiceUrl as A, estimateBase64DecodedBytes as B, readAccessToken as C, buildUserAgent as D, loadMSTeamsSdkWithAuth as E, ATTACHMENT_TAG_RE as F, isLikelyImageAttachment as G, extractInlineImageCandidates as H, GRAPH_ROOT as I, normalizeContentType as J, isRecord$1 as K, IMG_SRC_RE as L, tryNormalizeBotFrameworkServiceUrl as M, resolveMSTeamsSdkCloudOptions as N, ensureUserAgentHeader as O, validateMSTeamsProactiveServiceUrlBoundary as P, resolveRequestUrl as Q, applyAuthorizationHeaderForUrl as R, normalizeSecretInputString as S, createMSTeamsTokenProvider as T, inferPlaceholder as U, extractHtmlFromAttachment as V, isDownloadableAttachment as W, resolveAttachmentFetchPolicy as X, readNestedString as Y, resolveMediaSsrfPolicy as Z, hasConfiguredMSTeamsCredentials as _, searchGraphUsers as a, saveDelegatedTokens as b, fetchGraphAbsoluteUrl as c, listTeamsByName as d, safeHostForUrl as et, normalizeQuery as f, resolveGraphToken as g, postGraphJson as h, isRevokedProxyError as i, normalizeBotFrameworkServiceUrl as j, describeBotFrameworkServiceUrlHost as k, fetchGraphJson as l, postGraphBetaJson as m, formatMSTeamsSendErrorHint as n, deleteGraphRequest as o, patchGraphJson as p, isUrlAllowed as q, formatUnknownError as r, escapeOData as s, classifyMSTeamsSendError as t, tryBuildGraphSharesUrlForSharedLink as tt, listChannelsForTeam as u, loadDelegatedTokens as v, createMSTeamsExpressAdapter as w, resolveMSTeamsStorePath as x, resolveMSTeamsCredentials as y, encodeGraphShareId as z };
1378
+ export { extractInlineImageCandidates as $, MSTEAMS_OAUTH_CALLBACK_PORT as A, isAllowedBotFrameworkServiceUrl as B, resolveMSTeamsCredentials as C, exchangeMSTeamsCodeForTokens as D, normalizeSecretInputString as E, createMSTeamsTokenProvider as F, validateMSTeamsProactiveServiceUrlBoundary as G, tryNormalizeBotFrameworkServiceUrl as H, loadMSTeamsSdkWithAuth as I, IMG_SRC_RE as J, ATTACHMENT_TAG_RE as K, buildUserAgent as L, buildMSTeamsAuthEndpoint as M, readAccessToken as N, MSTEAMS_DEFAULT_DELEGATED_SCOPES as O, createMSTeamsExpressAdapter as P, extractHtmlFromAttachment as Q, ensureUserAgentHeader as R, loadDelegatedTokens as S, resolveMSTeamsStorePath as T, createMSTeamsHttpError as U, normalizeBotFrameworkServiceUrl as V, resolveMSTeamsSdkCloudOptions as W, encodeGraphShareId as X, applyAuthorizationHeaderForUrl as Y, estimateBase64DecodedBytes as Z, patchGraphJson as _, parseMSTeamsTeamChannelInput as a, normalizeContentType as at, resolveGraphToken as b, resolveMSTeamsUserAllowlist as c, resolveMediaSsrfPolicy as ct, escapeOData as d, safeHostForUrl as dt, inferPlaceholder as et, fetchGraphAbsoluteUrl as f, tryBuildGraphSharesUrlForSharedLink as ft, normalizeQuery as g, listTeamsByName as h, parseMSTeamsConversationId as i, isUrlAllowed as it, MSTEAMS_OAUTH_REDIRECT_URI as j, MSTEAMS_OAUTH_CALLBACK_PATH as k, searchGraphUsers as l, resolveRequestUrl as lt, listChannelsForTeam as m, normalizeMSTeamsMessagingTarget as n, isLikelyImageAttachment as nt, parseMSTeamsTeamEntry as o, readNestedString as ot, fetchGraphJson as p, GRAPH_ROOT as q, normalizeMSTeamsUserInput as r, isRecord$1 as rt, resolveMSTeamsChannelAllowlist as s, resolveAttachmentFetchPolicy as st, looksLikeMSTeamsTargetId as t, isDownloadableAttachment as tt, deleteGraphRequest as u, safeFetchWithPolicy as ut, postGraphBetaJson as v, saveDelegatedTokens as w, hasConfiguredMSTeamsCredentials as x, postGraphJson as y, describeBotFrameworkServiceUrlHost as z };
@@ -1,6 +1,6 @@
1
- import { y as resolveMSTeamsCredentials } from "./errors-Dpn8B05h.js";
2
- import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-DGTz8Mlf.js";
1
+ import { C as resolveMSTeamsCredentials } from "./resolve-allowlist-C6VVTfbj.js";
3
2
  import { t as MSTeamsChannelConfigSchema } from "./config-schema-BL4qQZiA.js";
3
+ import { c as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-DdyMxq-W.js";
4
4
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
5
5
  import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
6
6
  import { createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
@@ -1,223 +1,8 @@
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
- import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/allow-from";
3
- import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
1
+ import { C as resolveMSTeamsCredentials, E as normalizeSecretInputString, c as resolveMSTeamsUserAllowlist, o as parseMSTeamsTeamEntry, s as resolveMSTeamsChannelAllowlist, w as saveDelegatedTokens, x as hasConfiguredMSTeamsCredentials } from "./resolve-allowlist-C6VVTfbj.js";
2
+ import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ import { asFiniteNumberInRange, parseStrictFiniteNumber } from "openclaw/plugin-sdk/number-runtime";
4
4
  import { DEFAULT_ACCOUNT_ID, createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicy, createTopLevelChannelGroupPolicySetter, mergeAllowFromEntries, splitSetupEntries } from "openclaw/plugin-sdk/setup";
5
5
  import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
6
- //#region extensions/msteams/src/resolve-allowlist.ts
7
- function stripProviderPrefix(raw) {
8
- return raw.replace(/^(msteams|teams):/i, "");
9
- }
10
- function normalizeMSTeamsMessagingTarget(raw) {
11
- let trimmed = raw.trim();
12
- if (!trimmed) return;
13
- trimmed = stripProviderPrefix(trimmed).trim();
14
- if (/^conversation:/i.test(trimmed)) {
15
- const id = trimmed.slice(13).trim();
16
- return id ? `conversation:${id}` : void 0;
17
- }
18
- if (/^user:/i.test(trimmed)) {
19
- const id = trimmed.slice(5).trim();
20
- return id ? `user:${id}` : void 0;
21
- }
22
- return trimmed || void 0;
23
- }
24
- function normalizeMSTeamsUserInput(raw) {
25
- return stripProviderPrefix(raw).replace(/^(user|conversation):/i, "").trim();
26
- }
27
- function parseMSTeamsConversationId(raw) {
28
- const trimmed = stripProviderPrefix(raw).trim();
29
- if (!/^conversation:/i.test(trimmed)) return null;
30
- return trimmed.slice(13).trim();
31
- }
32
- /**
33
- * Detect whether a raw target string looks like a Microsoft Teams conversation
34
- * or user id that cron announce delivery and other explicit-target paths can
35
- * forward verbatim to the channel adapter.
36
- *
37
- * Accepts both prefixed and bare formats:
38
- * - `conversation:<id>` — explicit conversation prefix
39
- * - `user:<aad-guid>` — user id (16+ hex chars, UUID-like)
40
- * - `19:abc@thread.tacv2` / `19:abc@thread.skype` — channel / legacy group
41
- * - `19:{userId}_{appId}@unq.gbl.spaces` — Graph 1:1 chat thread format
42
- * - `a:1xxx` — Bot Framework personal (1:1) chat id
43
- * - `8:orgid:xxx` — Bot Framework org-scoped personal chat id
44
- * - `29:xxx` — Bot Framework user id
45
- *
46
- * Display-name user targets such as `user:John Smith` intentionally return
47
- * false so that the Graph API directory lookup still runs for them.
48
- */
49
- function looksLikeMSTeamsTargetId(raw) {
50
- const trimmed = raw.trim();
51
- if (!trimmed) return false;
52
- if (/^conversation:/i.test(trimmed)) return true;
53
- if (/^user:/i.test(trimmed)) {
54
- const id = trimmed.slice(5).trim();
55
- return /^[0-9a-fA-F-]{16,}$/.test(id);
56
- }
57
- if (/^19:.+@thread\.(tacv2|skype)$/i.test(trimmed)) return true;
58
- if (/^19:.+@unq\.gbl\.spaces$/i.test(trimmed)) return true;
59
- if (/^a:1[A-Za-z0-9_-]+$/i.test(trimmed)) return true;
60
- if (/^8:orgid:[A-Za-z0-9-]+$/i.test(trimmed)) return true;
61
- if (/^29:[A-Za-z0-9_-]+$/i.test(trimmed)) return true;
62
- return /@thread\b/i.test(trimmed);
63
- }
64
- function normalizeMSTeamsTeamKey(raw) {
65
- return stripProviderPrefix(raw).replace(/^team:/i, "").trim() || void 0;
66
- }
67
- function normalizeMSTeamsChannelKey(raw) {
68
- return (raw?.trim().replace(/^#/, "").trim() ?? "") || void 0;
69
- }
70
- function normalizeMSTeamsConversationTargetId(raw) {
71
- const trimmed = stripProviderPrefix(raw).trim();
72
- return parseMSTeamsConversationId(trimmed) ?? trimmed;
73
- }
74
- function looksLikeMSTeamsThreadConversationId(raw) {
75
- const normalized = normalizeMSTeamsConversationTargetId(raw);
76
- return /^19:.+@thread\./i.test(normalized);
77
- }
78
- function parseMSTeamsTeamChannelInput(raw) {
79
- const trimmed = stripProviderPrefix(raw).trim();
80
- if (!trimmed) return {};
81
- const parts = trimmed.split("/");
82
- const team = normalizeMSTeamsTeamKey(parts[0] ?? "");
83
- const channel = parts.length > 1 ? normalizeMSTeamsChannelKey(parts.slice(1).join("/")) : void 0;
84
- return {
85
- ...team ? { team } : {},
86
- ...channel ? { channel } : {}
87
- };
88
- }
89
- function parseMSTeamsTeamEntry(raw) {
90
- const { team, channel } = parseMSTeamsTeamChannelInput(raw);
91
- if (!team) return null;
92
- return {
93
- teamKey: team,
94
- ...channel ? { channelKey: channel } : {}
95
- };
96
- }
97
- async function resolveMSTeamsChannelAllowlist(params) {
98
- let tokenPromise;
99
- const getToken = () => {
100
- tokenPromise ??= resolveGraphToken(params.cfg);
101
- return tokenPromise;
102
- };
103
- return await mapAllowlistResolutionInputs({
104
- inputs: params.entries,
105
- mapInput: async (input) => {
106
- const { team, channel } = parseMSTeamsTeamChannelInput(input);
107
- if (!team) return {
108
- input,
109
- resolved: false
110
- };
111
- if (looksLikeMSTeamsThreadConversationId(team)) {
112
- const teamId = normalizeMSTeamsConversationTargetId(team);
113
- if (!channel) return {
114
- input,
115
- resolved: true,
116
- teamId,
117
- teamName: teamId
118
- };
119
- if (!looksLikeMSTeamsThreadConversationId(channel)) return {
120
- input,
121
- resolved: false,
122
- teamId,
123
- teamName: teamId,
124
- note: "channel id required for conversation-id team"
125
- };
126
- const channelId = normalizeMSTeamsConversationTargetId(channel);
127
- return {
128
- input,
129
- resolved: true,
130
- teamId,
131
- teamName: teamId,
132
- channelId,
133
- channelName: channelId
134
- };
135
- }
136
- const token = await getToken();
137
- const teams = /^[0-9a-fA-F-]{16,}$/.test(team) ? [{
138
- id: team,
139
- displayName: team
140
- }] : await listTeamsByName(token, team);
141
- if (teams.length === 0) return {
142
- input,
143
- resolved: false,
144
- note: "team not found"
145
- };
146
- const teamMatch = teams[0];
147
- const graphTeamId = teamMatch.id?.trim();
148
- const teamName = teamMatch.displayName?.trim() || team;
149
- if (!graphTeamId) return {
150
- input,
151
- resolved: false,
152
- note: "team id missing"
153
- };
154
- let teamChannels = [];
155
- try {
156
- teamChannels = await listChannelsForTeam(token, graphTeamId);
157
- } catch {}
158
- const teamId = teamChannels.find((ch) => normalizeOptionalLowercaseString(ch.displayName) === "general")?.id?.trim() || graphTeamId;
159
- if (!channel) return {
160
- input,
161
- resolved: true,
162
- teamId,
163
- teamName,
164
- note: teams.length > 1 ? "multiple teams; chose first" : void 0
165
- };
166
- const normalizedChannel = normalizeOptionalLowercaseString(channel);
167
- const channelMatch = teamChannels.find((item) => item.id === channel) ?? teamChannels.find((item) => normalizeOptionalLowercaseString(item.displayName) === normalizedChannel) ?? teamChannels.find((item) => normalizeLowercaseStringOrEmpty(item.displayName ?? "").includes(normalizedChannel ?? ""));
168
- if (!channelMatch?.id) return {
169
- input,
170
- resolved: false,
171
- note: "channel not found"
172
- };
173
- return {
174
- input,
175
- resolved: true,
176
- teamId,
177
- teamName,
178
- channelId: channelMatch.id,
179
- channelName: channelMatch.displayName ?? channel,
180
- note: teamChannels.length > 1 ? "multiple channels; chose first" : void 0
181
- };
182
- }
183
- });
184
- }
185
- async function resolveMSTeamsUserAllowlist(params) {
186
- const token = await resolveGraphToken(params.cfg);
187
- return await mapAllowlistResolutionInputs({
188
- inputs: params.entries,
189
- mapInput: async (input) => {
190
- const query = normalizeQuery(normalizeMSTeamsUserInput(input));
191
- if (!query) return {
192
- input,
193
- resolved: false
194
- };
195
- if (/^[0-9a-fA-F-]{16,}$/.test(query)) return {
196
- input,
197
- resolved: true,
198
- id: query
199
- };
200
- const users = await searchGraphUsers({
201
- token,
202
- query,
203
- top: 10
204
- });
205
- const match = users[0];
206
- if (!match?.id) return {
207
- input,
208
- resolved: false
209
- };
210
- return {
211
- input,
212
- resolved: true,
213
- id: match.id,
214
- name: match.displayName ?? void 0,
215
- note: users.length > 1 ? "multiple matches; chose first" : void 0
216
- };
217
- }
218
- });
219
- }
220
- //#endregion
221
6
  //#region extensions/msteams/src/setup-core.ts
222
7
  const t$1 = createSetupTranslator();
223
8
  const msteamsSetupAdapter = {
@@ -321,6 +106,181 @@ function createMSTeamsSetupWizardBase() {
321
106
  };
322
107
  }
323
108
  //#endregion
109
+ //#region extensions/msteams/src/errors.ts
110
+ const MAX_SAFE_RETRY_AFTER_SECONDS = Number.MAX_SAFE_INTEGER / 1e3;
111
+ function formatUnknownError(err) {
112
+ if (err instanceof Error) return err.message;
113
+ if (typeof err === "string") return err;
114
+ if (err === null) return "null";
115
+ if (err === void 0) return "undefined";
116
+ if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") return String(err);
117
+ if (typeof err === "symbol") return err.description ?? err.toString();
118
+ if (typeof err === "function") return err.name ? `[function ${err.name}]` : "[function]";
119
+ try {
120
+ return JSON.stringify(err) ?? "unknown error";
121
+ } catch {
122
+ return "unknown error";
123
+ }
124
+ }
125
+ function extractStatusCode(err) {
126
+ if (!isRecord(err)) return null;
127
+ const parseStatusCode = (value) => {
128
+ if (typeof value === "number") return Number.isInteger(value) && value >= 100 && value <= 599 ? value : null;
129
+ if (typeof value === "string") {
130
+ const trimmed = value.trim();
131
+ if (!/^\d{3}$/.test(trimmed)) return null;
132
+ const parsed = Number(trimmed);
133
+ return parsed >= 100 && parsed <= 599 ? parsed : null;
134
+ }
135
+ return null;
136
+ };
137
+ const directStatus = parseStatusCode(err.statusCode ?? err.status);
138
+ if (directStatus !== null) return directStatus;
139
+ const response = err.response;
140
+ if (isRecord(response)) {
141
+ const responseStatus = parseStatusCode(response.status);
142
+ if (responseStatus !== null) return responseStatus;
143
+ }
144
+ return null;
145
+ }
146
+ function extractErrorCode(err) {
147
+ if (!isRecord(err)) return null;
148
+ const direct = err.code;
149
+ if (typeof direct === "string" && direct.trim()) return direct;
150
+ const response = err.response;
151
+ if (!isRecord(response)) return null;
152
+ const body = response.body;
153
+ if (isRecord(body)) {
154
+ const error = body.error;
155
+ if (isRecord(error) && typeof error.code === "string" && error.code.trim()) return error.code;
156
+ }
157
+ return null;
158
+ }
159
+ function extractRetryAfterMs(err) {
160
+ if (!isRecord(err)) return null;
161
+ const directMs = asFiniteNumberInRange(err.retryAfterMs ?? err.retry_after_ms, {
162
+ min: 0,
163
+ max: Number.MAX_SAFE_INTEGER
164
+ });
165
+ if (directMs !== void 0) return directMs;
166
+ const retryAfter = err.retryAfter ?? err.retry_after;
167
+ const retryAfterSeconds = asFiniteNumberInRange(retryAfter, {
168
+ min: 0,
169
+ max: MAX_SAFE_RETRY_AFTER_SECONDS
170
+ });
171
+ if (retryAfterSeconds !== void 0) return retryAfterSeconds * 1e3;
172
+ if (typeof retryAfter === "string") {
173
+ const parsed = parseNonNegativeRetryAfterSeconds(retryAfter);
174
+ if (parsed !== void 0) return parsed * 1e3;
175
+ }
176
+ const response = err.response;
177
+ if (!isRecord(response)) return null;
178
+ const headers = response.headers;
179
+ if (!headers) return null;
180
+ if (isRecord(headers)) {
181
+ const raw = headers["retry-after"] ?? headers["Retry-After"];
182
+ if (typeof raw === "string") {
183
+ const parsed = parseNonNegativeRetryAfterSeconds(raw);
184
+ if (parsed !== void 0) return parsed * 1e3;
185
+ }
186
+ }
187
+ if (typeof headers === "object" && headers !== null && "get" in headers && typeof headers.get === "function") {
188
+ const raw = headers.get("retry-after");
189
+ if (raw) {
190
+ const parsed = parseNonNegativeRetryAfterSeconds(raw);
191
+ if (parsed !== void 0) return parsed * 1e3;
192
+ }
193
+ }
194
+ return null;
195
+ }
196
+ function parseNonNegativeRetryAfterSeconds(raw) {
197
+ const trimmed = raw.trim();
198
+ if (!/^\d+(?:\.\d+)?$/.test(trimmed)) return;
199
+ return asFiniteNumberInRange(parseStrictFiniteNumber(trimmed), {
200
+ min: 0,
201
+ max: MAX_SAFE_RETRY_AFTER_SECONDS
202
+ });
203
+ }
204
+ /**
205
+ * Classify outbound send errors for safe retries and actionable logs.
206
+ *
207
+ * Important: We only mark errors as retryable when we have an explicit HTTP
208
+ * status code that indicates the message was not accepted (e.g. 429, 5xx).
209
+ * For transport-level errors where delivery is ambiguous, we prefer to avoid
210
+ * retries to reduce the chance of duplicate posts.
211
+ */
212
+ function classifyMSTeamsSendError(err) {
213
+ const statusCode = extractStatusCode(err);
214
+ const retryAfterMs = extractRetryAfterMs(err);
215
+ const errorCode = extractErrorCode(err) ?? void 0;
216
+ if (statusCode === 401) return {
217
+ kind: "auth",
218
+ statusCode,
219
+ errorCode
220
+ };
221
+ if (statusCode === 403) {
222
+ if (errorCode === "ContentStreamNotAllowed") return {
223
+ kind: "permanent",
224
+ statusCode,
225
+ errorCode
226
+ };
227
+ return {
228
+ kind: "auth",
229
+ statusCode,
230
+ errorCode
231
+ };
232
+ }
233
+ if (statusCode === 429) return {
234
+ kind: "throttled",
235
+ statusCode,
236
+ retryAfterMs: retryAfterMs ?? void 0,
237
+ errorCode
238
+ };
239
+ if (statusCode === 408 || statusCode != null && statusCode >= 500) return {
240
+ kind: "transient",
241
+ statusCode,
242
+ retryAfterMs: retryAfterMs ?? void 0,
243
+ errorCode
244
+ };
245
+ if (statusCode != null && statusCode >= 400) return {
246
+ kind: "permanent",
247
+ statusCode,
248
+ errorCode
249
+ };
250
+ if (statusCode == null) {
251
+ const networkCode = isRecord(err) && typeof err.code === "string" ? err.code : null;
252
+ if (networkCode === "ECONNREFUSED" || networkCode === "ENOTFOUND" || networkCode === "EHOSTUNREACH" || networkCode === "ETIMEDOUT" || networkCode === "ECONNRESET") return {
253
+ kind: "network",
254
+ errorCode: networkCode
255
+ };
256
+ }
257
+ return {
258
+ kind: "unknown",
259
+ statusCode: statusCode ?? void 0,
260
+ retryAfterMs: retryAfterMs ?? void 0,
261
+ errorCode
262
+ };
263
+ }
264
+ /**
265
+ * Detect whether an error is caused by a revoked Proxy.
266
+ *
267
+ * The Bot Framework SDK wraps TurnContext in a Proxy that is revoked once the
268
+ * turn handler returns. Any later access (e.g. from a debounced callback)
269
+ * throws a TypeError whose message contains the distinctive "proxy that has
270
+ * been revoked" string.
271
+ */
272
+ function isRevokedProxyError(err) {
273
+ if (!(err instanceof TypeError)) return false;
274
+ return /proxy that has been revoked/i.test(err.message);
275
+ }
276
+ function formatMSTeamsSendErrorHint(classification) {
277
+ if (classification.kind === "auth") return "check msteams appId/appPassword/tenantId (or env vars MSTEAMS_APP_ID/MSTEAMS_APP_PASSWORD/MSTEAMS_TENANT_ID)";
278
+ if (classification.errorCode === "ContentStreamNotAllowed") return "Teams expired the content stream; stop streaming earlier and fall back to normal message delivery";
279
+ if (classification.kind === "throttled") return "Teams throttled the bot; backing off may help";
280
+ if (classification.kind === "transient") return "transient Teams/Bot Framework error; retry may succeed";
281
+ if (classification.kind === "network") return "transport-level failure sending reply to Teams Bot Connector (smba.trafficmanager.net) — check egress firewall rules allow outbound HTTPS to smba.trafficmanager.net";
282
+ }
283
+ //#endregion
324
284
  //#region extensions/msteams/src/setup-surface.ts
325
285
  const t = createSetupTranslator();
326
286
  const channel = "msteams";
@@ -489,7 +449,7 @@ const msteamsSetupWizard = {
489
449
  }
490
450
  };
491
451
  try {
492
- const { loginMSTeamsDelegated } = await import("./oauth-ei63gdyS.js");
452
+ const { loginMSTeamsDelegated } = await import("./oauth-CJj-b_Kn.js");
493
453
  const progress = params.prompter.progress(t("wizard.msteams.delegatedOAuthProgress"));
494
454
  saveDelegatedTokens(await loginMSTeamsDelegated({
495
455
  isRemote: true,
@@ -530,4 +490,4 @@ const msteamsSetupWizard = {
530
490
  })
531
491
  };
532
492
  //#endregion
533
- export { looksLikeMSTeamsTargetId as a, parseMSTeamsConversationId as c, resolveMSTeamsUserAllowlist as d, msteamsSetupAdapter as i, parseMSTeamsTeamChannelInput as l, openDelegatedOAuthUrl as n, normalizeMSTeamsMessagingTarget as o, createMSTeamsSetupWizardBase as r, normalizeMSTeamsUserInput as s, msteamsSetupWizard as t, resolveMSTeamsChannelAllowlist as u };
493
+ export { formatUnknownError as a, msteamsSetupAdapter as c, formatMSTeamsSendErrorHint as i, openDelegatedOAuthUrl as n, isRevokedProxyError as o, classifyMSTeamsSendError as r, createMSTeamsSetupWizardBase as s, msteamsSetupWizard as t };
@@ -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-Dpn8B05h.js";
3
- import { d as resolveMSTeamsUserAllowlist, u as resolveMSTeamsChannelAllowlist } from "./setup-surface-DGTz8Mlf.js";
4
- import { a as resolveMSTeamsReplyPolicy, i as resolveMSTeamsAllowlistMatch, o as resolveMSTeamsRouteConfig } from "./channel-Dhw8AvTl.js";
5
- import { C as resolveMSTeamsSqliteStateEnv, E as readJsonFile, S as createMSTeamsConversationStoreState, T as withMSTeamsSqliteMutationLock, _ 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 toPluginJsonValue, x as extractMSTeamsPollVote, y as uploadToConsentUrl } from "./probe-DYb-0Hmx.js";
2
+ import { $ as extractInlineImageCandidates, C as resolveMSTeamsCredentials, F as createMSTeamsTokenProvider, H as tryNormalizeBotFrameworkServiceUrl, I as loadMSTeamsSdkWithAuth, J as IMG_SRC_RE, K as ATTACHMENT_TAG_RE, P as createMSTeamsExpressAdapter, Q as extractHtmlFromAttachment, R as ensureUserAgentHeader, T as resolveMSTeamsStorePath, W as resolveMSTeamsSdkCloudOptions, X as encodeGraphShareId, Y as applyAuthorizationHeaderForUrl, Z as estimateBase64DecodedBytes, at as normalizeContentType, c as resolveMSTeamsUserAllowlist, ct as resolveMediaSsrfPolicy, dt as safeHostForUrl, et as inferPlaceholder, ft as tryBuildGraphSharesUrlForSharedLink, it as isUrlAllowed, lt as resolveRequestUrl, nt as isLikelyImageAttachment, ot as readNestedString, p as fetchGraphJson, q as GRAPH_ROOT, rt as isRecord$1, s as resolveMSTeamsChannelAllowlist, st as resolveAttachmentFetchPolicy, tt as isDownloadableAttachment, ut as safeFetchWithPolicy } from "./resolve-allowlist-C6VVTfbj.js";
3
+ import { a as resolveMSTeamsReplyPolicy, i as resolveMSTeamsAllowlistMatch, o as resolveMSTeamsRouteConfig } from "./channel-BwNnuHbh.js";
4
+ import { a as formatUnknownError, i as formatMSTeamsSendErrorHint, r as classifyMSTeamsSendError } from "./setup-surface-DdyMxq-W.js";
5
+ import { C as resolveMSTeamsSqliteStateEnv, E as readJsonFile, S as createMSTeamsConversationStoreState, T as withMSTeamsSqliteMutationLock, _ 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 toPluginJsonValue, x as extractMSTeamsPollVote, y as uploadToConsentUrl } from "./probe-bq_Uev4c.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";
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/msteams",
3
- "version": "2026.6.1-beta.3",
3
+ "version": "2026.6.2-beta.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/msteams",
9
- "version": "2026.6.1-beta.3",
9
+ "version": "2026.6.2-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.39"
16
16
  },
17
17
  "peerDependencies": {
18
- "openclaw": ">=2026.6.1-beta.3"
18
+ "openclaw": ">=2026.6.2-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.6.1-beta.3",
3
+ "version": "2026.6.2-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.39"
16
16
  },
17
17
  "peerDependencies": {
18
- "openclaw": ">=2026.6.1-beta.3"
18
+ "openclaw": ">=2026.6.2-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.6.1-beta.3"
54
+ "pluginApi": ">=2026.6.2-beta.1"
55
55
  },
56
56
  "build": {
57
- "openclawVersion": "2026.6.1-beta.3"
57
+ "openclawVersion": "2026.6.2-beta.1"
58
58
  },
59
59
  "release": {
60
60
  "publishToClawHub": true,
@@ -1,132 +0,0 @@
1
- import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
2
- import { createProviderHttpError, readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
3
- import { resolveExpiresAtMsFromDurationSeconds } from "openclaw/plugin-sdk/number-runtime";
4
- //#region extensions/msteams/src/http-error.ts
5
- async function createMSTeamsHttpError(response, label, options) {
6
- return await createProviderHttpError(response, label, options);
7
- }
8
- //#endregion
9
- //#region extensions/msteams/src/oauth.shared.ts
10
- const MSTEAMS_OAUTH_REDIRECT_URI = "http://localhost:8086/oauth2callback";
11
- const MSTEAMS_OAUTH_CALLBACK_PORT = 8086;
12
- const MSTEAMS_OAUTH_CALLBACK_PATH = "/oauth2callback";
13
- const MSTEAMS_DEFAULT_TOKEN_FETCH_TIMEOUT_MS = 1e4;
14
- const MSTEAMS_DEFAULT_DELEGATED_SCOPES = [
15
- "ChatMessage.Send",
16
- "ChannelMessage.Send",
17
- "Chat.ReadWrite",
18
- "offline_access"
19
- ];
20
- function buildMSTeamsAuthEndpoint(tenantId) {
21
- return `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/authorize`;
22
- }
23
- function buildMSTeamsTokenEndpoint(tenantId) {
24
- return `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`;
25
- }
26
- //#endregion
27
- //#region extensions/msteams/src/oauth.token.ts
28
- /** Five-minute buffer subtracted from token expiry to avoid edge-case clock drift. */
29
- const EXPIRY_BUFFER_MS = 300 * 1e3;
30
- function createMSTeamsTokenBody(params) {
31
- const body = new URLSearchParams({
32
- client_id: params.clientId,
33
- client_secret: params.clientSecret,
34
- grant_type: params.grantType,
35
- scope: [...params.scopes].join(" ")
36
- });
37
- for (const [key, value] of Object.entries(params.values ?? {})) body.set(key, value);
38
- return body;
39
- }
40
- function resolveMSTeamsTokenExpiresAt(value) {
41
- return resolveExpiresAtMsFromDurationSeconds(value, { bufferMs: EXPIRY_BUFFER_MS });
42
- }
43
- function parseMSTeamsTokenResponse(data, failureLabel) {
44
- const expiresAt = resolveMSTeamsTokenExpiresAt(data.expires_in);
45
- if (typeof data.access_token !== "string" || !data.access_token || expiresAt === void 0 || data.refresh_token !== void 0 && typeof data.refresh_token !== "string" || data.scope !== void 0 && typeof data.scope !== "string") throw new Error(`MSTeams ${failureLabel} failed: invalid token response fields`);
46
- return {
47
- access_token: data.access_token,
48
- refresh_token: data.refresh_token,
49
- expiresAt,
50
- scope: data.scope
51
- };
52
- }
53
- async function fetchMSTeamsTokens(params) {
54
- const currentFetch = globalThis.fetch;
55
- const { response, release } = await fetchWithSsrFGuard({
56
- url: params.tokenUrl,
57
- fetchImpl: async (input, guardedInit) => await currentFetch(input, guardedInit),
58
- init: {
59
- method: "POST",
60
- headers: {
61
- "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
62
- Accept: "application/json"
63
- },
64
- body: params.body,
65
- signal: AbortSignal.timeout(MSTEAMS_DEFAULT_TOKEN_FETCH_TIMEOUT_MS)
66
- },
67
- auditContext: params.auditContext
68
- });
69
- try {
70
- if (!response.ok) throw await createMSTeamsHttpError(response, `MSTeams ${params.failureLabel} failed`);
71
- return parseMSTeamsTokenResponse(await readProviderJsonResponse(response, `MSTeams ${params.failureLabel} failed`), params.failureLabel);
72
- } finally {
73
- await release();
74
- }
75
- }
76
- async function requestMSTeamsDelegatedTokens(params) {
77
- const scopes = params.scopes ?? MSTEAMS_DEFAULT_DELEGATED_SCOPES;
78
- const body = createMSTeamsTokenBody({
79
- clientId: params.clientId,
80
- clientSecret: params.clientSecret,
81
- grantType: params.grantType,
82
- scopes,
83
- values: params.values
84
- });
85
- const data = await fetchMSTeamsTokens({
86
- tokenUrl: buildMSTeamsTokenEndpoint(params.tenantId),
87
- body,
88
- auditContext: params.auditContext,
89
- failureLabel: params.failureLabel
90
- });
91
- return {
92
- accessToken: data.access_token,
93
- refreshToken: params.resolveRefreshToken(data),
94
- expiresAt: data.expiresAt,
95
- scopes: data.scope ? data.scope.split(" ") : [...scopes]
96
- };
97
- }
98
- async function exchangeMSTeamsCodeForTokens(params) {
99
- return await requestMSTeamsDelegatedTokens({
100
- tenantId: params.tenantId,
101
- clientId: params.clientId,
102
- clientSecret: params.clientSecret,
103
- grantType: "authorization_code",
104
- scopes: params.scopes,
105
- values: {
106
- code: params.code,
107
- redirect_uri: MSTEAMS_OAUTH_REDIRECT_URI,
108
- code_verifier: params.verifier
109
- },
110
- auditContext: "msteams-oauth-token-exchange",
111
- failureLabel: "token exchange",
112
- resolveRefreshToken: (data) => {
113
- if (!data.refresh_token) throw new Error("No refresh token received from Azure AD. Please try again.");
114
- return data.refresh_token;
115
- }
116
- });
117
- }
118
- async function refreshMSTeamsDelegatedTokens(params) {
119
- return await requestMSTeamsDelegatedTokens({
120
- tenantId: params.tenantId,
121
- clientId: params.clientId,
122
- clientSecret: params.clientSecret,
123
- grantType: "refresh_token",
124
- scopes: params.scopes,
125
- values: { refresh_token: params.refreshToken },
126
- auditContext: "msteams-oauth-token-refresh",
127
- failureLabel: "token refresh",
128
- resolveRefreshToken: (data) => data.refresh_token ?? params.refreshToken
129
- });
130
- }
131
- //#endregion
132
- export { MSTEAMS_OAUTH_CALLBACK_PORT as a, createMSTeamsHttpError as c, MSTEAMS_OAUTH_CALLBACK_PATH as i, refreshMSTeamsDelegatedTokens as n, MSTEAMS_OAUTH_REDIRECT_URI as o, MSTEAMS_DEFAULT_DELEGATED_SCOPES as r, buildMSTeamsAuthEndpoint as s, exchangeMSTeamsCodeForTokens as t };