@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 +2 -2
- package/dist/{channel-Dhw8AvTl.js → channel--oT3fyD5.js} +5 -5
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-t52N99bX.js → channel.runtime-KNuY2Oaw.js} +5 -4
- package/dist/directory-contract-api.js +35 -0
- package/dist/doctor-contract-api.js +254 -44
- package/dist/{oauth-ei63gdyS.js → oauth-CDUB5xPY.js} +1 -1
- package/dist/polls-C1VgSvKE.js +548 -0
- package/dist/{probe-DYb-0Hmx.js → probe-C-rWMb-m.js} +10 -742
- package/dist/{errors-Dpn8B05h.js → resolve-allowlist-BzIUWmMm.js} +334 -165
- package/dist/runtime-BS5AZrKK.js +8 -0
- package/dist/runtime-api.js +19 -1
- package/dist/setup-plugin-api.js +2 -2
- package/dist/{setup-surface-DGTz8Mlf.js → setup-surface-Dik4VU7f.js} +180 -220
- package/dist/{src-DZ76sgTp.js → src-CLsoqMj1.js} +9 -124
- package/dist/sso-token-store-BYZaKr82.js +70 -0
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +3 -0
- package/package.json +4 -4
- package/dist/oauth.token-BKzEFepQ.js +0 -132
- package/dist/runtime-api-BlvMnDKz.js +0 -26
|
@@ -1,223 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { C as resolveMSTeamsCredentials, T as normalizeSecretInputString, c as resolveMSTeamsUserAllowlist, o as parseMSTeamsTeamEntry, s as resolveMSTeamsChannelAllowlist, w as saveDelegatedTokens, x as hasConfiguredMSTeamsCredentials } from "./resolve-allowlist-BzIUWmMm.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-
|
|
452
|
+
const { loginMSTeamsDelegated } = await import("./oauth-CDUB5xPY.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 {
|
|
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,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { a as resolveMSTeamsReplyPolicy, i as resolveMSTeamsAllowlistMatch, o as resolveMSTeamsRouteConfig } from "./channel
|
|
5
|
-
import {
|
|
1
|
+
import { n as getOptionalMSTeamsRuntime, t as getMSTeamsRuntime } from "./runtime-BS5AZrKK.js";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, DEFAULT_WEBHOOK_MAX_BODY_BYTES, buildMediaPayload, createChannelMessageReplyPipeline, createChannelPairingController, dispatchReplyFromConfigWithSettledDispatcher as dispatchReplyFromConfigWithSettledDispatcher$1, isDangerousNameMatchingEnabled, keepHttpServerTaskAlive, logTypingFailure, mergeAllowlist, resolveChannelMediaMaxBytes, resolveDefaultGroupPolicy, summarizeMapping } from "./runtime-api.js";
|
|
3
|
+
import { $ as inferPlaceholder, C as resolveMSTeamsCredentials, F as loadMSTeamsSdkWithAuth, G as ATTACHMENT_TAG_RE, J as applyAuthorizationHeaderForUrl, K as GRAPH_ROOT, L as ensureUserAgentHeader, N as createMSTeamsExpressAdapter, P as createMSTeamsTokenProvider, Q as extractInlineImageCandidates, U as resolveMSTeamsSdkCloudOptions, V as tryNormalizeBotFrameworkServiceUrl, X as estimateBase64DecodedBytes, Y as encodeGraphShareId, Z as extractHtmlFromAttachment, at as readNestedString, c as resolveMSTeamsUserAllowlist, ct as resolveRequestUrl, dt as tryBuildGraphSharesUrlForSharedLink, et as isDownloadableAttachment, it as normalizeContentType, lt as safeFetchWithPolicy, nt as isRecord$1, ot as resolveAttachmentFetchPolicy, p as fetchGraphJson, q as IMG_SRC_RE, rt as isUrlAllowed, s as resolveMSTeamsChannelAllowlist, st as resolveMediaSsrfPolicy, tt as isLikelyImageAttachment, ut as safeHostForUrl } from "./resolve-allowlist-BzIUWmMm.js";
|
|
4
|
+
import { a as resolveMSTeamsReplyPolicy, i as resolveMSTeamsAllowlistMatch, o as resolveMSTeamsRouteConfig } from "./channel--oT3fyD5.js";
|
|
5
|
+
import { a as formatUnknownError, i as formatMSTeamsSendErrorHint, r as classifyMSTeamsSendError } from "./setup-surface-Dik4VU7f.js";
|
|
6
|
+
import { l as createMSTeamsPollStoreState, u as extractMSTeamsPollVote, v as createMSTeamsConversationStoreState } from "./polls-C1VgSvKE.js";
|
|
7
|
+
import { i as createMSTeamsSsoTokenStoreFs } from "./sso-token-store-BYZaKr82.js";
|
|
8
|
+
import { _ as buildFileInfoCard, 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, y as uploadToConsentUrl } from "./probe-C-rWMb-m.js";
|
|
6
9
|
import { formatAllowlistMatchMeta } from "openclaw/plugin-sdk/allow-from";
|
|
7
10
|
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelProgressDraftGate, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, normalizeChannelProgressDraftLineIdentity, resolveChannelPreviewStreamMode, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
|
|
8
11
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
@@ -12,8 +15,7 @@ import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
|
12
15
|
import path from "node:path";
|
|
13
16
|
import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
14
17
|
import { appendRegularFile } from "openclaw/plugin-sdk/security-runtime";
|
|
15
|
-
import crypto
|
|
16
|
-
import fs from "node:fs/promises";
|
|
18
|
+
import crypto from "node:crypto";
|
|
17
19
|
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
|
18
20
|
import { channelIngressRoutes, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
19
21
|
import { filterSupplementalContextItems, resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
@@ -3425,123 +3427,6 @@ async function runMSTeamsFileConsentInvokeHandler(context, log) {
|
|
|
3425
3427
|
}
|
|
3426
3428
|
}
|
|
3427
3429
|
//#endregion
|
|
3428
|
-
//#region extensions/msteams/src/sso-token-store.ts
|
|
3429
|
-
/**
|
|
3430
|
-
* SQLite-backed store for Bot Framework OAuth SSO tokens.
|
|
3431
|
-
*
|
|
3432
|
-
* Tokens are keyed by (connectionName, userId). `userId` should be the
|
|
3433
|
-
* stable AAD object ID (`activity.from.aadObjectId`) when available,
|
|
3434
|
-
* falling back to the Bot Framework `activity.from.id`.
|
|
3435
|
-
*
|
|
3436
|
-
* The store is intentionally minimal: it persists the exchanged user
|
|
3437
|
-
* token plus its expiration so consumers (for example tool handlers
|
|
3438
|
-
* that call Microsoft Graph with delegated permissions) can fetch a
|
|
3439
|
-
* valid token without reaching back into Bot Framework every turn.
|
|
3440
|
-
*/
|
|
3441
|
-
const STORE_FILENAME = "msteams-sso-tokens.json";
|
|
3442
|
-
const SSO_TOKENS_NAMESPACE = "sso-tokens";
|
|
3443
|
-
const SSO_TOKEN_MIGRATIONS_NAMESPACE = "sso-token-migrations";
|
|
3444
|
-
const SSO_TOKEN_LOCK_FILENAME = "msteams-sso-tokens.sqlite.lock";
|
|
3445
|
-
const MAX_SSO_TOKENS = 5e3;
|
|
3446
|
-
const STORE_KEY_VERSION_PREFIX = "v2:";
|
|
3447
|
-
function makeKey(connectionName, userId) {
|
|
3448
|
-
return `${STORE_KEY_VERSION_PREFIX}${createHash("sha256").update(JSON.stringify([connectionName, userId])).digest("hex")}`;
|
|
3449
|
-
}
|
|
3450
|
-
function buildMigrationKey(filePath) {
|
|
3451
|
-
return `legacy-json:${createHash("sha256").update(filePath).digest("hex")}`;
|
|
3452
|
-
}
|
|
3453
|
-
function buildMigrationContentKey(filePath, value) {
|
|
3454
|
-
return `legacy-json-content:${createHash("sha256").update(filePath).update("\0").update(JSON.stringify(value) ?? "undefined").digest("hex")}`;
|
|
3455
|
-
}
|
|
3456
|
-
function createTokenStore(params) {
|
|
3457
|
-
return getMSTeamsRuntime().state.openKeyedStore({
|
|
3458
|
-
namespace: SSO_TOKENS_NAMESPACE,
|
|
3459
|
-
maxEntries: MAX_SSO_TOKENS,
|
|
3460
|
-
env: resolveMSTeamsSqliteStateEnv(params)
|
|
3461
|
-
});
|
|
3462
|
-
}
|
|
3463
|
-
function createMigrationStore(params) {
|
|
3464
|
-
return getMSTeamsRuntime().state.openKeyedStore({
|
|
3465
|
-
namespace: SSO_TOKEN_MIGRATIONS_NAMESPACE,
|
|
3466
|
-
maxEntries: 100,
|
|
3467
|
-
env: resolveMSTeamsSqliteStateEnv(params)
|
|
3468
|
-
});
|
|
3469
|
-
}
|
|
3470
|
-
function normalizeStoredToken(value) {
|
|
3471
|
-
if (!value || typeof value !== "object") return null;
|
|
3472
|
-
const token = value;
|
|
3473
|
-
if (typeof token.connectionName !== "string" || !token.connectionName || typeof token.userId !== "string" || !token.userId || typeof token.token !== "string" || !token.token || typeof token.updatedAt !== "string" || !token.updatedAt) return null;
|
|
3474
|
-
return {
|
|
3475
|
-
connectionName: token.connectionName,
|
|
3476
|
-
userId: token.userId,
|
|
3477
|
-
token: token.token,
|
|
3478
|
-
...typeof token.expiresAt === "string" ? { expiresAt: token.expiresAt } : {},
|
|
3479
|
-
updatedAt: token.updatedAt
|
|
3480
|
-
};
|
|
3481
|
-
}
|
|
3482
|
-
function isSsoStoreData(value) {
|
|
3483
|
-
if (!value || typeof value !== "object") return false;
|
|
3484
|
-
const obj = value;
|
|
3485
|
-
return obj.version === 1 && typeof obj.tokens === "object" && obj.tokens !== null;
|
|
3486
|
-
}
|
|
3487
|
-
function createMSTeamsSsoTokenStoreFs(params) {
|
|
3488
|
-
const legacyFilePath = resolveMSTeamsStorePath({
|
|
3489
|
-
filename: STORE_FILENAME,
|
|
3490
|
-
env: params?.env,
|
|
3491
|
-
homedir: params?.homedir,
|
|
3492
|
-
stateDir: params?.stateDir,
|
|
3493
|
-
storePath: params?.storePath
|
|
3494
|
-
});
|
|
3495
|
-
const empty = {
|
|
3496
|
-
version: 1,
|
|
3497
|
-
tokens: {}
|
|
3498
|
-
};
|
|
3499
|
-
const tokenStore = createTokenStore(params);
|
|
3500
|
-
const migrationStore = createMigrationStore(params);
|
|
3501
|
-
const migrationKey = buildMigrationKey(legacyFilePath);
|
|
3502
|
-
let legacyImportPromise = null;
|
|
3503
|
-
const importLegacyStore = async () => {
|
|
3504
|
-
const imported = await migrationStore.lookup(migrationKey) !== void 0;
|
|
3505
|
-
const { value, exists } = await readJsonFile(legacyFilePath, empty);
|
|
3506
|
-
const contentKey = exists ? buildMigrationContentKey(legacyFilePath, value) : null;
|
|
3507
|
-
if (contentKey && await migrationStore.lookup(contentKey)) return;
|
|
3508
|
-
if (exists && isSsoStoreData(value)) for (const stored of Object.values(value.tokens)) {
|
|
3509
|
-
const normalized = normalizeStoredToken(stored);
|
|
3510
|
-
if (!normalized) continue;
|
|
3511
|
-
await tokenStore.registerIfAbsent(makeKey(normalized.connectionName, normalized.userId), toPluginJsonValue(normalized));
|
|
3512
|
-
}
|
|
3513
|
-
if (contentKey) await migrationStore.register(contentKey, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3514
|
-
if (!imported) await migrationStore.register(migrationKey, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3515
|
-
if (exists) await fs.rm(legacyFilePath, { force: true }).catch(() => {});
|
|
3516
|
-
};
|
|
3517
|
-
const ensureLegacyImported = async () => {
|
|
3518
|
-
if (!legacyImportPromise) legacyImportPromise = withMSTeamsSqliteMutationLock(params, SSO_TOKEN_LOCK_FILENAME, () => importLegacyStore()).finally(() => {
|
|
3519
|
-
legacyImportPromise = null;
|
|
3520
|
-
});
|
|
3521
|
-
await legacyImportPromise;
|
|
3522
|
-
};
|
|
3523
|
-
return {
|
|
3524
|
-
async get({ connectionName, userId }) {
|
|
3525
|
-
await ensureLegacyImported();
|
|
3526
|
-
return await tokenStore.lookup(makeKey(connectionName, userId)) ?? null;
|
|
3527
|
-
},
|
|
3528
|
-
async save(token) {
|
|
3529
|
-
await withMSTeamsSqliteMutationLock(params, SSO_TOKEN_LOCK_FILENAME, async () => {
|
|
3530
|
-
await importLegacyStore();
|
|
3531
|
-
await tokenStore.register(makeKey(token.connectionName, token.userId), toPluginJsonValue({ ...token }));
|
|
3532
|
-
});
|
|
3533
|
-
},
|
|
3534
|
-
async remove({ connectionName, userId }) {
|
|
3535
|
-
let removed = false;
|
|
3536
|
-
await withMSTeamsSqliteMutationLock(params, SSO_TOKEN_LOCK_FILENAME, async () => {
|
|
3537
|
-
await importLegacyStore();
|
|
3538
|
-
removed = await tokenStore.delete(makeKey(connectionName, userId));
|
|
3539
|
-
});
|
|
3540
|
-
return removed;
|
|
3541
|
-
}
|
|
3542
|
-
};
|
|
3543
|
-
}
|
|
3544
|
-
//#endregion
|
|
3545
3430
|
//#region extensions/msteams/src/webhook-timeouts.ts
|
|
3546
3431
|
const MSTEAMS_WEBHOOK_INACTIVITY_TIMEOUT_MS = 3e4;
|
|
3547
3432
|
const MSTEAMS_WEBHOOK_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { t as getMSTeamsRuntime } from "./runtime-BS5AZrKK.js";
|
|
2
|
+
import { C as toPluginJsonValue, S as resolveMSTeamsSqliteStateEnv, w as withMSTeamsSqliteMutationLock } from "./polls-C1VgSvKE.js";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
//#region extensions/msteams/src/sso-token-store.ts
|
|
5
|
+
/**
|
|
6
|
+
* SQLite-backed store for Bot Framework OAuth SSO tokens.
|
|
7
|
+
*
|
|
8
|
+
* Tokens are keyed by (connectionName, userId). `userId` should be the
|
|
9
|
+
* stable AAD object ID (`activity.from.aadObjectId`) when available,
|
|
10
|
+
* falling back to the Bot Framework `activity.from.id`.
|
|
11
|
+
*
|
|
12
|
+
* The store is intentionally minimal: it persists the exchanged user
|
|
13
|
+
* token plus its expiration so consumers (for example tool handlers
|
|
14
|
+
* that call Microsoft Graph with delegated permissions) can fetch a
|
|
15
|
+
* valid token without reaching back into Bot Framework every turn.
|
|
16
|
+
*/
|
|
17
|
+
const MSTEAMS_SSO_TOKENS_LEGACY_FILENAME = "msteams-sso-tokens.json";
|
|
18
|
+
const MSTEAMS_SSO_TOKENS_NAMESPACE = "sso-tokens";
|
|
19
|
+
const SSO_TOKEN_LOCK_FILENAME = "msteams-sso-tokens.sqlite.lock";
|
|
20
|
+
const MSTEAMS_MAX_SSO_TOKENS = 5e3;
|
|
21
|
+
const STORE_KEY_VERSION_PREFIX = "v2:";
|
|
22
|
+
function makeMSTeamsSsoTokenStoreKey(connectionName, userId) {
|
|
23
|
+
return `${STORE_KEY_VERSION_PREFIX}${createHash("sha256").update(JSON.stringify([connectionName, userId])).digest("hex")}`;
|
|
24
|
+
}
|
|
25
|
+
function createTokenStore(params) {
|
|
26
|
+
return getMSTeamsRuntime().state.openKeyedStore({
|
|
27
|
+
namespace: MSTEAMS_SSO_TOKENS_NAMESPACE,
|
|
28
|
+
maxEntries: MSTEAMS_MAX_SSO_TOKENS,
|
|
29
|
+
env: resolveMSTeamsSqliteStateEnv(params)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function normalizeMSTeamsSsoStoredToken(value) {
|
|
33
|
+
if (!value || typeof value !== "object") return null;
|
|
34
|
+
const token = value;
|
|
35
|
+
if (typeof token.connectionName !== "string" || !token.connectionName || typeof token.userId !== "string" || !token.userId || typeof token.token !== "string" || !token.token || typeof token.updatedAt !== "string" || !token.updatedAt) return null;
|
|
36
|
+
return {
|
|
37
|
+
connectionName: token.connectionName,
|
|
38
|
+
userId: token.userId,
|
|
39
|
+
token: token.token,
|
|
40
|
+
...typeof token.expiresAt === "string" ? { expiresAt: token.expiresAt } : {},
|
|
41
|
+
updatedAt: token.updatedAt
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function isMSTeamsSsoStoreData(value) {
|
|
45
|
+
if (!value || typeof value !== "object") return false;
|
|
46
|
+
const obj = value;
|
|
47
|
+
return obj.version === 1 && typeof obj.tokens === "object" && obj.tokens !== null;
|
|
48
|
+
}
|
|
49
|
+
function createMSTeamsSsoTokenStoreFs(params) {
|
|
50
|
+
const tokenStore = createTokenStore(params);
|
|
51
|
+
return {
|
|
52
|
+
async get({ connectionName, userId }) {
|
|
53
|
+
return await tokenStore.lookup(makeMSTeamsSsoTokenStoreKey(connectionName, userId)) ?? null;
|
|
54
|
+
},
|
|
55
|
+
async save(token) {
|
|
56
|
+
await withMSTeamsSqliteMutationLock(params, SSO_TOKEN_LOCK_FILENAME, async () => {
|
|
57
|
+
await tokenStore.register(makeMSTeamsSsoTokenStoreKey(token.connectionName, token.userId), toPluginJsonValue({ ...token }));
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
async remove({ connectionName, userId }) {
|
|
61
|
+
let removed = false;
|
|
62
|
+
await withMSTeamsSqliteMutationLock(params, SSO_TOKEN_LOCK_FILENAME, async () => {
|
|
63
|
+
removed = await tokenStore.delete(makeMSTeamsSsoTokenStoreKey(connectionName, userId));
|
|
64
|
+
});
|
|
65
|
+
return removed;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { isMSTeamsSsoStoreData as a, createMSTeamsSsoTokenStoreFs as i, MSTEAMS_SSO_TOKENS_LEGACY_FILENAME as n, makeMSTeamsSsoTokenStoreKey as o, MSTEAMS_SSO_TOKENS_NAMESPACE as r, normalizeMSTeamsSsoStoredToken as s, MSTEAMS_MAX_SSO_TOKENS as t };
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/msteams",
|
|
3
|
-
"version": "2026.6.1",
|
|
3
|
+
"version": "2026.6.5-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",
|
|
9
|
+
"version": "2026.6.5-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"
|
|
18
|
+
"openclaw": ">=2026.6.5-beta.1"
|
|
19
19
|
},
|
|
20
20
|
"peerDependenciesMeta": {
|
|
21
21
|
"openclaw": {
|