@openclaw/msteams 2026.7.2-beta.5 → 2026.7.2-beta.7
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.cjs +2 -2
- package/dist/{channel-DtZFlwhL.cjs → channel-C7QxRjBW.cjs} +8 -100
- package/dist/channel-config-api.cjs +1 -1
- package/dist/channel-plugin-api.cjs +1 -1
- package/dist/{channel.runtime-BtdOD_kD.cjs → channel.runtime-DjY6d_hd.cjs} +19 -14
- package/dist/{config-schema-BTnLgWAe.cjs → config-schema-C4CszdDi.cjs} +12 -28
- package/dist/{delegated-state-DOSCHaAB.cjs → delegated-state-DiEz9JIa.cjs} +0 -28
- package/dist/directory-contract-api.cjs +1 -1
- package/dist/doctor-contract-api.cjs +6 -6
- package/dist/{messenger-BJ0KM2sD.cjs → messenger-DTiaLTDO.cjs} +40 -20
- package/dist/{oauth-CNcnL8of.cjs → oauth-CW6jVGp8.cjs} +1 -1
- package/dist/{polls-CWExq2xZ.cjs → polls-BWKNpBSg.cjs} +30 -3
- package/dist/{resolve-allowlist-Dr_1sOJh.cjs → resolve-allowlist-CEj9sABO.cjs} +124 -11
- package/dist/setup-plugin-api.cjs +3 -3
- package/dist/{setup-surface-xVejKCJV.cjs → setup-surface-ByWjaXEn.cjs} +17 -36
- package/dist/{src-CsXzz0x2.cjs → src-B_BdEvcr.cjs} +1311 -992
- package/dist/{sso-token-store-OSg229re.cjs → sso-token-store-TKYk_Ms7.cjs} +1 -1
- package/package.json +5 -5
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_polls = require("./polls-BWKNpBSg.cjs");
|
|
2
2
|
const require_runtime = require("./runtime-gZTAuFux.cjs");
|
|
3
3
|
require("./runtime-api.cjs");
|
|
4
|
-
const require_resolve_allowlist = require("./resolve-allowlist-
|
|
5
|
-
const require_channel = require("./channel-
|
|
6
|
-
const require_setup_surface = require("./setup-surface-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const require_messenger = require("./messenger-BJ0KM2sD.cjs");
|
|
4
|
+
const require_resolve_allowlist = require("./resolve-allowlist-CEj9sABO.cjs");
|
|
5
|
+
const require_channel = require("./channel-C7QxRjBW.cjs");
|
|
6
|
+
const require_setup_surface = require("./setup-surface-ByWjaXEn.cjs");
|
|
7
|
+
const require_sso_token_store = require("./sso-token-store-TKYk_Ms7.cjs");
|
|
8
|
+
const require_messenger = require("./messenger-DTiaLTDO.cjs");
|
|
10
9
|
let openclaw_plugin_sdk_channel_outbound = require("openclaw/plugin-sdk/channel-outbound");
|
|
11
10
|
let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
|
|
12
11
|
let openclaw_plugin_sdk_allow_from = require("openclaw/plugin-sdk/allow-from");
|
|
@@ -17,12 +16,15 @@ let openclaw_plugin_sdk_number_runtime = require("openclaw/plugin-sdk/number-run
|
|
|
17
16
|
let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text-utility-runtime");
|
|
18
17
|
let openclaw_plugin_sdk_routing = require("openclaw/plugin-sdk/routing");
|
|
19
18
|
let node_crypto = require("node:crypto");
|
|
20
|
-
node_crypto =
|
|
19
|
+
node_crypto = require_polls.__toESM(node_crypto, 1);
|
|
20
|
+
let openclaw_plugin_sdk_error_runtime = require("openclaw/plugin-sdk/error-runtime");
|
|
21
21
|
let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
|
|
22
22
|
let openclaw_plugin_sdk_channel_ingress_runtime = require("openclaw/plugin-sdk/channel-ingress-runtime");
|
|
23
23
|
let openclaw_plugin_sdk_context_visibility_runtime = require("openclaw/plugin-sdk/context-visibility-runtime");
|
|
24
24
|
let openclaw_plugin_sdk_reply_history = require("openclaw/plugin-sdk/reply-history");
|
|
25
25
|
let openclaw_plugin_sdk_agent_runtime = require("openclaw/plugin-sdk/agent-runtime");
|
|
26
|
+
let openclaw_plugin_sdk_extension_shared = require("openclaw/plugin-sdk/extension-shared");
|
|
27
|
+
let openclaw_plugin_sdk_plugin_runtime = require("openclaw/plugin-sdk/plugin-runtime");
|
|
26
28
|
let openclaw_plugin_sdk_dedupe_runtime = require("openclaw/plugin-sdk/dedupe-runtime");
|
|
27
29
|
let openclaw_plugin_sdk_collection_runtime = require("openclaw/plugin-sdk/collection-runtime");
|
|
28
30
|
let openclaw_plugin_sdk_channel_pairing = require("openclaw/plugin-sdk/channel-pairing");
|
|
@@ -168,20 +170,50 @@ const msteamsIngressIdentity = {
|
|
|
168
170
|
aliases: [{
|
|
169
171
|
key: "sender-name",
|
|
170
172
|
kind: "plugin:msteams-sender-name",
|
|
171
|
-
normalizeEntry:
|
|
172
|
-
normalizeSubject:
|
|
173
|
+
normalizeEntry: normalizeSenderNameIngressValue,
|
|
174
|
+
normalizeSubject: normalizeSenderNameIngressValue,
|
|
173
175
|
dangerous: true
|
|
176
|
+
}, {
|
|
177
|
+
key: "conversation-id",
|
|
178
|
+
kind: "plugin:msteams-conversation-id",
|
|
179
|
+
normalizeEntry: normalizeAllowlistConversationId,
|
|
180
|
+
normalizeSubject: normalizeAllowlistConversationId
|
|
174
181
|
}],
|
|
175
182
|
isWildcardEntry: (entry) => normalizeIngressValue(entry) === "*",
|
|
176
|
-
resolveEntryId: ({ entryIndex, fieldKey }) => `msteams-entry-${entryIndex + 1}:${fieldKey === "sender-name" ? "name" : "id"}`
|
|
183
|
+
resolveEntryId: ({ entryIndex, fieldKey }) => `msteams-entry-${entryIndex + 1}:${fieldKey === "sender-name" ? "name" : fieldKey === "conversation-id" ? "conversation-id" : "id"}`
|
|
177
184
|
};
|
|
178
185
|
function normalizeIngressValue(value) {
|
|
179
186
|
return (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(value) ?? null;
|
|
180
187
|
}
|
|
188
|
+
function normalizeSenderNameIngressValue(value) {
|
|
189
|
+
const normalized = normalizeIngressValue(value);
|
|
190
|
+
if (!normalized) return null;
|
|
191
|
+
return require_resolve_allowlist.looksLikeMSTeamsConversationId(require_resolve_allowlist.normalizeMSTeamsConversationId(normalized)) ? null : normalized;
|
|
192
|
+
}
|
|
193
|
+
function normalizeAllowlistConversationId(value) {
|
|
194
|
+
const trimmed = value?.trim();
|
|
195
|
+
return trimmed ? require_resolve_allowlist.normalizeMSTeamsConversationId(trimmed) : null;
|
|
196
|
+
}
|
|
197
|
+
function formatMSTeamsSenderReason(params) {
|
|
198
|
+
switch (params.reasonCode) {
|
|
199
|
+
case "dm_policy_open": return "dmPolicy=open";
|
|
200
|
+
case "dm_policy_disabled": return "dmPolicy=disabled";
|
|
201
|
+
case "dm_policy_pairing_required": return "dmPolicy=pairing (not allowlisted)";
|
|
202
|
+
case "dm_policy_allowlisted": return `dmPolicy=${params.dmPolicy ?? "allowlist"} (allowlisted)`;
|
|
203
|
+
case "dm_policy_not_allowlisted": return `dmPolicy=${params.dmPolicy ?? "allowlist"} (not allowlisted)`;
|
|
204
|
+
case "group_policy_disabled": return "groupPolicy=disabled";
|
|
205
|
+
case "group_policy_empty_allowlist":
|
|
206
|
+
case "route_sender_empty": return "groupPolicy=allowlist (empty allowlist)";
|
|
207
|
+
case "group_policy_not_allowlisted": return "groupPolicy=allowlist (not allowlisted)";
|
|
208
|
+
case "group_policy_open": return "groupPolicy=open";
|
|
209
|
+
case "group_policy_allowed": return `groupPolicy=${params.groupPolicy ?? "allowlist"}`;
|
|
210
|
+
default: return params.reasonCode;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
181
213
|
async function resolveMSTeamsSenderAccess(params) {
|
|
182
214
|
const activity = params.activity;
|
|
183
215
|
const msteamsCfg = params.cfg.channels?.msteams;
|
|
184
|
-
const conversationId =
|
|
216
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "unknown");
|
|
185
217
|
const convType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(activity.conversation?.conversationType);
|
|
186
218
|
const isDirectMessage = convType === "personal" || !convType && !activity.conversation?.isGroup;
|
|
187
219
|
const senderId = activity.from?.aadObjectId ?? activity.from?.id ?? "unknown";
|
|
@@ -214,7 +246,10 @@ async function resolveMSTeamsSenderAccess(params) {
|
|
|
214
246
|
readStoreAllowFrom: pairing.readAllowFromStore,
|
|
215
247
|
subject: {
|
|
216
248
|
stableId: senderId,
|
|
217
|
-
aliases: {
|
|
249
|
+
aliases: {
|
|
250
|
+
"sender-name": senderName,
|
|
251
|
+
...!isDirectMessage ? { "conversation-id": conversationId } : {}
|
|
252
|
+
}
|
|
218
253
|
},
|
|
219
254
|
conversation: {
|
|
220
255
|
kind: isDirectMessage ? "direct" : convType === "channel" ? "channel" : "group",
|
|
@@ -258,6 +293,136 @@ async function resolveMSTeamsSenderAccess(params) {
|
|
|
258
293
|
groupPolicy
|
|
259
294
|
};
|
|
260
295
|
}
|
|
296
|
+
async function admitMSTeamsMessage(params) {
|
|
297
|
+
const core = require_runtime.getMSTeamsRuntime();
|
|
298
|
+
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
|
299
|
+
cfg: params.cfg,
|
|
300
|
+
surface: "msteams"
|
|
301
|
+
});
|
|
302
|
+
const isControlCommand = allowTextCommands && core.channel.commands.isControlCommandMessage(params.text, params.cfg);
|
|
303
|
+
const access = await resolveMSTeamsSenderAccess({
|
|
304
|
+
cfg: params.cfg,
|
|
305
|
+
activity: params.activity,
|
|
306
|
+
hasControlCommand: isControlCommand
|
|
307
|
+
});
|
|
308
|
+
const { dmPolicy, senderId, senderName, pairing, isDirectMessage, channelGate, senderAccess, commandAccess, allowNameMatching, groupPolicy, msteamsCfg } = access;
|
|
309
|
+
const effectiveDmAllowFrom = senderAccess.effectiveAllowFrom;
|
|
310
|
+
const effectiveGroupAllowFrom = senderAccess.effectiveGroupAllowFrom;
|
|
311
|
+
if (isDirectMessage && msteamsCfg && senderAccess.decision !== "allow") {
|
|
312
|
+
if (senderAccess.reasonCode === "dm_policy_disabled") {
|
|
313
|
+
params.log.info("dropping dm (dms disabled)", {
|
|
314
|
+
sender: senderId,
|
|
315
|
+
label: senderName
|
|
316
|
+
});
|
|
317
|
+
params.log.debug?.("dropping dm (dms disabled)");
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const allowMatch = require_channel.resolveMSTeamsAllowlistMatch({
|
|
321
|
+
allowFrom: effectiveDmAllowFrom,
|
|
322
|
+
senderId,
|
|
323
|
+
senderName,
|
|
324
|
+
allowNameMatching
|
|
325
|
+
});
|
|
326
|
+
if (senderAccess.decision === "pairing") {
|
|
327
|
+
params.conversationStore.upsert(params.conversationId, params.conversationRef).catch((err) => {
|
|
328
|
+
params.log.debug?.("failed to save conversation reference", { error: require_setup_surface.formatUnknownError(err) });
|
|
329
|
+
});
|
|
330
|
+
if (await pairing.upsertPairingRequest({
|
|
331
|
+
id: senderId,
|
|
332
|
+
meta: { name: senderName }
|
|
333
|
+
})) params.log.info("msteams pairing request created", {
|
|
334
|
+
sender: senderId,
|
|
335
|
+
label: senderName
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
params.log.debug?.("dropping dm (not allowlisted)", {
|
|
339
|
+
sender: senderId,
|
|
340
|
+
label: senderName,
|
|
341
|
+
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
342
|
+
});
|
|
343
|
+
params.log.info("dropping dm (not allowlisted)", {
|
|
344
|
+
sender: senderId,
|
|
345
|
+
label: senderName,
|
|
346
|
+
dmPolicy,
|
|
347
|
+
reason: formatMSTeamsSenderReason({
|
|
348
|
+
reasonCode: senderAccess.reasonCode,
|
|
349
|
+
dmPolicy,
|
|
350
|
+
groupPolicy
|
|
351
|
+
}),
|
|
352
|
+
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
353
|
+
});
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
if (!isDirectMessage && msteamsCfg) {
|
|
357
|
+
if (channelGate.allowlistConfigured && !channelGate.allowed) {
|
|
358
|
+
params.log.info("dropping group message (not in team/channel allowlist)", {
|
|
359
|
+
conversationId: params.conversationId,
|
|
360
|
+
teamKey: channelGate.teamKey ?? "none",
|
|
361
|
+
channelKey: channelGate.channelKey ?? "none",
|
|
362
|
+
channelMatchKey: channelGate.channelMatchKey ?? "none",
|
|
363
|
+
channelMatchSource: channelGate.channelMatchSource ?? "none"
|
|
364
|
+
});
|
|
365
|
+
params.log.debug?.("dropping group message (not in team/channel allowlist)", {
|
|
366
|
+
conversationId: params.conversationId,
|
|
367
|
+
teamKey: channelGate.teamKey ?? "none",
|
|
368
|
+
channelKey: channelGate.channelKey ?? "none",
|
|
369
|
+
channelMatchKey: channelGate.channelMatchKey ?? "none",
|
|
370
|
+
channelMatchSource: channelGate.channelMatchSource ?? "none"
|
|
371
|
+
});
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_disabled") {
|
|
375
|
+
params.log.info("dropping group message (groupPolicy: disabled)", { conversationId: params.conversationId });
|
|
376
|
+
params.log.debug?.("dropping group message (groupPolicy: disabled)", { conversationId: params.conversationId });
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
if (!senderAccess.allowed && (senderAccess.reasonCode === "group_policy_empty_allowlist" || senderAccess.reasonCode === "route_sender_empty")) {
|
|
380
|
+
params.log.info("dropping group message (groupPolicy: allowlist, no allowlist)", { conversationId: params.conversationId });
|
|
381
|
+
params.log.debug?.("dropping group message (groupPolicy: allowlist, no allowlist)", { conversationId: params.conversationId });
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_not_allowlisted") {
|
|
385
|
+
const allowMatch = require_channel.resolveMSTeamsAllowlistMatch({
|
|
386
|
+
allowFrom: effectiveGroupAllowFrom,
|
|
387
|
+
senderId,
|
|
388
|
+
senderName,
|
|
389
|
+
allowNameMatching
|
|
390
|
+
});
|
|
391
|
+
params.log.debug?.("dropping group message (not in groupAllowFrom)", {
|
|
392
|
+
sender: senderId,
|
|
393
|
+
label: senderName,
|
|
394
|
+
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
395
|
+
});
|
|
396
|
+
params.log.info("dropping group message (not in groupAllowFrom)", {
|
|
397
|
+
sender: senderId,
|
|
398
|
+
label: senderName,
|
|
399
|
+
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
400
|
+
});
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (commandAccess.shouldBlockControlCommand) {
|
|
405
|
+
(0, openclaw_plugin_sdk_channel_inbound.logInboundDrop)({
|
|
406
|
+
log: params.logVerboseMessage,
|
|
407
|
+
channel: "msteams",
|
|
408
|
+
reason: "control command (unauthorized)",
|
|
409
|
+
target: senderId
|
|
410
|
+
});
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
params.conversationStore.upsert(params.conversationId, params.conversationRef).catch((err) => {
|
|
414
|
+
params.log.debug?.("failed to save conversation reference", { error: require_setup_surface.formatUnknownError(err) });
|
|
415
|
+
});
|
|
416
|
+
return {
|
|
417
|
+
...access,
|
|
418
|
+
allowTextCommands,
|
|
419
|
+
isControlCommand,
|
|
420
|
+
commandAuthorized: commandAccess.requested ? commandAccess.authorized : void 0,
|
|
421
|
+
effectiveDmAllowFrom,
|
|
422
|
+
effectiveGroupAllowFrom,
|
|
423
|
+
isChannel: params.isChannel
|
|
424
|
+
};
|
|
425
|
+
}
|
|
261
426
|
//#endregion
|
|
262
427
|
//#region extensions/msteams/src/attachments/bot-framework.ts
|
|
263
428
|
/**
|
|
@@ -933,10 +1098,13 @@ async function fetchGraphCollection(params) {
|
|
|
933
1098
|
});
|
|
934
1099
|
try {
|
|
935
1100
|
const status = response.status;
|
|
936
|
-
if (!response.ok)
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1101
|
+
if (!response.ok) {
|
|
1102
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1103
|
+
return {
|
|
1104
|
+
status,
|
|
1105
|
+
items: []
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
940
1108
|
try {
|
|
941
1109
|
return {
|
|
942
1110
|
status,
|
|
@@ -1237,147 +1405,293 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1237
1405
|
};
|
|
1238
1406
|
}
|
|
1239
1407
|
//#endregion
|
|
1240
|
-
//#region extensions/msteams/src/
|
|
1241
|
-
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
const INJECTED_MAX = 200;
|
|
1245
|
-
const injectedParents = /* @__PURE__ */ new Map();
|
|
1246
|
-
function touchLru(map, key, value, max) {
|
|
1247
|
-
if (map.has(key)) map.delete(key);
|
|
1248
|
-
else if (map.size >= max) {
|
|
1249
|
-
const firstKey = map.keys().next().value;
|
|
1250
|
-
if (firstKey !== void 0) map.delete(firstKey);
|
|
1251
|
-
}
|
|
1252
|
-
map.set(key, value);
|
|
1408
|
+
//#region extensions/msteams/src/monitor-handler/inbound-media.ts
|
|
1409
|
+
function shouldAttemptMSTeamsGraphMediaFallback(params) {
|
|
1410
|
+
const conversationType = params.conversationType.trim().toLowerCase();
|
|
1411
|
+
return params.graphMediaFallback === true && (conversationType === "channel" || conversationType === "groupchat") && (params.htmlSummary?.htmlAttachments ?? 0) > 0;
|
|
1253
1412
|
}
|
|
1254
|
-
function
|
|
1255
|
-
|
|
1413
|
+
function resolveMSTeamsInboundMediaBody(params) {
|
|
1414
|
+
const unavailableCount = params.materializedMedia.filter((media) => !media.path).length + Math.max(0, params.nativeMedia.length - params.materializedMedia.length);
|
|
1415
|
+
if (unavailableCount === 0) return params.body;
|
|
1416
|
+
return (0, openclaw_plugin_sdk_channel_inbound.formatInboundMediaUnavailableText)({
|
|
1417
|
+
body: params.body,
|
|
1418
|
+
notice: `[msteams ${unavailableCount > 1 ? `${unavailableCount} attachments` : "attachment"} unavailable]`
|
|
1419
|
+
});
|
|
1256
1420
|
}
|
|
1257
|
-
function
|
|
1258
|
-
const
|
|
1259
|
-
return
|
|
1421
|
+
function hasDefinitiveContentType(media) {
|
|
1422
|
+
const contentType = media.contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
1423
|
+
return Boolean(contentType && contentType !== "application/octet-stream" && contentType !== "binary/octet-stream");
|
|
1260
1424
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1425
|
+
function mergeMSTeamsMediaFacts(nativeMedia, materializedMedia, options = {}) {
|
|
1426
|
+
const merged = [...nativeMedia];
|
|
1427
|
+
const nativeSlotCount = nativeMedia.length;
|
|
1428
|
+
const nativeIndexBySourceId = /* @__PURE__ */ new Map();
|
|
1429
|
+
nativeMedia.forEach((media, index) => {
|
|
1430
|
+
if (media.sourceId && !nativeIndexBySourceId.has(media.sourceId)) nativeIndexBySourceId.set(media.sourceId, index);
|
|
1431
|
+
});
|
|
1432
|
+
for (const [index, materialized] of materializedMedia.entries()) {
|
|
1433
|
+
const sourceIndex = materialized.sourceId ? nativeIndexBySourceId.get(materialized.sourceId) : void 0;
|
|
1434
|
+
const positionalIndex = options.positionallyAligned === false || index >= nativeMedia.length ? void 0 : index;
|
|
1435
|
+
const mayUseFallbackOrder = options.positionallyAligned === false;
|
|
1436
|
+
const isEligibleUnresolved = (media) => !media.path && !media.sourceId;
|
|
1437
|
+
const sameKindUnresolvedIndexes = mayUseFallbackOrder ? merged.slice(0, nativeSlotCount).flatMap((media, mediaIndex) => isEligibleUnresolved(media) && media.kind === materialized.kind ? [mediaIndex] : []) : [];
|
|
1438
|
+
const unresolvedIndexes = sameKindUnresolvedIndexes.length === 0 && mayUseFallbackOrder ? merged.slice(0, nativeSlotCount).flatMap((media, mediaIndex) => isEligibleUnresolved(media) ? [mediaIndex] : []) : [];
|
|
1439
|
+
const fallbackIndex = sameKindUnresolvedIndexes.length > 0 ? sameKindUnresolvedIndexes[0] : unresolvedIndexes.length === 1 ? unresolvedIndexes[0] : void 0;
|
|
1440
|
+
const targetIndex = sourceIndex ?? positionalIndex ?? fallbackIndex;
|
|
1441
|
+
if (targetIndex === void 0) {
|
|
1442
|
+
if (materialized.sourceId) nativeIndexBySourceId.set(materialized.sourceId, merged.length);
|
|
1443
|
+
merged.push(materialized);
|
|
1444
|
+
} else {
|
|
1445
|
+
if (materialized.sourceId) nativeIndexBySourceId.set(materialized.sourceId, targetIndex);
|
|
1446
|
+
const current = merged[targetIndex];
|
|
1447
|
+
if (materialized.path) merged[targetIndex] = materialized;
|
|
1448
|
+
else if (!current?.path) merged[targetIndex] = {
|
|
1449
|
+
...current,
|
|
1450
|
+
...materialized,
|
|
1451
|
+
kind: hasDefinitiveContentType(materialized) || !current?.kind ? materialized.kind : current.kind
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1276
1454
|
}
|
|
1277
|
-
|
|
1278
|
-
const message = await fetchParent(token, groupId, channelId, parentId);
|
|
1279
|
-
const expiresAt = resolveParentCacheExpiresAt(Date.now());
|
|
1280
|
-
if (expiresAt !== void 0) touchLru(parentCache, key, {
|
|
1281
|
-
message,
|
|
1282
|
-
expiresAt
|
|
1283
|
-
}, PARENT_CACHE_MAX);
|
|
1284
|
-
return message;
|
|
1285
|
-
}
|
|
1286
|
-
const PARENT_TEXT_MAX_CHARS = 400;
|
|
1287
|
-
/**
|
|
1288
|
-
* Extract a compact summary (sender + plain-text body) from a Graph parent
|
|
1289
|
-
* message. Returns undefined when the parent cannot be summarized (missing
|
|
1290
|
-
* or blank body).
|
|
1291
|
-
*/
|
|
1292
|
-
function summarizeParentMessage(message) {
|
|
1293
|
-
if (!message) return;
|
|
1294
|
-
const sender = message.from?.user?.displayName ?? message.from?.application?.displayName ?? "unknown";
|
|
1295
|
-
const contentType = message.body?.contentType ?? "text";
|
|
1296
|
-
const raw = message.body?.content ?? "";
|
|
1297
|
-
const text = contentType === "html" ? require_messenger.stripHtmlFromTeamsMessage(raw) : raw.replace(/\s+/g, " ").trim();
|
|
1298
|
-
if (!text) return;
|
|
1299
|
-
return {
|
|
1300
|
-
sender,
|
|
1301
|
-
text: text.length > PARENT_TEXT_MAX_CHARS ? `${(0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(text, PARENT_TEXT_MAX_CHARS - 1)}…` : text
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
/**
|
|
1305
|
-
* Build the single-line `Replying to @sender: body` system event text.
|
|
1306
|
-
* Callers should pass this text to `enqueueSystemEvent` together with a
|
|
1307
|
-
* stable contextKey derived from the parent id.
|
|
1308
|
-
*/
|
|
1309
|
-
function formatParentContextEvent(summary) {
|
|
1310
|
-
return `Replying to @${summary.sender}: ${summary.text}`;
|
|
1311
|
-
}
|
|
1312
|
-
/**
|
|
1313
|
-
* Decide whether a parent context event should be enqueued for the current
|
|
1314
|
-
* session. Returns `false` when we already injected the same parent for this
|
|
1315
|
-
* session recently (prevents re-prepending identical context on every reply
|
|
1316
|
-
* in the thread).
|
|
1317
|
-
*/
|
|
1318
|
-
function shouldInjectParentContext(sessionKey, parentId) {
|
|
1319
|
-
const key = sessionKey;
|
|
1320
|
-
return injectedParents.get(key) !== parentId;
|
|
1321
|
-
}
|
|
1322
|
-
/**
|
|
1323
|
-
* Record that `parentId` was just injected for `sessionKey` so subsequent
|
|
1324
|
-
* replies with the same parent can short-circuit via `shouldInjectParentContext`.
|
|
1325
|
-
*/
|
|
1326
|
-
function markParentContextInjected(sessionKey, parentId) {
|
|
1327
|
-
touchLru(injectedParents, sessionKey, parentId, INJECTED_MAX);
|
|
1455
|
+
return merged;
|
|
1328
1456
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
function isStreamCancelledError(err) {
|
|
1332
|
-
return err instanceof Error && err.name === "StreamCancelledError";
|
|
1457
|
+
function hasMaterializedMedia(media) {
|
|
1458
|
+
return media.some((entry) => Boolean(entry.path));
|
|
1333
1459
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1460
|
+
async function resolveMSTeamsInboundMedia(params) {
|
|
1461
|
+
const { attachments, htmlSummary, maxBytes, tokenProvider, allowHosts, conversationType, conversationId, conversationMessageId, teamAadGroupId, serviceUrl, activity, log, preserveFilenames } = params;
|
|
1462
|
+
let mediaList = await downloadMSTeamsAttachments({
|
|
1463
|
+
attachments,
|
|
1464
|
+
maxBytes,
|
|
1465
|
+
tokenProvider,
|
|
1466
|
+
allowHosts,
|
|
1467
|
+
authAllowHosts: params.authAllowHosts,
|
|
1468
|
+
preserveFilenames,
|
|
1469
|
+
deadline: params.deadline,
|
|
1470
|
+
logger: log
|
|
1471
|
+
});
|
|
1472
|
+
if (!hasMaterializedMedia(mediaList)) {
|
|
1473
|
+
const attachmentIds = extractMSTeamsHtmlAttachmentIds(attachments);
|
|
1474
|
+
const hasHtmlFileAttachment = attachmentIds.length > 0;
|
|
1475
|
+
const hasChannelOrGroupHtml = shouldAttemptMSTeamsGraphMediaFallback({
|
|
1476
|
+
conversationType,
|
|
1477
|
+
htmlSummary,
|
|
1478
|
+
graphMediaFallback: params.graphMediaFallback
|
|
1479
|
+
});
|
|
1480
|
+
const shouldFetchGraphMessage = hasHtmlFileAttachment || hasChannelOrGroupHtml;
|
|
1481
|
+
const isBotFrameworkPersonalChat = isBotFrameworkPersonalChatId(conversationId);
|
|
1482
|
+
if (hasHtmlFileAttachment && isBotFrameworkPersonalChat) if (!serviceUrl) log.debug?.("bot framework attachment skipped (missing serviceUrl)", {
|
|
1483
|
+
conversationType,
|
|
1484
|
+
conversationId
|
|
1485
|
+
});
|
|
1486
|
+
else {
|
|
1487
|
+
const bfMedia = await downloadMSTeamsBotFrameworkAttachments({
|
|
1488
|
+
serviceUrl,
|
|
1489
|
+
attachmentIds,
|
|
1490
|
+
tokenProvider,
|
|
1491
|
+
maxBytes,
|
|
1492
|
+
allowHosts,
|
|
1493
|
+
authAllowHosts: params.authAllowHosts,
|
|
1494
|
+
preserveFilenames,
|
|
1495
|
+
deadline: params.deadline
|
|
1496
|
+
});
|
|
1497
|
+
if (bfMedia.media.length > 0) mediaList = mergeMSTeamsMediaFacts(mediaList, bfMedia.media, { positionallyAligned: false });
|
|
1498
|
+
if (!hasMaterializedMedia(bfMedia.media)) log.debug?.("bot framework attachments fetch empty", {
|
|
1499
|
+
conversationType,
|
|
1500
|
+
attachmentCount: bfMedia.attachmentCount ?? attachmentIds.length
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
if (shouldFetchGraphMessage && !hasMaterializedMedia(mediaList) && !isBotFrameworkPersonalChat) {
|
|
1504
|
+
const graphTeamAadGroupId = conversationType.trim().toLowerCase() === "channel" && !teamAadGroupId ? await params.resolveTeamAadGroupId?.() : teamAadGroupId;
|
|
1505
|
+
const messageUrl = buildMSTeamsGraphMessageUrl({
|
|
1506
|
+
conversationType,
|
|
1507
|
+
conversationId,
|
|
1508
|
+
messageId: activity.id ?? void 0,
|
|
1509
|
+
threadRootMessageId: conversationMessageId ?? activity.replyToId,
|
|
1510
|
+
teamAadGroupId: graphTeamAadGroupId,
|
|
1511
|
+
channelId: activity.channelData?.channel?.id
|
|
1512
|
+
});
|
|
1513
|
+
if (!messageUrl) log.debug?.("graph message url unavailable", {
|
|
1514
|
+
conversationType,
|
|
1515
|
+
hasChannelData: Boolean(activity.channelData),
|
|
1516
|
+
messageId: activity.id ?? void 0,
|
|
1517
|
+
replyToId: activity.replyToId ?? void 0
|
|
1518
|
+
});
|
|
1519
|
+
else {
|
|
1520
|
+
const graphMedia = await downloadMSTeamsGraphMedia({
|
|
1521
|
+
messageUrl,
|
|
1522
|
+
tokenProvider,
|
|
1523
|
+
maxBytes,
|
|
1524
|
+
allowHosts,
|
|
1525
|
+
authAllowHosts: params.authAllowHosts,
|
|
1526
|
+
preserveFilenames,
|
|
1527
|
+
deadline: params.deadline,
|
|
1528
|
+
logger: log
|
|
1529
|
+
});
|
|
1530
|
+
if (graphMedia.media.length > 0) mediaList = mergeMSTeamsMediaFacts(mediaList, graphMedia.media, { positionallyAligned: false });
|
|
1531
|
+
if (!hasMaterializedMedia(mediaList)) log.debug?.("graph media fetch empty", {
|
|
1532
|
+
messageUrl,
|
|
1533
|
+
hostedStatus: graphMedia.hostedStatus,
|
|
1534
|
+
attachmentStatus: graphMedia.attachmentStatus,
|
|
1535
|
+
hostedCount: graphMedia.hostedCount,
|
|
1536
|
+
attachmentCount: graphMedia.attachmentCount,
|
|
1537
|
+
tokenError: graphMedia.tokenError,
|
|
1538
|
+
attachmentIdCount: attachmentIds.length
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
const materializedCount = mediaList.filter((media) => Boolean(media.path)).length;
|
|
1544
|
+
if (materializedCount > 0) log.debug?.("downloaded attachments", { count: materializedCount });
|
|
1545
|
+
else if (htmlSummary?.imgTags) log.debug?.("inline images detected but none downloaded", {
|
|
1546
|
+
imgTags: htmlSummary.imgTags,
|
|
1547
|
+
srcHosts: htmlSummary.srcHosts,
|
|
1548
|
+
dataImages: htmlSummary.dataImages,
|
|
1549
|
+
cidImages: htmlSummary.cidImages
|
|
1550
|
+
});
|
|
1551
|
+
return mediaList;
|
|
1552
|
+
}
|
|
1553
|
+
//#endregion
|
|
1554
|
+
//#region extensions/msteams/src/monitor-handler/inbound-content.ts
|
|
1555
|
+
async function prepareMSTeamsInboundContent(params) {
|
|
1556
|
+
const activity = params.entry.context.activity;
|
|
1557
|
+
const mayRecoverGraphMedia = Boolean(params.htmlSummary?.attachmentIds.length) || shouldAttemptMSTeamsGraphMediaFallback({
|
|
1558
|
+
conversationType: params.conversationType,
|
|
1559
|
+
htmlSummary: params.htmlSummary,
|
|
1560
|
+
graphMediaFallback: params.graphMediaFallback
|
|
1561
|
+
});
|
|
1562
|
+
if (!params.rawBody && params.advertisedMedia.length === 0 && !mayRecoverGraphMedia) {
|
|
1563
|
+
params.log.debug?.("skipping empty message after stripping mentions");
|
|
1564
|
+
return null;
|
|
1565
|
+
}
|
|
1566
|
+
let mediaList = [];
|
|
1567
|
+
try {
|
|
1568
|
+
mediaList = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
1569
|
+
deadline: params.deadline,
|
|
1570
|
+
label: "MS Teams inbound media",
|
|
1571
|
+
work: () => resolveMSTeamsInboundMedia({
|
|
1572
|
+
attachments: params.entry.attachments,
|
|
1573
|
+
htmlSummary: params.htmlSummary,
|
|
1574
|
+
maxBytes: params.mediaMaxBytes,
|
|
1575
|
+
tokenProvider: params.tokenProvider,
|
|
1576
|
+
allowHosts: params.mediaAllowHosts,
|
|
1577
|
+
authAllowHosts: params.mediaAuthAllowHosts,
|
|
1578
|
+
graphMediaFallback: params.graphMediaFallback,
|
|
1579
|
+
conversationType: params.conversationType,
|
|
1580
|
+
conversationId: params.conversationId,
|
|
1581
|
+
conversationMessageId: params.conversationMessageId,
|
|
1582
|
+
teamAadGroupId: params.teamAadGroupId,
|
|
1583
|
+
resolveTeamAadGroupId: params.resolveTeamAadGroupId,
|
|
1584
|
+
serviceUrl: activity.serviceUrl,
|
|
1585
|
+
activity: {
|
|
1586
|
+
id: activity.id,
|
|
1587
|
+
replyToId: activity.replyToId,
|
|
1588
|
+
channelData: activity.channelData
|
|
1589
|
+
},
|
|
1590
|
+
log: params.log,
|
|
1591
|
+
deadline: params.deadline,
|
|
1592
|
+
preserveFilenames: false
|
|
1593
|
+
})
|
|
1594
|
+
});
|
|
1595
|
+
} catch (err) {
|
|
1596
|
+
params.log.debug?.("failed to resolve inbound Teams media", { error: require_setup_surface.formatUnknownError(err) });
|
|
1597
|
+
}
|
|
1598
|
+
const inboundMedia = mergeMSTeamsMediaFacts(params.advertisedMedia, mediaList);
|
|
1599
|
+
const nativeMediaForComparison = [...params.advertisedMedia, ...mediaList.slice(params.advertisedMedia.length).map((media) => ({
|
|
1600
|
+
contentType: media.contentType,
|
|
1601
|
+
kind: media.kind
|
|
1602
|
+
}))];
|
|
1603
|
+
const agentBody = resolveMSTeamsInboundMediaBody({
|
|
1604
|
+
body: params.rawBody,
|
|
1605
|
+
nativeMedia: nativeMediaForComparison,
|
|
1606
|
+
materializedMedia: inboundMedia
|
|
1607
|
+
});
|
|
1608
|
+
if (!agentBody && inboundMedia.length === 0) {
|
|
1609
|
+
params.log.debug?.("skipping empty message after Graph media recovery");
|
|
1610
|
+
return null;
|
|
1611
|
+
}
|
|
1612
|
+
return {
|
|
1613
|
+
agentBody,
|
|
1614
|
+
inboundMedia
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
//#endregion
|
|
1618
|
+
//#region extensions/msteams/src/reply-stream-controller.ts
|
|
1619
|
+
function isStreamCancelledError(err) {
|
|
1620
|
+
return err instanceof Error && err.name === "StreamCancelledError";
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Bridges openclaw's reply pipeline callbacks to the SDK's `ctx.stream`.
|
|
1624
|
+
* Streaming is enabled for personal (DM) conversations only; group/channel
|
|
1625
|
+
* messages fall through to block delivery.
|
|
1626
|
+
*
|
|
1627
|
+
* Streaming modes (resolved from `cfg.channels.msteams.streaming.preview`):
|
|
1628
|
+
* - "partial" (default): per-token streaming via `stream.emit(text)`. Each
|
|
1629
|
+
* chunk goes onto the live preview card in Teams.
|
|
1630
|
+
* - "progress": no per-token streaming; the preview card carries an
|
|
1631
|
+
* informative status that updates as tools run (e.g. "Looking up the
|
|
1632
|
+
* schema..." → "Generating SQL..."). When tool-progress streaming is also
|
|
1633
|
+
* enabled, raw tool names appear as bullets above the label.
|
|
1634
|
+
* - "block": disable native streaming entirely; the reply lands as a regular
|
|
1635
|
+
* block message. We bypass the controller in that case.
|
|
1636
|
+
*/
|
|
1637
|
+
function createTeamsReplyStreamController(params) {
|
|
1638
|
+
const isPersonal = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(params.conversationType) === "personal";
|
|
1639
|
+
const streamMode = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelPreviewStreamMode)(params.msteamsConfig, "partial");
|
|
1640
|
+
const shouldUseNativeStream = params.allowProviderPreview && isPersonal && (streamMode === "partial" || streamMode === "progress");
|
|
1641
|
+
const shouldStreamPreviewToolProgress = streamMode === "progress" && (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingPreviewToolProgress)(params.msteamsConfig, true, streamMode);
|
|
1642
|
+
const stream = shouldUseNativeStream ? params.context.stream : void 0;
|
|
1643
|
+
let tokensEmitted = false;
|
|
1644
|
+
let nativeDispatchStarted = false;
|
|
1645
|
+
let nativeDeliveryClaimed = false;
|
|
1646
|
+
let streamFinalizationPending = false;
|
|
1647
|
+
let canceledLocally = false;
|
|
1648
|
+
let streamFailed = false;
|
|
1649
|
+
let lastInformativeText = "";
|
|
1650
|
+
let progressLines = [];
|
|
1651
|
+
let latestPlan;
|
|
1652
|
+
let latestPlanExplanation;
|
|
1653
|
+
let pendingFinalPayload;
|
|
1654
|
+
let emittedText = "";
|
|
1655
|
+
let acknowledgedText = "";
|
|
1656
|
+
let acknowledgedStreamId;
|
|
1657
|
+
const streamEvents = stream?.events;
|
|
1658
|
+
let streamChunkSubscription;
|
|
1659
|
+
if (typeof streamEvents?.on === "function" && typeof streamEvents.off === "function") streamChunkSubscription = streamEvents.on("chunk", (activity) => {
|
|
1660
|
+
if (activity.type !== "typing" || activity.channelData?.streamType !== "streaming" || !activity.id || !activity.text || acknowledgedStreamId !== void 0 && activity.id !== acknowledgedStreamId || !activity.text.startsWith(acknowledgedText) || !emittedText.startsWith(activity.text)) return;
|
|
1661
|
+
acknowledgedStreamId = activity.id;
|
|
1662
|
+
acknowledgedText = activity.text;
|
|
1663
|
+
});
|
|
1664
|
+
const wasCanceled = () => canceledLocally || Boolean(stream?.canceled);
|
|
1665
|
+
const releaseStreamChunkSubscription = () => {
|
|
1666
|
+
if (streamChunkSubscription === void 0) return;
|
|
1667
|
+
streamEvents?.off(streamChunkSubscription);
|
|
1668
|
+
streamChunkSubscription = void 0;
|
|
1669
|
+
};
|
|
1670
|
+
const fallbackPayloadAfterAcknowledgedText = (payload) => {
|
|
1671
|
+
if (!acknowledgedText || typeof payload.text !== "string" || !payload.text.startsWith(acknowledgedText)) return payload;
|
|
1672
|
+
const remainingText = payload.text.slice(acknowledgedText.length);
|
|
1673
|
+
const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
|
|
1674
|
+
if (!remainingText && !hasMedia) return;
|
|
1675
|
+
return {
|
|
1676
|
+
...payload,
|
|
1677
|
+
text: remainingText || void 0
|
|
1678
|
+
};
|
|
1679
|
+
};
|
|
1680
|
+
const fallbackPayloadForSuppressedFinal = (payload) => {
|
|
1681
|
+
return Boolean(payload.mediaUrl || payload.mediaUrls?.length) ? {
|
|
1682
|
+
...payload,
|
|
1683
|
+
mediaUrl: void 0,
|
|
1684
|
+
mediaUrls: void 0
|
|
1685
|
+
} : payload;
|
|
1686
|
+
};
|
|
1687
|
+
/**
|
|
1688
|
+
* Render the current informative status line into the streaming card. Pulls
|
|
1689
|
+
* the rotating "Thinking..." label from msteams config (or the plugin-sdk
|
|
1690
|
+
* default) and prepends collected tool-progress lines when configured.
|
|
1691
|
+
*/
|
|
1692
|
+
const renderInformativeUpdate = () => {
|
|
1693
|
+
if (!stream || wasCanceled() || streamFinalizationPending) return;
|
|
1694
|
+
const informativeText = (0, openclaw_plugin_sdk_channel_outbound.formatChannelProgressDraftText)({
|
|
1381
1695
|
entry: params.msteamsConfig,
|
|
1382
1696
|
lines: shouldStreamPreviewToolProgress ? progressLines : [],
|
|
1383
1697
|
seed: params.progressSeed,
|
|
@@ -1418,6 +1732,7 @@ function createTeamsReplyStreamController(params) {
|
|
|
1418
1732
|
stream.emit(delta);
|
|
1419
1733
|
emittedText = fullText;
|
|
1420
1734
|
tokensEmitted = true;
|
|
1735
|
+
nativeDispatchStarted = true;
|
|
1421
1736
|
} catch (err) {
|
|
1422
1737
|
if (isStreamCancelledError(err)) {
|
|
1423
1738
|
canceledLocally = true;
|
|
@@ -1476,8 +1791,17 @@ function createTeamsReplyStreamController(params) {
|
|
|
1476
1791
|
text: void 0
|
|
1477
1792
|
} : void 0;
|
|
1478
1793
|
}
|
|
1794
|
+
if (streamFailed) {
|
|
1795
|
+
const fallback = fallbackPayloadAfterAcknowledgedText(payload);
|
|
1796
|
+
pendingFinalPayload = void 0;
|
|
1797
|
+
acknowledgedText = "";
|
|
1798
|
+
acknowledgedStreamId = void 0;
|
|
1799
|
+
releaseStreamChunkSubscription();
|
|
1800
|
+
return fallback;
|
|
1801
|
+
}
|
|
1479
1802
|
if (streamMode === "progress" && payload.text) try {
|
|
1480
1803
|
stream.emit(payload.text);
|
|
1804
|
+
nativeDispatchStarted = true;
|
|
1481
1805
|
pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
|
|
1482
1806
|
streamFinalizationPending = true;
|
|
1483
1807
|
return Boolean(payload.mediaUrl || payload.mediaUrls?.length) ? {
|
|
@@ -1493,18 +1817,39 @@ function createTeamsReplyStreamController(params) {
|
|
|
1493
1817
|
}
|
|
1494
1818
|
return payload;
|
|
1495
1819
|
},
|
|
1820
|
+
claimNativeDelivery() {
|
|
1821
|
+
if (!nativeDispatchStarted || nativeDeliveryClaimed) return false;
|
|
1822
|
+
nativeDeliveryClaimed = true;
|
|
1823
|
+
return true;
|
|
1824
|
+
},
|
|
1496
1825
|
async finalize() {
|
|
1497
1826
|
progressDraftGate.cancel();
|
|
1498
|
-
if (!stream || !
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
"@id": "",
|
|
1504
|
-
additionalType: ["AIGeneratedContent"]
|
|
1505
|
-
}];
|
|
1506
|
-
const finalChannelData = params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {};
|
|
1827
|
+
if (!stream || !nativeDispatchStarted) {
|
|
1828
|
+
releaseStreamChunkSubscription();
|
|
1829
|
+
return { visibleReplySent: false };
|
|
1830
|
+
}
|
|
1831
|
+
const content = wasCanceled() ? acknowledgedText || void 0 : pendingFinalPayload?.text ?? (emittedText || void 0);
|
|
1507
1832
|
try {
|
|
1833
|
+
if (wasCanceled()) {
|
|
1834
|
+
pendingFinalPayload = void 0;
|
|
1835
|
+
streamFinalizationPending = false;
|
|
1836
|
+
return {
|
|
1837
|
+
visibleReplySent: content !== void 0,
|
|
1838
|
+
...content === void 0 ? {} : { content }
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
if (!streamFinalizationPending) return {
|
|
1842
|
+
visibleReplySent: true,
|
|
1843
|
+
...content === void 0 ? {} : { content }
|
|
1844
|
+
};
|
|
1845
|
+
const finalEntities = [{
|
|
1846
|
+
type: "https://schema.org/Message",
|
|
1847
|
+
"@type": "Message",
|
|
1848
|
+
"@context": "https://schema.org",
|
|
1849
|
+
"@id": "",
|
|
1850
|
+
additionalType: ["AIGeneratedContent"]
|
|
1851
|
+
}];
|
|
1852
|
+
const finalChannelData = params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {};
|
|
1508
1853
|
stream.emit({
|
|
1509
1854
|
type: "message",
|
|
1510
1855
|
entities: finalEntities,
|
|
@@ -1515,23 +1860,43 @@ function createTeamsReplyStreamController(params) {
|
|
|
1515
1860
|
if (!result) {
|
|
1516
1861
|
const fallback = pendingFinalPayload;
|
|
1517
1862
|
pendingFinalPayload = void 0;
|
|
1518
|
-
|
|
1863
|
+
const fallbackPayload = fallback && !wasCanceled() ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
1864
|
+
return {
|
|
1865
|
+
visibleReplySent: true,
|
|
1866
|
+
...content === void 0 ? {} : { content },
|
|
1867
|
+
...fallbackPayload ? { fallbackPayload } : {}
|
|
1868
|
+
};
|
|
1519
1869
|
}
|
|
1520
1870
|
pendingFinalPayload = void 0;
|
|
1521
|
-
|
|
1871
|
+
const messageId = require_messenger.extractMessageId(result) ?? void 0;
|
|
1872
|
+
return {
|
|
1873
|
+
visibleReplySent: true,
|
|
1874
|
+
...content === void 0 ? {} : { content },
|
|
1875
|
+
...messageId ? { messageId } : {}
|
|
1876
|
+
};
|
|
1522
1877
|
} catch (err) {
|
|
1523
1878
|
if (isStreamCancelledError(err)) {
|
|
1524
1879
|
canceledLocally = true;
|
|
1525
1880
|
pendingFinalPayload = void 0;
|
|
1526
1881
|
streamFinalizationPending = false;
|
|
1527
|
-
return
|
|
1882
|
+
return {
|
|
1883
|
+
visibleReplySent: true,
|
|
1884
|
+
...content === void 0 ? {} : { content }
|
|
1885
|
+
};
|
|
1528
1886
|
}
|
|
1529
1887
|
streamFailed = true;
|
|
1530
1888
|
streamFinalizationPending = false;
|
|
1531
1889
|
params.log?.warn?.(`msteams stream finalize failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1532
1890
|
const fallback = pendingFinalPayload;
|
|
1533
1891
|
pendingFinalPayload = void 0;
|
|
1534
|
-
|
|
1892
|
+
const fallbackPayload = fallback ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
1893
|
+
return {
|
|
1894
|
+
visibleReplySent: true,
|
|
1895
|
+
...content === void 0 ? {} : { content },
|
|
1896
|
+
...fallbackPayload ? { fallbackPayload } : {}
|
|
1897
|
+
};
|
|
1898
|
+
} finally {
|
|
1899
|
+
releaseStreamChunkSubscription();
|
|
1535
1900
|
}
|
|
1536
1901
|
},
|
|
1537
1902
|
hasStream() {
|
|
@@ -1616,7 +1981,9 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1616
1981
|
resolveChannelLimitMb: ({ cfg }) => cfg.channels?.msteams?.mediaMaxMb
|
|
1617
1982
|
});
|
|
1618
1983
|
const feedbackLoopEnabled = params.cfg.channels?.msteams?.feedbackEnabled !== false;
|
|
1984
|
+
const hookRunner = (0, openclaw_plugin_sdk_plugin_runtime.getGlobalHookRunner)();
|
|
1619
1985
|
const streamController = createTeamsReplyStreamController({
|
|
1986
|
+
allowProviderPreview: !((hookRunner?.hasHooks("reply_payload_sending") ?? false) || (hookRunner?.hasHooks("message_sending") ?? false)),
|
|
1620
1987
|
conversationType,
|
|
1621
1988
|
context: params.context,
|
|
1622
1989
|
feedbackLoopEnabled,
|
|
@@ -1630,7 +1997,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1630
1997
|
const blockStreamingResolved = teamsStreamMode === "block" ? true : (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingBlockEnabled)(msteamsCfg);
|
|
1631
1998
|
const blockStreamingEnabled = blockStreamingResolved ?? false;
|
|
1632
1999
|
const typingIndicatorEnabled = typeof msteamsCfg?.typingIndicator === "boolean" ? msteamsCfg.typingIndicator : true;
|
|
1633
|
-
const
|
|
2000
|
+
const pendingDeliveries = [];
|
|
1634
2001
|
const sendMessages = async (messages) => {
|
|
1635
2002
|
return require_messenger.sendMSTeamsMessages({
|
|
1636
2003
|
replyStyle: params.replyStyle,
|
|
@@ -1669,33 +2036,71 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1669
2036
|
contextKey: `msteams:delivery-failure:${params.conversationRef.conversation?.id ?? "unknown"}`
|
|
1670
2037
|
});
|
|
1671
2038
|
};
|
|
1672
|
-
const
|
|
1673
|
-
|
|
2039
|
+
const renderReplyPayload = (payload) => {
|
|
2040
|
+
return require_messenger.renderReplyPayloadsToMessages([payload], {
|
|
1674
2041
|
textChunkLimit: params.textLimit,
|
|
1675
2042
|
chunkText: true,
|
|
1676
2043
|
mediaMode: "split",
|
|
1677
2044
|
tableMode,
|
|
1678
2045
|
chunkMode
|
|
1679
2046
|
});
|
|
1680
|
-
|
|
2047
|
+
};
|
|
2048
|
+
const deliveryOutcome = (delivery) => {
|
|
2049
|
+
const messageIds = [...delivery.nativeMessageId ? [delivery.nativeMessageId] : [], ...delivery.messageIds];
|
|
2050
|
+
return {
|
|
2051
|
+
visibleReplySent: delivery.visibleReplySent,
|
|
2052
|
+
...messageIds.length > 0 ? { messageIds } : {},
|
|
2053
|
+
...delivery.visibleReplySent && delivery.content !== void 0 ? { content: delivery.content } : {}
|
|
2054
|
+
};
|
|
2055
|
+
};
|
|
2056
|
+
const settlePendingDelivery = (delivery) => {
|
|
2057
|
+
if (delivery.settled || !delivery.blockSettled || delivery.native && !delivery.nativeSettled) return;
|
|
2058
|
+
delivery.settled = true;
|
|
2059
|
+
const outcome = deliveryOutcome(delivery);
|
|
2060
|
+
if (delivery.errors.length === 0) {
|
|
2061
|
+
delivery.finalization.resolve(outcome);
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
const error = delivery.errors.find((candidate) => !(candidate instanceof openclaw_plugin_sdk_error_runtime.PlatformMessageNotDispatchedError)) ?? delivery.errors[0];
|
|
2065
|
+
delivery.finalization.reject(delivery.visibleReplySent ? (0, openclaw_plugin_sdk_channel_inbound.createChannelPartialDeliveryError)(error, {
|
|
2066
|
+
...outcome,
|
|
2067
|
+
visibleReplySent: true
|
|
2068
|
+
}) : error);
|
|
2069
|
+
};
|
|
2070
|
+
const queueReplyPayload = (payload, messages, native) => {
|
|
2071
|
+
const delivery = {
|
|
2072
|
+
messages,
|
|
2073
|
+
finalization: (0, openclaw_plugin_sdk_extension_shared.createDeferred)(),
|
|
2074
|
+
content: payload.text,
|
|
2075
|
+
native,
|
|
2076
|
+
nativeSettled: !native,
|
|
2077
|
+
blockSettled: messages.length === 0,
|
|
2078
|
+
settled: false,
|
|
2079
|
+
visibleReplySent: false,
|
|
2080
|
+
messageIds: [],
|
|
2081
|
+
errors: []
|
|
2082
|
+
};
|
|
2083
|
+
pendingDeliveries.push(delivery);
|
|
2084
|
+
return delivery;
|
|
1681
2085
|
};
|
|
1682
2086
|
const flushPendingMessages = async () => {
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
try {
|
|
1688
|
-
ids = await sendMessages(toSend);
|
|
1689
|
-
} catch (batchError) {
|
|
1690
|
-
ids = [];
|
|
2087
|
+
for (const delivery of pendingDeliveries) {
|
|
2088
|
+
if (delivery.blockSettled) continue;
|
|
2089
|
+
const toSend = delivery.messages.splice(0);
|
|
2090
|
+
const total = toSend.length;
|
|
1691
2091
|
let failed = 0;
|
|
1692
|
-
let lastFailedError
|
|
2092
|
+
let lastFailedError;
|
|
2093
|
+
const sentIds = [];
|
|
1693
2094
|
for (const msg of toSend) try {
|
|
1694
2095
|
const msgIds = await sendMessages([msg]);
|
|
1695
|
-
|
|
2096
|
+
delivery.visibleReplySent ||= msgIds.length > 0;
|
|
2097
|
+
const validIds = msgIds.filter((id) => id.trim() && id !== "unknown");
|
|
2098
|
+
delivery.messageIds.push(...validIds);
|
|
2099
|
+
sentIds.push(...validIds);
|
|
1696
2100
|
} catch (msgError) {
|
|
1697
2101
|
failed += 1;
|
|
1698
2102
|
lastFailedError = msgError;
|
|
2103
|
+
delivery.errors.push(msgError);
|
|
1699
2104
|
params.log.debug?.("individual message send failed, continuing with remaining blocks");
|
|
1700
2105
|
}
|
|
1701
2106
|
if (failed > 0) {
|
|
@@ -1709,8 +2114,14 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1709
2114
|
error: lastFailedError
|
|
1710
2115
|
});
|
|
1711
2116
|
}
|
|
2117
|
+
delivery.blockSettled = true;
|
|
2118
|
+
settlePendingDelivery(delivery);
|
|
2119
|
+
if (sentIds.length > 0) try {
|
|
2120
|
+
params.onSentMessageIds?.(sentIds);
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
params.log.warn?.("failed to record sent Teams message ids", { error: require_setup_surface.formatUnknownError(error) });
|
|
2123
|
+
}
|
|
1712
2124
|
}
|
|
1713
|
-
if (ids.length > 0) params.onSentMessageIds?.(ids);
|
|
1714
2125
|
};
|
|
1715
2126
|
const dispatcherOptions = {
|
|
1716
2127
|
...replyPipeline,
|
|
@@ -1722,11 +2133,22 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1722
2133
|
typingCallbacks
|
|
1723
2134
|
};
|
|
1724
2135
|
const delivery = {
|
|
2136
|
+
observeMessageSent: true,
|
|
1725
2137
|
deliver: async (payload) => {
|
|
1726
2138
|
const preparedPayload = streamController.preparePayload(payload);
|
|
1727
|
-
|
|
1728
|
-
|
|
2139
|
+
const native = streamController.claimNativeDelivery();
|
|
2140
|
+
const messages = preparedPayload ? renderReplyPayload(preparedPayload) : [];
|
|
2141
|
+
if (!native && messages.length === 0) return {
|
|
2142
|
+
visibleReplySent: false,
|
|
2143
|
+
suppression: { reason: "no_visible_result" }
|
|
2144
|
+
};
|
|
2145
|
+
const pending = queueReplyPayload(payload, messages, native);
|
|
1729
2146
|
if (blockStreamingEnabled) await flushPendingMessages();
|
|
2147
|
+
settlePendingDelivery(pending);
|
|
2148
|
+
return {
|
|
2149
|
+
visibleReplySent: false,
|
|
2150
|
+
finalization: pending.finalization.promise
|
|
2151
|
+
};
|
|
1730
2152
|
},
|
|
1731
2153
|
onError: (err, info) => {
|
|
1732
2154
|
const errMsg = require_setup_surface.formatUnknownError(err);
|
|
@@ -1741,30 +2163,41 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1741
2163
|
});
|
|
1742
2164
|
}
|
|
1743
2165
|
};
|
|
1744
|
-
const settleDelivery = () => {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
2166
|
+
const settleDelivery = async () => {
|
|
2167
|
+
await flushPendingMessages();
|
|
2168
|
+
const nativeDelivery = pendingDeliveries.find((candidate) => candidate.native && !candidate.nativeSettled);
|
|
2169
|
+
if (!nativeDelivery) {
|
|
2170
|
+
await streamController.finalize();
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
let nativeResult;
|
|
2174
|
+
try {
|
|
2175
|
+
nativeResult = await streamController.finalize();
|
|
2176
|
+
} catch (error) {
|
|
2177
|
+
nativeDelivery.errors.push(error);
|
|
2178
|
+
nativeDelivery.nativeSettled = true;
|
|
2179
|
+
settlePendingDelivery(nativeDelivery);
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
nativeDelivery.visibleReplySent ||= nativeResult.visibleReplySent;
|
|
2183
|
+
nativeDelivery.nativeMessageId = nativeResult.messageId;
|
|
2184
|
+
if (nativeResult.content !== void 0) nativeDelivery.content = nativeResult.content;
|
|
2185
|
+
if (nativeResult.fallbackPayload) {
|
|
2186
|
+
nativeDelivery.messages.push(...renderReplyPayload(nativeResult.fallbackPayload));
|
|
2187
|
+
nativeDelivery.blockSettled = nativeDelivery.messages.length === 0;
|
|
2188
|
+
}
|
|
2189
|
+
nativeDelivery.nativeSettled = true;
|
|
2190
|
+
if (!nativeDelivery.blockSettled) await flushPendingMessages();
|
|
2191
|
+
settlePendingDelivery(nativeDelivery);
|
|
2192
|
+
if (nativeResult.messageId) try {
|
|
2193
|
+
params.onSentMessageIds?.([nativeResult.messageId]);
|
|
2194
|
+
} catch (error) {
|
|
2195
|
+
params.log.warn?.("failed to record sent Teams message id", { error: require_setup_surface.formatUnknownError(error) });
|
|
2196
|
+
}
|
|
1764
2197
|
};
|
|
1765
|
-
const previewToolProgressEnabled = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingPreviewToolProgress)(msteamsCfg);
|
|
2198
|
+
const previewToolProgressEnabled = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingPreviewToolProgress)(msteamsCfg, true, teamsStreamMode);
|
|
1766
2199
|
const suppressDefaultToolProgressMessages = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingSuppressDefaultToolProgressMessages)(msteamsCfg);
|
|
1767
|
-
const shouldSuppressDefaultToolProgressMessages = teamsStreamMode === "progress" && suppressDefaultToolProgressMessages && previewToolProgressEnabled;
|
|
2200
|
+
const shouldSuppressDefaultToolProgressMessages = streamController.hasStream() && teamsStreamMode === "progress" && suppressDefaultToolProgressMessages && previewToolProgressEnabled;
|
|
1768
2201
|
const progressCallbacks = streamController.hasStream() ? {
|
|
1769
2202
|
onReasoningStream: async (payload) => {
|
|
1770
2203
|
const text = typeof payload?.text === "string" ? payload.text : void 0;
|
|
@@ -1901,238 +2334,571 @@ async function wasMSTeamsMessageSentWithPersistence(params) {
|
|
|
1901
2334
|
return await sentMessages.lookup(makeKey(params.conversationId, params.messageId));
|
|
1902
2335
|
}
|
|
1903
2336
|
//#endregion
|
|
1904
|
-
//#region extensions/msteams/src/
|
|
1905
|
-
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
const getTeamDetails = params.getTeamDetails;
|
|
1921
|
-
if (!getTeamDetails) return;
|
|
1922
|
-
const groupId = (await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
1923
|
-
deadline: params.deadline,
|
|
1924
|
-
label: "MS Teams team details",
|
|
1925
|
-
work: () => getTeamDetails(params.conversationTeamId)
|
|
1926
|
-
})).aadGroupId?.trim();
|
|
1927
|
-
if (!groupId) return;
|
|
1928
|
-
cacheTeamGroupId(params.conversationTeamId, groupId);
|
|
1929
|
-
return groupId;
|
|
1930
|
-
}
|
|
1931
|
-
//#endregion
|
|
1932
|
-
//#region extensions/msteams/src/monitor-handler/inbound-media.ts
|
|
1933
|
-
function shouldAttemptMSTeamsGraphMediaFallback(params) {
|
|
1934
|
-
const conversationType = params.conversationType.trim().toLowerCase();
|
|
1935
|
-
return params.graphMediaFallback === true && (conversationType === "channel" || conversationType === "groupchat") && (params.htmlSummary?.htmlAttachments ?? 0) > 0;
|
|
1936
|
-
}
|
|
1937
|
-
function resolveMSTeamsInboundMediaBody(params) {
|
|
1938
|
-
const unavailableCount = params.materializedMedia.filter((media) => !media.path).length + Math.max(0, params.nativeMedia.length - params.materializedMedia.length);
|
|
1939
|
-
if (unavailableCount === 0) return params.body;
|
|
1940
|
-
return (0, openclaw_plugin_sdk_channel_inbound.formatInboundMediaUnavailableText)({
|
|
1941
|
-
body: params.body,
|
|
1942
|
-
notice: `[msteams ${unavailableCount > 1 ? `${unavailableCount} attachments` : "attachment"} unavailable]`
|
|
2337
|
+
//#region extensions/msteams/src/monitor-handler/inbound-dispatch.ts
|
|
2338
|
+
async function dispatchMSTeamsInboundTurn(params) {
|
|
2339
|
+
const core = require_runtime.getMSTeamsRuntime();
|
|
2340
|
+
const { cfg, runtime, appId, app, tokenProvider, textLimit, log, logVerboseMessage, facts, admission, content, routing, thread, replyStyle, timestamp, contextVisibilityMode, conversationHistories, historyLimit } = params;
|
|
2341
|
+
const { context, activity, rawBody, text, quoteInfo, conversationRef } = facts;
|
|
2342
|
+
const { senderId, senderName, isDirectMessage, allowNameMatching, groupPolicy, commandAuthorized, effectiveGroupAllowFrom } = admission;
|
|
2343
|
+
const { route } = routing;
|
|
2344
|
+
const { agentBody, inboundMedia } = content;
|
|
2345
|
+
const { teamAadGroupId, quoteBodyFull, quoteSenderId, quoteSenderName, threadContext } = thread;
|
|
2346
|
+
const { conversationId, conversationType, isChannel, teamId, graphChannelId } = facts;
|
|
2347
|
+
const teamsFrom = isDirectMessage ? `msteams:${senderId}` : isChannel ? `msteams:channel:${conversationId}` : `msteams:group:${conversationId}`;
|
|
2348
|
+
const teamsTo = isDirectMessage ? `user:${senderId}` : `conversation:${conversationId}`;
|
|
2349
|
+
const envelopeFrom = isDirectMessage ? senderName : conversationType;
|
|
2350
|
+
const buildEnvelope = (0, openclaw_plugin_sdk_channel_inbound.createChannelInboundEnvelopeBuilder)({
|
|
2351
|
+
cfg,
|
|
2352
|
+
route
|
|
1943
2353
|
});
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
function mergeMSTeamsMediaFacts(nativeMedia, materializedMedia, options = {}) {
|
|
1950
|
-
const merged = [...nativeMedia];
|
|
1951
|
-
const nativeSlotCount = nativeMedia.length;
|
|
1952
|
-
const nativeIndexBySourceId = /* @__PURE__ */ new Map();
|
|
1953
|
-
nativeMedia.forEach((media, index) => {
|
|
1954
|
-
if (media.sourceId && !nativeIndexBySourceId.has(media.sourceId)) nativeIndexBySourceId.set(media.sourceId, index);
|
|
2354
|
+
let combinedBody = buildEnvelope({
|
|
2355
|
+
channel: "Teams",
|
|
2356
|
+
from: envelopeFrom,
|
|
2357
|
+
timestamp,
|
|
2358
|
+
body: agentBody
|
|
1955
2359
|
});
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2360
|
+
const isRoomish = !isDirectMessage;
|
|
2361
|
+
const historyKey = isRoomish ? conversationId : void 0;
|
|
2362
|
+
if (isRoomish && historyKey) combinedBody = (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildPendingContext({
|
|
2363
|
+
historyKey,
|
|
2364
|
+
limit: historyLimit,
|
|
2365
|
+
currentMessage: combinedBody,
|
|
2366
|
+
formatEntry: (entry) => buildEnvelope({
|
|
2367
|
+
channel: "Teams",
|
|
2368
|
+
from: conversationType,
|
|
2369
|
+
timestamp: entry.timestamp,
|
|
2370
|
+
previousTimestamp: null,
|
|
2371
|
+
body: `${entry.sender}: ${entry.body}${entry.messageId ? ` [id:${entry.messageId}]` : ""}`
|
|
2372
|
+
})
|
|
2373
|
+
});
|
|
2374
|
+
const inboundHistory = isRoomish && historyKey && historyLimit > 0 ? (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildInboundHistory({
|
|
2375
|
+
historyKey,
|
|
2376
|
+
limit: historyLimit
|
|
2377
|
+
}) : void 0;
|
|
2378
|
+
const commandBody = text.trim();
|
|
2379
|
+
const quoteSenderAllowed = quoteInfo && quoteInfo.sender ? (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
|
|
2380
|
+
isGroup: !isDirectMessage,
|
|
2381
|
+
groupPolicy,
|
|
2382
|
+
allowFrom: effectiveGroupAllowFrom,
|
|
2383
|
+
isSenderAllowed: (allowFrom) => require_channel.resolveMSTeamsAllowlistMatch({
|
|
2384
|
+
allowFrom,
|
|
2385
|
+
senderId: quoteSenderId ?? "",
|
|
2386
|
+
senderName: quoteSenderName,
|
|
2387
|
+
allowNameMatching
|
|
2388
|
+
}).allowed
|
|
2389
|
+
}) : true;
|
|
2390
|
+
const bodyForAgent = threadContext ? `[Thread history]\n${threadContext}\n[/Thread history]\n\n${agentBody}` : agentBody;
|
|
2391
|
+
const nativeChannelId = isChannel && teamAadGroupId ? `${teamAadGroupId}/${graphChannelId}` : void 0;
|
|
2392
|
+
const ctxPayload = (0, openclaw_plugin_sdk_channel_inbound.buildChannelInboundEventContext)({
|
|
2393
|
+
channel: "msteams",
|
|
2394
|
+
contextVisibility: contextVisibilityMode,
|
|
2395
|
+
supplemental: { quote: quoteInfo ? {
|
|
2396
|
+
id: quoteInfo.id ?? activity.replyToId ?? void 0,
|
|
2397
|
+
body: quoteBodyFull ?? quoteInfo.body,
|
|
2398
|
+
sender: quoteInfo.sender,
|
|
2399
|
+
senderAllowed: quoteSenderAllowed,
|
|
2400
|
+
isQuote: true
|
|
2401
|
+
} : void 0 },
|
|
2402
|
+
media: await (0, openclaw_plugin_sdk_channel_inbound.toInboundMediaFactsWithMetadata)(inboundMedia),
|
|
2403
|
+
messageId: activity.id,
|
|
2404
|
+
timestamp: timestamp?.getTime() ?? Date.now(),
|
|
2405
|
+
from: teamsFrom,
|
|
2406
|
+
sender: {
|
|
2407
|
+
id: senderId,
|
|
2408
|
+
name: senderName
|
|
2409
|
+
},
|
|
2410
|
+
conversation: {
|
|
2411
|
+
kind: isDirectMessage ? "direct" : isChannel ? "channel" : "group",
|
|
2412
|
+
id: conversationId,
|
|
2413
|
+
label: envelopeFrom,
|
|
2414
|
+
spaceId: teamId,
|
|
2415
|
+
nativeChannelId
|
|
2416
|
+
},
|
|
2417
|
+
route: {
|
|
2418
|
+
agentId: route.agentId,
|
|
2419
|
+
dmScope: route.dmScope,
|
|
2420
|
+
accountId: route.accountId,
|
|
2421
|
+
routeSessionKey: route.sessionKey
|
|
2422
|
+
},
|
|
2423
|
+
reply: {
|
|
2424
|
+
to: teamsTo,
|
|
2425
|
+
replyToId: activity.replyToId ?? void 0,
|
|
2426
|
+
nativeChannelId
|
|
2427
|
+
},
|
|
2428
|
+
message: {
|
|
2429
|
+
body: combinedBody,
|
|
2430
|
+
bodyForAgent,
|
|
2431
|
+
inboundHistory,
|
|
2432
|
+
rawBody,
|
|
2433
|
+
commandBody
|
|
2434
|
+
},
|
|
2435
|
+
sessionTranscript: { historyLimit: isRoomish ? historyLimit : 0 },
|
|
2436
|
+
access: {
|
|
2437
|
+
mentions: {
|
|
2438
|
+
canDetectMention: !isDirectMessage,
|
|
2439
|
+
wasMentioned: isDirectMessage || params.mentionWasEffective
|
|
2440
|
+
},
|
|
2441
|
+
commands: { authorized: commandAuthorized === true }
|
|
2442
|
+
},
|
|
2443
|
+
extra: {
|
|
2444
|
+
GroupSubject: !isDirectMessage ? conversationType : void 0,
|
|
2445
|
+
ReplyToIsQuote: quoteInfo ? true : void 0
|
|
2446
|
+
}
|
|
2447
|
+
});
|
|
2448
|
+
const preview = (0, openclaw_plugin_sdk_text_utility_runtime.sliceUtf16Safe)(rawBody.replace(/\s+/g, " "), 0, 160);
|
|
2449
|
+
logVerboseMessage(`msteams inbound: from=${ctxPayload.From} preview="${preview}"`);
|
|
2450
|
+
const { dispatcherOptions, delivery, replyOptions } = createMSTeamsReplyDispatcher({
|
|
2451
|
+
cfg,
|
|
2452
|
+
agentId: route.agentId,
|
|
2453
|
+
sessionKey: route.sessionKey,
|
|
2454
|
+
accountId: route.accountId,
|
|
2455
|
+
runtime,
|
|
2456
|
+
log,
|
|
2457
|
+
app,
|
|
2458
|
+
appId,
|
|
2459
|
+
conversationRef,
|
|
2460
|
+
context,
|
|
2461
|
+
replyStyle,
|
|
2462
|
+
textLimit,
|
|
2463
|
+
onSentMessageIds: (ids) => {
|
|
2464
|
+
for (const id of ids) recordMSTeamsSentMessage(conversationId, id);
|
|
2465
|
+
},
|
|
2466
|
+
tokenProvider,
|
|
2467
|
+
sharePointSiteId: cfg.channels?.msteams?.sharePointSiteId
|
|
2468
|
+
});
|
|
2469
|
+
const senderTimezone = (activity.entities?.find((entity) => entity.type === "clientInfo"))?.timezone || conversationRef.timezone;
|
|
2470
|
+
const turnConfig = senderTimezone && !cfg.agents?.defaults?.userTimezone ? {
|
|
2471
|
+
...cfg,
|
|
2472
|
+
agents: {
|
|
2473
|
+
...cfg.agents,
|
|
2474
|
+
defaults: {
|
|
2475
|
+
...cfg.agents?.defaults,
|
|
2476
|
+
userTimezone: senderTimezone
|
|
2477
|
+
}
|
|
1977
2478
|
}
|
|
2479
|
+
} : cfg;
|
|
2480
|
+
log.info("dispatching to agent", { sessionKey: route.sessionKey });
|
|
2481
|
+
try {
|
|
2482
|
+
const turnResult = await core.channel.inbound.run({
|
|
2483
|
+
channel: "msteams",
|
|
2484
|
+
accountId: route.accountId,
|
|
2485
|
+
raw: context,
|
|
2486
|
+
adapter: {
|
|
2487
|
+
ingest: () => ({
|
|
2488
|
+
id: activity.id ?? `${teamsFrom}:${Date.now()}`,
|
|
2489
|
+
timestamp: timestamp?.getTime(),
|
|
2490
|
+
rawText: rawBody,
|
|
2491
|
+
textForAgent: bodyForAgent,
|
|
2492
|
+
textForCommands: commandBody,
|
|
2493
|
+
raw: activity
|
|
2494
|
+
}),
|
|
2495
|
+
resolveTurn: () => ({
|
|
2496
|
+
cfg: turnConfig,
|
|
2497
|
+
channel: "msteams",
|
|
2498
|
+
accountId: route.accountId,
|
|
2499
|
+
route: {
|
|
2500
|
+
agentId: route.agentId,
|
|
2501
|
+
sessionKey: route.sessionKey
|
|
2502
|
+
},
|
|
2503
|
+
ctxPayload,
|
|
2504
|
+
record: { onRecordError: (err) => {
|
|
2505
|
+
logVerboseMessage(`msteams: failed updating session meta: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2506
|
+
} },
|
|
2507
|
+
history: {
|
|
2508
|
+
isGroup: isRoomish,
|
|
2509
|
+
historyKey,
|
|
2510
|
+
historyMap: conversationHistories,
|
|
2511
|
+
limit: historyLimit
|
|
2512
|
+
},
|
|
2513
|
+
dispatcherOptions,
|
|
2514
|
+
delivery,
|
|
2515
|
+
replyOptions: {
|
|
2516
|
+
...replyOptions,
|
|
2517
|
+
...facts.turnAdoptionLifecycle ? (0, openclaw_plugin_sdk_channel_outbound.bindIngressLifecycleToReplyOptions)(facts.turnAdoptionLifecycle) : {}
|
|
2518
|
+
}
|
|
2519
|
+
})
|
|
2520
|
+
}
|
|
2521
|
+
});
|
|
2522
|
+
const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
|
|
2523
|
+
const counts = (0, openclaw_plugin_sdk_channel_inbound.resolveInboundReplyDispatchCounts)(dispatchResult);
|
|
2524
|
+
log.info("dispatch complete", {
|
|
2525
|
+
queuedFinal: dispatchResult?.queuedFinal ?? false,
|
|
2526
|
+
counts
|
|
2527
|
+
});
|
|
2528
|
+
if ((0, openclaw_plugin_sdk_channel_inbound.hasFinalInboundReplyDispatch)(dispatchResult)) logVerboseMessage(`msteams: delivered ${counts.final} repl${counts.final === 1 ? "y" : "ies"} to ${teamsTo}`);
|
|
2529
|
+
return {
|
|
2530
|
+
kind: "completed",
|
|
2531
|
+
finalResponses: counts.final
|
|
2532
|
+
};
|
|
2533
|
+
} catch (err) {
|
|
2534
|
+
log.error("dispatch failed", { error: require_setup_surface.formatUnknownError(err) });
|
|
2535
|
+
runtime.error(`msteams dispatch failed: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2536
|
+
if (facts.turnAdoptionLifecycle) throw err;
|
|
2537
|
+
try {
|
|
2538
|
+
await context.sendActivity("⚠️ Something went wrong. Please try again.");
|
|
2539
|
+
} catch {}
|
|
2540
|
+
return { kind: "failed" };
|
|
1978
2541
|
}
|
|
1979
|
-
return merged;
|
|
1980
2542
|
}
|
|
1981
|
-
|
|
1982
|
-
|
|
2543
|
+
//#endregion
|
|
2544
|
+
//#region extensions/msteams/src/monitor-handler/inbound-facts.ts
|
|
2545
|
+
function extractTextFromHtmlAttachments(attachments) {
|
|
2546
|
+
for (const attachment of attachments) {
|
|
2547
|
+
const raw = require_resolve_allowlist.extractHtmlFromAttachment(attachment);
|
|
2548
|
+
if (!raw) continue;
|
|
2549
|
+
const text = raw.replace(/<at[^>]*>.*?<\/at>/gis, " ").replace(/<a\b[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gis, "$2 $1").replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<[^>]+>/g, " ").replace(/ /gi, " ").replace(/&/gi, "&").replace(/\s+/g, " ").trim();
|
|
2550
|
+
if (text) return text;
|
|
2551
|
+
}
|
|
2552
|
+
return "";
|
|
1983
2553
|
}
|
|
1984
|
-
async function
|
|
1985
|
-
const
|
|
1986
|
-
|
|
2554
|
+
async function prepareMSTeamsDebounceEntry(params) {
|
|
2555
|
+
const activity = params.context.activity;
|
|
2556
|
+
const attachments = Array.isArray(activity.attachments) ? activity.attachments : [];
|
|
2557
|
+
const rawText = activity.text?.trim() ?? "";
|
|
2558
|
+
const htmlText = extractTextFromHtmlAttachments(attachments);
|
|
2559
|
+
const valueText = rawText || htmlText ? "" : serializeMSTeamsAdaptiveCardActionValue(activity.value);
|
|
2560
|
+
const text = require_resolve_allowlist.stripMSTeamsMentionTags(rawText || htmlText || valueText || "");
|
|
2561
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
2562
|
+
const replyToId = activity.replyToId ?? void 0;
|
|
2563
|
+
const implicitMentionKinds = conversationId && replyToId && await wasMSTeamsMessageSentWithPersistence({
|
|
2564
|
+
conversationId,
|
|
2565
|
+
messageId: replyToId
|
|
2566
|
+
}) ? ["reply_to_bot"] : [];
|
|
2567
|
+
return {
|
|
2568
|
+
context: params.context,
|
|
2569
|
+
rawText,
|
|
2570
|
+
text,
|
|
1987
2571
|
attachments,
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2572
|
+
wasMentioned: require_resolve_allowlist.wasMSTeamsBotMentioned(activity),
|
|
2573
|
+
implicitMentionKinds,
|
|
2574
|
+
turnAdoptionLifecycle: params.turnAdoptionLifecycle
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
function buildStoredConversationReference(params) {
|
|
2578
|
+
const { activity, conversationId, conversationType, teamId, threadId } = params;
|
|
2579
|
+
const from = activity.from;
|
|
2580
|
+
const conversation = activity.conversation;
|
|
2581
|
+
const clientInfo = activity.entities?.find((entity) => entity.type === "clientInfo");
|
|
2582
|
+
const tenantId = activity.channelData?.tenant?.id ?? conversation?.tenantId;
|
|
2583
|
+
const aadObjectId = from?.aadObjectId;
|
|
2584
|
+
const serviceUrl = require_resolve_allowlist.tryNormalizeBotFrameworkServiceUrl(activity.serviceUrl);
|
|
2585
|
+
return {
|
|
2586
|
+
activityId: activity.id,
|
|
2587
|
+
user: from ? {
|
|
2588
|
+
id: from.id,
|
|
2589
|
+
name: from.name,
|
|
2590
|
+
aadObjectId: from.aadObjectId
|
|
2591
|
+
} : void 0,
|
|
2592
|
+
agent: activity.recipient,
|
|
2593
|
+
conversation: {
|
|
2594
|
+
id: conversationId,
|
|
2000
2595
|
conversationType,
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2596
|
+
tenantId
|
|
2597
|
+
},
|
|
2598
|
+
...tenantId ? { tenantId } : {},
|
|
2599
|
+
...aadObjectId ? { aadObjectId } : {},
|
|
2600
|
+
teamId,
|
|
2601
|
+
channelId: activity.channelId,
|
|
2602
|
+
...serviceUrl ? { serviceUrl } : {},
|
|
2603
|
+
locale: activity.locale,
|
|
2604
|
+
...clientInfo?.timezone ? { timezone: clientInfo.timezone } : {},
|
|
2605
|
+
...threadId ? { threadId } : {}
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
function assembleMSTeamsInboundFacts(params) {
|
|
2609
|
+
const { entry, mediaMaxBytes } = params;
|
|
2610
|
+
const activity = entry.context.activity;
|
|
2611
|
+
const conversation = activity.conversation;
|
|
2612
|
+
const rawConversationId = conversation?.id ?? "";
|
|
2613
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(rawConversationId);
|
|
2614
|
+
const conversationMessageId = require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId);
|
|
2615
|
+
const conversationType = conversation?.conversationType ?? "personal";
|
|
2616
|
+
const isChannel = conversationType === "channel";
|
|
2617
|
+
const teamId = activity.channelData?.team?.id;
|
|
2618
|
+
const threadId = isChannel ? conversationMessageId ?? activity.replyToId ?? void 0 : void 0;
|
|
2619
|
+
const advertisedMedia = resolveMSTeamsAdvertisedMedia(entry.attachments, {
|
|
2620
|
+
maxInlineBytes: mediaMaxBytes,
|
|
2621
|
+
maxInlineTotalBytes: mediaMaxBytes
|
|
2622
|
+
});
|
|
2623
|
+
return {
|
|
2624
|
+
...entry,
|
|
2625
|
+
activity,
|
|
2626
|
+
from: activity.from,
|
|
2627
|
+
conversation,
|
|
2628
|
+
rawBody: entry.text,
|
|
2629
|
+
advertisedMedia,
|
|
2630
|
+
quoteInfo: require_resolve_allowlist.extractMSTeamsQuoteInfo(entry.attachments),
|
|
2631
|
+
attachmentTypes: entry.attachments.map((attachment) => typeof attachment.contentType === "string" ? attachment.contentType : void 0).filter(Boolean).slice(0, 3),
|
|
2632
|
+
htmlSummary: summarizeMSTeamsHtmlAttachments(entry.attachments),
|
|
2633
|
+
rawConversationId,
|
|
2634
|
+
conversationId,
|
|
2635
|
+
conversationMessageId,
|
|
2636
|
+
conversationType,
|
|
2637
|
+
isChannel,
|
|
2638
|
+
teamId,
|
|
2639
|
+
graphChannelId: activity.channelData?.channel?.id?.trim() || conversationId,
|
|
2640
|
+
threadId,
|
|
2641
|
+
conversationRef: buildStoredConversationReference({
|
|
2642
|
+
activity,
|
|
2643
|
+
conversationId,
|
|
2007
2644
|
conversationType,
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
if (shouldFetchGraphMessage && !hasMaterializedMedia(mediaList) && !isBotFrameworkPersonalChat) {
|
|
2028
|
-
const graphTeamAadGroupId = conversationType.trim().toLowerCase() === "channel" && !teamAadGroupId ? await params.resolveTeamAadGroupId?.() : teamAadGroupId;
|
|
2029
|
-
const messageUrl = buildMSTeamsGraphMessageUrl({
|
|
2030
|
-
conversationType,
|
|
2031
|
-
conversationId,
|
|
2032
|
-
messageId: activity.id ?? void 0,
|
|
2033
|
-
threadRootMessageId: conversationMessageId ?? activity.replyToId,
|
|
2034
|
-
teamAadGroupId: graphTeamAadGroupId,
|
|
2035
|
-
channelId: activity.channelData?.channel?.id
|
|
2036
|
-
});
|
|
2037
|
-
if (!messageUrl) log.debug?.("graph message url unavailable", {
|
|
2038
|
-
conversationType,
|
|
2039
|
-
hasChannelData: Boolean(activity.channelData),
|
|
2040
|
-
messageId: activity.id ?? void 0,
|
|
2041
|
-
replyToId: activity.replyToId ?? void 0
|
|
2042
|
-
});
|
|
2043
|
-
else {
|
|
2044
|
-
const graphMedia = await downloadMSTeamsGraphMedia({
|
|
2045
|
-
messageUrl,
|
|
2046
|
-
tokenProvider,
|
|
2047
|
-
maxBytes,
|
|
2048
|
-
allowHosts,
|
|
2049
|
-
authAllowHosts: params.authAllowHosts,
|
|
2050
|
-
preserveFilenames,
|
|
2051
|
-
deadline: params.deadline,
|
|
2052
|
-
logger: log
|
|
2053
|
-
});
|
|
2054
|
-
if (graphMedia.media.length > 0) mediaList = mergeMSTeamsMediaFacts(mediaList, graphMedia.media, { positionallyAligned: false });
|
|
2055
|
-
if (!hasMaterializedMedia(mediaList)) log.debug?.("graph media fetch empty", {
|
|
2056
|
-
messageUrl,
|
|
2057
|
-
hostedStatus: graphMedia.hostedStatus,
|
|
2058
|
-
attachmentStatus: graphMedia.attachmentStatus,
|
|
2059
|
-
hostedCount: graphMedia.hostedCount,
|
|
2060
|
-
attachmentCount: graphMedia.attachmentCount,
|
|
2061
|
-
tokenError: graphMedia.tokenError,
|
|
2062
|
-
attachmentIdCount: attachmentIds.length
|
|
2063
|
-
});
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2645
|
+
teamId,
|
|
2646
|
+
threadId
|
|
2647
|
+
})
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
//#endregion
|
|
2651
|
+
//#region extensions/msteams/src/team-identity.ts
|
|
2652
|
+
const teamGroupIdCache = /* @__PURE__ */ new Map();
|
|
2653
|
+
const TEAM_GROUP_ID_CACHE_MAX_ENTRIES = 500;
|
|
2654
|
+
function cacheTeamGroupId(conversationTeamId, groupId) {
|
|
2655
|
+
teamGroupIdCache.set(conversationTeamId, groupId);
|
|
2656
|
+
(0, openclaw_plugin_sdk_collection_runtime.pruneMapToMaxSize)(teamGroupIdCache, TEAM_GROUP_ID_CACHE_MAX_ENTRIES);
|
|
2657
|
+
}
|
|
2658
|
+
/** Resolve the Graph team GUID without ever treating a Bot Framework team ID as equivalent. */
|
|
2659
|
+
async function resolveTeamGroupId(params) {
|
|
2660
|
+
const activityGroupId = params.aadGroupId?.trim();
|
|
2661
|
+
if (activityGroupId) {
|
|
2662
|
+
cacheTeamGroupId(params.conversationTeamId, activityGroupId);
|
|
2663
|
+
return activityGroupId;
|
|
2066
2664
|
}
|
|
2067
|
-
const
|
|
2068
|
-
if (
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2665
|
+
const cached = teamGroupIdCache.get(params.conversationTeamId);
|
|
2666
|
+
if (cached) return cached;
|
|
2667
|
+
const getTeamDetails = params.getTeamDetails;
|
|
2668
|
+
if (!getTeamDetails) return;
|
|
2669
|
+
const groupId = (await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2670
|
+
deadline: params.deadline,
|
|
2671
|
+
label: "MS Teams team details",
|
|
2672
|
+
work: () => getTeamDetails(params.conversationTeamId)
|
|
2673
|
+
})).aadGroupId?.trim();
|
|
2674
|
+
if (!groupId) return;
|
|
2675
|
+
cacheTeamGroupId(params.conversationTeamId, groupId);
|
|
2676
|
+
return groupId;
|
|
2677
|
+
}
|
|
2678
|
+
//#endregion
|
|
2679
|
+
//#region extensions/msteams/src/thread-parent-context.ts
|
|
2680
|
+
const PARENT_CACHE_TTL_MS = 300 * 1e3;
|
|
2681
|
+
const PARENT_CACHE_MAX = 100;
|
|
2682
|
+
const parentCache = /* @__PURE__ */ new Map();
|
|
2683
|
+
const INJECTED_MAX = 200;
|
|
2684
|
+
const injectedParents = /* @__PURE__ */ new Map();
|
|
2685
|
+
function touchLru(map, key, value, max) {
|
|
2686
|
+
if (map.has(key)) map.delete(key);
|
|
2687
|
+
else if (map.size >= max) {
|
|
2688
|
+
const firstKey = map.keys().next().value;
|
|
2689
|
+
if (firstKey !== void 0) map.delete(firstKey);
|
|
2690
|
+
}
|
|
2691
|
+
map.set(key, value);
|
|
2692
|
+
}
|
|
2693
|
+
function buildParentCacheKey(groupId, channelId, parentId) {
|
|
2694
|
+
return `${groupId}\u0000${channelId}\u0000${parentId}`;
|
|
2695
|
+
}
|
|
2696
|
+
function resolveParentCacheExpiresAt(nowRaw) {
|
|
2697
|
+
const nowMs = (0, openclaw_plugin_sdk_number_runtime.asDateTimestampMs)(nowRaw);
|
|
2698
|
+
return nowMs === void 0 ? void 0 : (0, openclaw_plugin_sdk_number_runtime.resolveExpiresAtMsFromDurationMs)(PARENT_CACHE_TTL_MS, { nowMs });
|
|
2699
|
+
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Fetch a channel parent message with an LRU+TTL cache.
|
|
2702
|
+
*
|
|
2703
|
+
* Uses the injected `fetchParent` (defaults to `fetchChannelMessage`) so
|
|
2704
|
+
* tests can swap in a stub without mocking the Graph transport.
|
|
2705
|
+
*/
|
|
2706
|
+
async function fetchParentMessageCached(token, groupId, channelId, parentId, fetchParent = require_messenger.fetchChannelMessage) {
|
|
2707
|
+
const key = buildParentCacheKey(groupId, channelId, parentId);
|
|
2708
|
+
const now = (0, openclaw_plugin_sdk_number_runtime.asDateTimestampMs)(Date.now());
|
|
2709
|
+
const cached = parentCache.get(key);
|
|
2710
|
+
const cachedExpiresAt = cached ? (0, openclaw_plugin_sdk_number_runtime.asDateTimestampMs)(cached.expiresAt) : void 0;
|
|
2711
|
+
if (cached && now !== void 0 && cachedExpiresAt !== void 0 && cachedExpiresAt > now) {
|
|
2712
|
+
parentCache.delete(key);
|
|
2713
|
+
parentCache.set(key, cached);
|
|
2714
|
+
return cached.message;
|
|
2715
|
+
}
|
|
2716
|
+
if (cached) parentCache.delete(key);
|
|
2717
|
+
const message = await fetchParent(token, groupId, channelId, parentId);
|
|
2718
|
+
const expiresAt = resolveParentCacheExpiresAt(Date.now());
|
|
2719
|
+
if (expiresAt !== void 0) touchLru(parentCache, key, {
|
|
2720
|
+
message,
|
|
2721
|
+
expiresAt
|
|
2722
|
+
}, PARENT_CACHE_MAX);
|
|
2723
|
+
return message;
|
|
2724
|
+
}
|
|
2725
|
+
const PARENT_TEXT_MAX_CHARS = 400;
|
|
2726
|
+
/**
|
|
2727
|
+
* Extract a compact summary (sender + plain-text body) from a Graph parent
|
|
2728
|
+
* message. Returns undefined when the parent cannot be summarized (missing
|
|
2729
|
+
* or blank body).
|
|
2730
|
+
*/
|
|
2731
|
+
function summarizeParentMessage(message) {
|
|
2732
|
+
if (!message) return;
|
|
2733
|
+
const sender = message.from?.user?.displayName ?? message.from?.application?.displayName ?? "unknown";
|
|
2734
|
+
const contentType = message.body?.contentType ?? "text";
|
|
2735
|
+
const raw = message.body?.content ?? "";
|
|
2736
|
+
const text = contentType === "html" ? require_messenger.stripHtmlFromTeamsMessage(raw) : raw.replace(/\s+/g, " ").trim();
|
|
2737
|
+
if (!text) return;
|
|
2738
|
+
return {
|
|
2739
|
+
sender,
|
|
2740
|
+
text: text.length > PARENT_TEXT_MAX_CHARS ? `${(0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(text, PARENT_TEXT_MAX_CHARS - 1)}…` : text
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
/**
|
|
2744
|
+
* Build the single-line `Replying to @sender: body` system event text.
|
|
2745
|
+
* Callers should pass this text to `enqueueSystemEvent` together with a
|
|
2746
|
+
* stable contextKey derived from the parent id.
|
|
2747
|
+
*/
|
|
2748
|
+
function formatParentContextEvent(summary) {
|
|
2749
|
+
return `Replying to @${summary.sender}: ${summary.text}`;
|
|
2750
|
+
}
|
|
2751
|
+
/**
|
|
2752
|
+
* Decide whether a parent context event should be enqueued for the current
|
|
2753
|
+
* session. Returns `false` when we already injected the same parent for this
|
|
2754
|
+
* session recently (prevents re-prepending identical context on every reply
|
|
2755
|
+
* in the thread).
|
|
2756
|
+
*/
|
|
2757
|
+
function shouldInjectParentContext(sessionKey, parentId) {
|
|
2758
|
+
const key = sessionKey;
|
|
2759
|
+
return injectedParents.get(key) !== parentId;
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Record that `parentId` was just injected for `sessionKey` so subsequent
|
|
2763
|
+
* replies with the same parent can short-circuit via `shouldInjectParentContext`.
|
|
2764
|
+
*/
|
|
2765
|
+
function markParentContextInjected(sessionKey, parentId) {
|
|
2766
|
+
touchLru(injectedParents, sessionKey, parentId, INJECTED_MAX);
|
|
2767
|
+
}
|
|
2768
|
+
//#endregion
|
|
2769
|
+
//#region extensions/msteams/src/monitor-handler/thread-context.ts
|
|
2770
|
+
function prepareMSTeamsThreadRouting(params) {
|
|
2771
|
+
const route = require_runtime.getMSTeamsRuntime().channel.routing.resolveAgentRoute({
|
|
2772
|
+
cfg: params.cfg,
|
|
2773
|
+
channel: "msteams",
|
|
2774
|
+
teamId: params.teamId,
|
|
2775
|
+
peer: {
|
|
2776
|
+
kind: params.isDirectMessage ? "direct" : params.isChannel ? "channel" : "group",
|
|
2777
|
+
id: params.isDirectMessage ? params.senderId : params.conversationId
|
|
2778
|
+
}
|
|
2074
2779
|
});
|
|
2075
|
-
|
|
2780
|
+
route.sessionKey = require_channel.resolveMSTeamsRouteSessionKey({
|
|
2781
|
+
baseSessionKey: route.sessionKey,
|
|
2782
|
+
isChannel: params.isChannel,
|
|
2783
|
+
conversationMessageId: params.conversationMessageId,
|
|
2784
|
+
replyToId: params.context.activity.replyToId
|
|
2785
|
+
});
|
|
2786
|
+
const deadline = require_resolve_allowlist.createMSTeamsInboundDeadline();
|
|
2787
|
+
let teamAadGroupId = params.context.activity.channelData?.team?.aadGroupId?.trim() || void 0;
|
|
2788
|
+
const conversationTeamId = params.isChannel ? params.teamId : void 0;
|
|
2789
|
+
let teamGroupIdPromise;
|
|
2790
|
+
const resolveTeamAadGroupId = async () => {
|
|
2791
|
+
if (!conversationTeamId) return;
|
|
2792
|
+
teamGroupIdPromise ??= resolveTeamGroupId({
|
|
2793
|
+
conversationTeamId,
|
|
2794
|
+
aadGroupId: teamAadGroupId,
|
|
2795
|
+
getTeamDetails: params.context.getTeamDetails,
|
|
2796
|
+
deadline
|
|
2797
|
+
}).catch((err) => {
|
|
2798
|
+
params.log.debug?.("failed to resolve Teams AAD group ID", {
|
|
2799
|
+
teamId: conversationTeamId,
|
|
2800
|
+
error: require_setup_surface.formatUnknownError(err)
|
|
2801
|
+
});
|
|
2802
|
+
});
|
|
2803
|
+
teamAadGroupId = await teamGroupIdPromise;
|
|
2804
|
+
return teamAadGroupId;
|
|
2805
|
+
};
|
|
2806
|
+
return {
|
|
2807
|
+
route,
|
|
2808
|
+
deadline,
|
|
2809
|
+
resolveTeamAadGroupId,
|
|
2810
|
+
getTeamAadGroupId: () => teamAadGroupId
|
|
2811
|
+
};
|
|
2076
2812
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2813
|
+
async function resolveMSTeamsThreadContext(params) {
|
|
2814
|
+
const core = require_runtime.getMSTeamsRuntime();
|
|
2815
|
+
const activity = params.context.activity;
|
|
2816
|
+
const { route, deadline } = params.routing;
|
|
2817
|
+
const teamAadGroupId = await params.routing.resolveTeamAadGroupId();
|
|
2818
|
+
let quoteBodyFull;
|
|
2819
|
+
let quoteSenderId;
|
|
2820
|
+
let quoteSenderName;
|
|
2821
|
+
const quoteMessageId = params.quoteInfo?.id;
|
|
2822
|
+
if (quoteMessageId && params.isDirectMessage && params.conversationId.startsWith("19:")) try {
|
|
2823
|
+
const graphToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2824
|
+
deadline,
|
|
2825
|
+
label: "MS Teams quote token",
|
|
2826
|
+
work: () => params.tokenProvider.getAccessToken("https://graph.microsoft.com")
|
|
2827
|
+
});
|
|
2828
|
+
quoteBodyFull = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2829
|
+
deadline,
|
|
2830
|
+
label: "MS Teams quote lookup",
|
|
2831
|
+
work: () => require_messenger.fetchChatMessageText(graphToken, params.conversationId, quoteMessageId, deadline)
|
|
2832
|
+
});
|
|
2833
|
+
} catch (err) {
|
|
2834
|
+
params.log.debug?.("failed to fetch full quoted message text", { error: require_setup_surface.formatUnknownError(err) });
|
|
2085
2835
|
}
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2836
|
+
let threadContext;
|
|
2837
|
+
const threadParentId = activity.replyToId;
|
|
2838
|
+
if (threadParentId && params.isChannel && teamAadGroupId) try {
|
|
2839
|
+
const graphToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2840
|
+
deadline,
|
|
2841
|
+
label: "MS Teams thread token",
|
|
2842
|
+
work: () => params.tokenProvider.getAccessToken("https://graph.microsoft.com")
|
|
2843
|
+
});
|
|
2844
|
+
const [parentResult, repliesResult] = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2845
|
+
deadline,
|
|
2846
|
+
label: "MS Teams thread history",
|
|
2847
|
+
work: () => Promise.allSettled([fetchParentMessageCached(graphToken, teamAadGroupId, params.conversationId, threadParentId, (token, groupId, requestedChannelId, messageId) => require_messenger.fetchChannelMessage(token, groupId, requestedChannelId, messageId, deadline)), require_messenger.fetchThreadReplies(graphToken, teamAadGroupId, params.conversationId, threadParentId, 50, deadline)])
|
|
2848
|
+
});
|
|
2849
|
+
const parentMsg = parentResult.status === "fulfilled" ? parentResult.value : void 0;
|
|
2850
|
+
const replies = repliesResult.status === "fulfilled" ? repliesResult.value : [];
|
|
2851
|
+
if (parentResult.status === "rejected") params.log.debug?.("failed to fetch parent message", { error: require_setup_surface.formatUnknownError(parentResult.reason) });
|
|
2852
|
+
if (repliesResult.status === "rejected") params.log.debug?.("failed to fetch thread replies", { error: require_setup_surface.formatUnknownError(repliesResult.reason) });
|
|
2853
|
+
const isThreadSenderAllowed = (message) => (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
|
|
2854
|
+
isGroup: params.isChannel,
|
|
2855
|
+
groupPolicy: params.groupPolicy,
|
|
2856
|
+
allowFrom: params.effectiveGroupAllowFrom,
|
|
2857
|
+
isSenderAllowed: (allowFrom) => require_channel.resolveMSTeamsAllowlistMatch({
|
|
2858
|
+
allowFrom,
|
|
2859
|
+
senderId: message.from?.user?.id ?? "",
|
|
2860
|
+
senderName: message.from?.user?.displayName,
|
|
2861
|
+
allowNameMatching: params.allowNameMatching
|
|
2862
|
+
}).allowed
|
|
2863
|
+
});
|
|
2864
|
+
const parentSummary = summarizeParentMessage(parentMsg);
|
|
2865
|
+
const visibleParentMessages = parentMsg ? (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
|
|
2866
|
+
items: [parentMsg],
|
|
2867
|
+
mode: params.contextVisibilityMode,
|
|
2868
|
+
kind: "thread",
|
|
2869
|
+
isSenderAllowed: isThreadSenderAllowed
|
|
2870
|
+
}).items : [];
|
|
2871
|
+
if (parentSummary && visibleParentMessages.length > 0 && shouldInjectParentContext(route.sessionKey, threadParentId)) {
|
|
2872
|
+
core.system.enqueueSystemEvent(formatParentContextEvent(parentSummary), {
|
|
2873
|
+
sessionKey: route.sessionKey,
|
|
2874
|
+
contextKey: `msteams:thread-parent:${params.conversationId}:${threadParentId}`
|
|
2875
|
+
});
|
|
2876
|
+
markParentContextInjected(route.sessionKey, threadParentId);
|
|
2877
|
+
}
|
|
2878
|
+
const allMessages = parentMsg ? [parentMsg, ...replies] : replies;
|
|
2879
|
+
quoteSenderId = parentMsg?.from?.user?.id ?? parentMsg?.from?.application?.id ?? void 0;
|
|
2880
|
+
quoteSenderName = parentMsg?.from?.user?.displayName ?? parentMsg?.from?.application?.displayName ?? params.quoteInfo?.sender;
|
|
2881
|
+
const { items: threadMessages } = (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
|
|
2882
|
+
items: allMessages,
|
|
2883
|
+
mode: params.contextVisibilityMode,
|
|
2884
|
+
kind: "thread",
|
|
2885
|
+
isSenderAllowed: isThreadSenderAllowed
|
|
2886
|
+
});
|
|
2887
|
+
threadContext = require_messenger.formatThreadContext(threadMessages, activity.id) || void 0;
|
|
2888
|
+
} catch (err) {
|
|
2889
|
+
params.log.debug?.("failed to fetch thread history", { error: require_setup_surface.formatUnknownError(err) });
|
|
2102
2890
|
}
|
|
2103
|
-
|
|
2104
|
-
function buildStoredConversationReference(params) {
|
|
2105
|
-
const { activity, conversationId, conversationType, teamId, threadId } = params;
|
|
2106
|
-
const from = activity.from;
|
|
2107
|
-
const conversation = activity.conversation;
|
|
2108
|
-
const agent = activity.recipient;
|
|
2109
|
-
const clientInfo = activity.entities?.find((e) => e.type === "clientInfo");
|
|
2110
|
-
const tenantId = activity.channelData?.tenant?.id ?? conversation?.tenantId;
|
|
2111
|
-
const aadObjectId = from?.aadObjectId;
|
|
2112
|
-
const serviceUrl = require_resolve_allowlist.tryNormalizeBotFrameworkServiceUrl(activity.serviceUrl);
|
|
2891
|
+
quoteSenderName ??= params.quoteInfo?.sender;
|
|
2113
2892
|
return {
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
} : void 0,
|
|
2120
|
-
agent,
|
|
2121
|
-
conversation: {
|
|
2122
|
-
id: conversationId,
|
|
2123
|
-
conversationType,
|
|
2124
|
-
tenantId
|
|
2125
|
-
},
|
|
2126
|
-
...tenantId ? { tenantId } : {},
|
|
2127
|
-
...aadObjectId ? { aadObjectId } : {},
|
|
2128
|
-
teamId,
|
|
2129
|
-
channelId: activity.channelId,
|
|
2130
|
-
...serviceUrl ? { serviceUrl } : {},
|
|
2131
|
-
locale: activity.locale,
|
|
2132
|
-
...clientInfo?.timezone ? { timezone: clientInfo.timezone } : {},
|
|
2133
|
-
...threadId ? { threadId } : {}
|
|
2893
|
+
teamAadGroupId,
|
|
2894
|
+
quoteBodyFull,
|
|
2895
|
+
quoteSenderId,
|
|
2896
|
+
quoteSenderName,
|
|
2897
|
+
threadContext
|
|
2134
2898
|
};
|
|
2135
2899
|
}
|
|
2900
|
+
//#endregion
|
|
2901
|
+
//#region extensions/msteams/src/monitor-handler/message-handler.ts
|
|
2136
2902
|
function createMSTeamsMessageHandler(deps) {
|
|
2137
2903
|
const { cfg, runtime, appId, app, tokenProvider, textLimit, mediaMaxBytes, conversationStore, pollStore, log } = deps;
|
|
2138
2904
|
const core = require_runtime.getMSTeamsRuntime();
|
|
@@ -2151,24 +2917,12 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2151
2917
|
channel: "msteams"
|
|
2152
2918
|
});
|
|
2153
2919
|
const handleTeamsMessageNow = async (params) => {
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
const attachments =
|
|
2159
|
-
const advertisedMedia = resolveMSTeamsAdvertisedMedia(attachments, {
|
|
2160
|
-
maxInlineBytes: mediaMaxBytes,
|
|
2161
|
-
maxInlineTotalBytes: mediaMaxBytes
|
|
2162
|
-
});
|
|
2163
|
-
const rawBody = text;
|
|
2920
|
+
const facts = assembleMSTeamsInboundFacts({
|
|
2921
|
+
entry: params,
|
|
2922
|
+
mediaMaxBytes
|
|
2923
|
+
});
|
|
2924
|
+
const { context, activity, rawText, text, attachments, advertisedMedia, rawBody, quoteInfo, from, conversation, attachmentTypes, htmlSummary, conversationId, conversationMessageId, conversationType, isChannel, teamId, conversationRef } = facts;
|
|
2164
2925
|
const historyBody = [text, (0, openclaw_plugin_sdk_channel_inbound.formatMediaPlaceholderText)(advertisedMedia)].filter(Boolean).join("\n");
|
|
2165
|
-
const quoteInfo = require_channel.extractMSTeamsQuoteInfo(attachments);
|
|
2166
|
-
let quoteSenderId;
|
|
2167
|
-
let quoteSenderName;
|
|
2168
|
-
const from = activity.from;
|
|
2169
|
-
const conversation = activity.conversation;
|
|
2170
|
-
const attachmentTypes = attachments.map((att) => typeof att.contentType === "string" ? att.contentType : void 0).filter(Boolean).slice(0, 3);
|
|
2171
|
-
const htmlSummary = summarizeMSTeamsHtmlAttachments(attachments);
|
|
2172
2926
|
log.info("received message", {
|
|
2173
2927
|
rawText: (0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(rawText, 50),
|
|
2174
2928
|
text: (0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(text, 50),
|
|
@@ -2182,138 +2936,19 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2182
2936
|
log.debug?.("skipping message without from.id");
|
|
2183
2937
|
return;
|
|
2184
2938
|
}
|
|
2185
|
-
const
|
|
2186
|
-
const conversationId = require_channel.normalizeMSTeamsConversationId(rawConversationId);
|
|
2187
|
-
const conversationMessageId = require_channel.extractMSTeamsConversationMessageId(rawConversationId);
|
|
2188
|
-
const conversationType = conversation?.conversationType ?? "personal";
|
|
2189
|
-
const teamId = activity.channelData?.team?.id;
|
|
2190
|
-
const graphChannelId = activity.channelData?.channel?.id?.trim() || conversationId;
|
|
2191
|
-
const conversationRef = buildStoredConversationReference({
|
|
2192
|
-
activity,
|
|
2193
|
-
conversationId,
|
|
2194
|
-
conversationType,
|
|
2195
|
-
teamId,
|
|
2196
|
-
threadId: conversationType === "channel" ? conversationMessageId ?? activity.replyToId ?? void 0 : void 0
|
|
2197
|
-
});
|
|
2198
|
-
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
|
2199
|
-
cfg,
|
|
2200
|
-
surface: "msteams"
|
|
2201
|
-
});
|
|
2202
|
-
const isControlCommand = allowTextCommands && core.channel.commands.isControlCommandMessage(text, cfg);
|
|
2203
|
-
const { dmPolicy, senderId, senderName, pairing, isDirectMessage, channelGate, senderAccess, commandAccess, allowNameMatching, groupPolicy } = await resolveMSTeamsSenderAccess({
|
|
2939
|
+
const admission = await admitMSTeamsMessage({
|
|
2204
2940
|
cfg,
|
|
2205
2941
|
activity,
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
if (senderAccess.reasonCode === "dm_policy_disabled") {
|
|
2214
|
-
log.info("dropping dm (dms disabled)", {
|
|
2215
|
-
sender: senderId,
|
|
2216
|
-
label: senderName
|
|
2217
|
-
});
|
|
2218
|
-
log.debug?.("dropping dm (dms disabled)");
|
|
2219
|
-
return;
|
|
2220
|
-
}
|
|
2221
|
-
const allowMatch = require_channel.resolveMSTeamsAllowlistMatch({
|
|
2222
|
-
allowFrom: effectiveDmAllowFrom,
|
|
2223
|
-
senderId,
|
|
2224
|
-
senderName,
|
|
2225
|
-
allowNameMatching
|
|
2226
|
-
});
|
|
2227
|
-
if (senderAccess.decision === "pairing") {
|
|
2228
|
-
conversationStore.upsert(conversationId, conversationRef).catch((err) => {
|
|
2229
|
-
log.debug?.("failed to save conversation reference", { error: require_setup_surface.formatUnknownError(err) });
|
|
2230
|
-
});
|
|
2231
|
-
if (await pairing.upsertPairingRequest({
|
|
2232
|
-
id: senderId,
|
|
2233
|
-
meta: { name: senderName }
|
|
2234
|
-
})) log.info("msteams pairing request created", {
|
|
2235
|
-
sender: senderId,
|
|
2236
|
-
label: senderName
|
|
2237
|
-
});
|
|
2238
|
-
}
|
|
2239
|
-
log.debug?.("dropping dm (not allowlisted)", {
|
|
2240
|
-
sender: senderId,
|
|
2241
|
-
label: senderName,
|
|
2242
|
-
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
2243
|
-
});
|
|
2244
|
-
log.info("dropping dm (not allowlisted)", {
|
|
2245
|
-
sender: senderId,
|
|
2246
|
-
label: senderName,
|
|
2247
|
-
dmPolicy,
|
|
2248
|
-
reason: formatMSTeamsSenderReason({
|
|
2249
|
-
reasonCode: senderAccess.reasonCode,
|
|
2250
|
-
dmPolicy,
|
|
2251
|
-
groupPolicy
|
|
2252
|
-
}),
|
|
2253
|
-
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
2254
|
-
});
|
|
2255
|
-
return;
|
|
2256
|
-
}
|
|
2257
|
-
if (!isDirectMessage && msteamsCfg) {
|
|
2258
|
-
if (channelGate.allowlistConfigured && !channelGate.allowed) {
|
|
2259
|
-
log.info("dropping group message (not in team/channel allowlist)", {
|
|
2260
|
-
conversationId,
|
|
2261
|
-
teamKey: channelGate.teamKey ?? "none",
|
|
2262
|
-
channelKey: channelGate.channelKey ?? "none",
|
|
2263
|
-
channelMatchKey: channelGate.channelMatchKey ?? "none",
|
|
2264
|
-
channelMatchSource: channelGate.channelMatchSource ?? "none"
|
|
2265
|
-
});
|
|
2266
|
-
log.debug?.("dropping group message (not in team/channel allowlist)", {
|
|
2267
|
-
conversationId,
|
|
2268
|
-
teamKey: channelGate.teamKey ?? "none",
|
|
2269
|
-
channelKey: channelGate.channelKey ?? "none",
|
|
2270
|
-
channelMatchKey: channelGate.channelMatchKey ?? "none",
|
|
2271
|
-
channelMatchSource: channelGate.channelMatchSource ?? "none"
|
|
2272
|
-
});
|
|
2273
|
-
return;
|
|
2274
|
-
}
|
|
2275
|
-
if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_disabled") {
|
|
2276
|
-
log.info("dropping group message (groupPolicy: disabled)", { conversationId });
|
|
2277
|
-
log.debug?.("dropping group message (groupPolicy: disabled)", { conversationId });
|
|
2278
|
-
return;
|
|
2279
|
-
}
|
|
2280
|
-
if (!senderAccess.allowed && (senderAccess.reasonCode === "group_policy_empty_allowlist" || senderAccess.reasonCode === "route_sender_empty")) {
|
|
2281
|
-
log.info("dropping group message (groupPolicy: allowlist, no allowlist)", { conversationId });
|
|
2282
|
-
log.debug?.("dropping group message (groupPolicy: allowlist, no allowlist)", { conversationId });
|
|
2283
|
-
return;
|
|
2284
|
-
}
|
|
2285
|
-
if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_not_allowlisted") {
|
|
2286
|
-
const allowMatch = require_channel.resolveMSTeamsAllowlistMatch({
|
|
2287
|
-
allowFrom: effectiveGroupAllowFrom,
|
|
2288
|
-
senderId,
|
|
2289
|
-
senderName,
|
|
2290
|
-
allowNameMatching
|
|
2291
|
-
});
|
|
2292
|
-
log.debug?.("dropping group message (not in groupAllowFrom)", {
|
|
2293
|
-
sender: senderId,
|
|
2294
|
-
label: senderName,
|
|
2295
|
-
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
2296
|
-
});
|
|
2297
|
-
log.info("dropping group message (not in groupAllowFrom)", {
|
|
2298
|
-
sender: senderId,
|
|
2299
|
-
label: senderName,
|
|
2300
|
-
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
2301
|
-
});
|
|
2302
|
-
return;
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
if (commandAccess.shouldBlockControlCommand) {
|
|
2306
|
-
(0, openclaw_plugin_sdk_channel_inbound.logInboundDrop)({
|
|
2307
|
-
log: logVerboseMessage,
|
|
2308
|
-
channel: "msteams",
|
|
2309
|
-
reason: "control command (unauthorized)",
|
|
2310
|
-
target: senderId
|
|
2311
|
-
});
|
|
2312
|
-
return;
|
|
2313
|
-
}
|
|
2314
|
-
conversationStore.upsert(conversationId, conversationRef).catch((err) => {
|
|
2315
|
-
log.debug?.("failed to save conversation reference", { error: require_setup_surface.formatUnknownError(err) });
|
|
2942
|
+
text,
|
|
2943
|
+
conversationId,
|
|
2944
|
+
conversationRef,
|
|
2945
|
+
isChannel,
|
|
2946
|
+
conversationStore,
|
|
2947
|
+
log,
|
|
2948
|
+
logVerboseMessage
|
|
2316
2949
|
});
|
|
2950
|
+
if (!admission) return;
|
|
2951
|
+
const { senderId, senderName, isDirectMessage, channelGate, allowNameMatching, groupPolicy, commandAuthorized, effectiveGroupAllowFrom, allowTextCommands, isControlCommand } = admission;
|
|
2317
2952
|
const pollVote = require_polls.extractMSTeamsPollVote(activity);
|
|
2318
2953
|
if (pollVote) {
|
|
2319
2954
|
try {
|
|
@@ -2335,33 +2970,18 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2335
2970
|
}
|
|
2336
2971
|
return;
|
|
2337
2972
|
}
|
|
2338
|
-
const
|
|
2339
|
-
conversationType,
|
|
2340
|
-
htmlSummary: htmlSummary ?? void 0,
|
|
2341
|
-
graphMediaFallback: msteamsCfg?.graphMediaFallback
|
|
2342
|
-
});
|
|
2343
|
-
if (!rawBody && advertisedMedia.length === 0 && !mayRecoverGraphMedia) {
|
|
2344
|
-
log.debug?.("skipping empty message after stripping mentions");
|
|
2345
|
-
return;
|
|
2346
|
-
}
|
|
2347
|
-
const teamsFrom = isDirectMessage ? `msteams:${senderId}` : isChannel ? `msteams:channel:${conversationId}` : `msteams:group:${conversationId}`;
|
|
2348
|
-
const teamsTo = isDirectMessage ? `user:${senderId}` : `conversation:${conversationId}`;
|
|
2349
|
-
const route = core.channel.routing.resolveAgentRoute({
|
|
2973
|
+
const threadRouting = prepareMSTeamsThreadRouting({
|
|
2350
2974
|
cfg,
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
peer: {
|
|
2354
|
-
kind: isDirectMessage ? "direct" : isChannel ? "channel" : "group",
|
|
2355
|
-
id: isDirectMessage ? senderId : conversationId
|
|
2356
|
-
}
|
|
2357
|
-
});
|
|
2358
|
-
route.sessionKey = require_channel.resolveMSTeamsRouteSessionKey({
|
|
2359
|
-
baseSessionKey: route.sessionKey,
|
|
2975
|
+
context,
|
|
2976
|
+
isDirectMessage,
|
|
2360
2977
|
isChannel,
|
|
2361
|
-
|
|
2362
|
-
|
|
2978
|
+
senderId,
|
|
2979
|
+
conversationId,
|
|
2980
|
+
conversationMessageId: conversationMessageId ?? void 0,
|
|
2981
|
+
teamId,
|
|
2982
|
+
log
|
|
2363
2983
|
});
|
|
2364
|
-
const
|
|
2984
|
+
const { route, deadline: preprocessingDeadline } = threadRouting;
|
|
2365
2985
|
const inboundLabel = isDirectMessage ? `Teams DM from ${senderName}` : `Teams message in ${conversationType} from ${senderName}`;
|
|
2366
2986
|
const enqueuePrimaryMessageSystemEvent = () => core.system.enqueueSystemEvent(inboundLabel, {
|
|
2367
2987
|
sessionKey: route.sessionKey,
|
|
@@ -2375,7 +2995,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2375
2995
|
teamConfig,
|
|
2376
2996
|
channelConfig
|
|
2377
2997
|
});
|
|
2378
|
-
const timestamp =
|
|
2998
|
+
const timestamp = require_resolve_allowlist.parseMSTeamsActivityTimestamp(activity.timestamp);
|
|
2379
2999
|
const mentionDecision = (0, openclaw_plugin_sdk_channel_inbound.resolveInboundMentionDecision)({
|
|
2380
3000
|
facts: {
|
|
2381
3001
|
canDetectMention: true,
|
|
@@ -2415,344 +3035,66 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2415
3035
|
return;
|
|
2416
3036
|
}
|
|
2417
3037
|
}
|
|
2418
|
-
const
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
teamAadGroupId = await teamGroupIdPromise;
|
|
2436
|
-
return teamAadGroupId;
|
|
2437
|
-
};
|
|
2438
|
-
let mediaList = [];
|
|
2439
|
-
try {
|
|
2440
|
-
mediaList = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2441
|
-
deadline: preprocessingDeadline,
|
|
2442
|
-
label: "MS Teams inbound media",
|
|
2443
|
-
work: () => resolveMSTeamsInboundMedia({
|
|
2444
|
-
attachments,
|
|
2445
|
-
htmlSummary: htmlSummary ?? void 0,
|
|
2446
|
-
maxBytes: mediaMaxBytes,
|
|
2447
|
-
tokenProvider,
|
|
2448
|
-
allowHosts: msteamsCfg?.mediaAllowHosts,
|
|
2449
|
-
authAllowHosts: msteamsCfg?.mediaAuthAllowHosts,
|
|
2450
|
-
graphMediaFallback: msteamsCfg?.graphMediaFallback,
|
|
2451
|
-
conversationType,
|
|
2452
|
-
conversationId,
|
|
2453
|
-
conversationMessageId: conversationMessageId ?? void 0,
|
|
2454
|
-
teamAadGroupId,
|
|
2455
|
-
resolveTeamAadGroupId: resolveChannelTeamGroupId,
|
|
2456
|
-
serviceUrl: activity.serviceUrl,
|
|
2457
|
-
activity: {
|
|
2458
|
-
id: activity.id,
|
|
2459
|
-
replyToId: activity.replyToId,
|
|
2460
|
-
channelData: activity.channelData
|
|
2461
|
-
},
|
|
2462
|
-
log,
|
|
2463
|
-
deadline: preprocessingDeadline,
|
|
2464
|
-
preserveFilenames: false
|
|
2465
|
-
})
|
|
2466
|
-
});
|
|
2467
|
-
} catch (err) {
|
|
2468
|
-
log.debug?.("failed to resolve inbound Teams media", { error: require_setup_surface.formatUnknownError(err) });
|
|
2469
|
-
}
|
|
2470
|
-
const inboundMedia = mergeMSTeamsMediaFacts(advertisedMedia, mediaList);
|
|
2471
|
-
const agentBody = resolveMSTeamsInboundMediaBody({
|
|
2472
|
-
body: rawBody,
|
|
2473
|
-
nativeMedia: [...advertisedMedia, ...mediaList.slice(advertisedMedia.length).map((media) => ({
|
|
2474
|
-
contentType: media.contentType,
|
|
2475
|
-
kind: media.kind
|
|
2476
|
-
}))],
|
|
2477
|
-
materializedMedia: inboundMedia
|
|
2478
|
-
});
|
|
2479
|
-
if (!agentBody && inboundMedia.length === 0) {
|
|
2480
|
-
log.debug?.("skipping empty message after Graph media recovery");
|
|
2481
|
-
return;
|
|
2482
|
-
}
|
|
2483
|
-
enqueuePrimaryMessageSystemEvent();
|
|
2484
|
-
teamAadGroupId = await resolveChannelTeamGroupId();
|
|
2485
|
-
let quoteBodyFull;
|
|
2486
|
-
const quoteMessageId = quoteInfo?.id;
|
|
2487
|
-
if (quoteMessageId && isDirectMessage && conversationId.startsWith("19:")) try {
|
|
2488
|
-
const graphToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2489
|
-
deadline: preprocessingDeadline,
|
|
2490
|
-
label: "MS Teams quote token",
|
|
2491
|
-
work: () => tokenProvider.getAccessToken("https://graph.microsoft.com")
|
|
2492
|
-
});
|
|
2493
|
-
quoteBodyFull = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2494
|
-
deadline: preprocessingDeadline,
|
|
2495
|
-
label: "MS Teams quote lookup",
|
|
2496
|
-
work: () => require_messenger.fetchChatMessageText(graphToken, conversationId, quoteMessageId, preprocessingDeadline)
|
|
2497
|
-
});
|
|
2498
|
-
} catch (err) {
|
|
2499
|
-
log.debug?.("failed to fetch full quoted message text", { error: require_setup_surface.formatUnknownError(err) });
|
|
2500
|
-
}
|
|
2501
|
-
let threadContext;
|
|
2502
|
-
const threadParentId = activity.replyToId;
|
|
2503
|
-
const channelGroupId = teamAadGroupId;
|
|
2504
|
-
if (threadParentId && isChannel && channelGroupId) try {
|
|
2505
|
-
const graphToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2506
|
-
deadline: preprocessingDeadline,
|
|
2507
|
-
label: "MS Teams thread token",
|
|
2508
|
-
work: () => tokenProvider.getAccessToken("https://graph.microsoft.com")
|
|
2509
|
-
});
|
|
2510
|
-
const [parentResult, repliesResult] = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2511
|
-
deadline: preprocessingDeadline,
|
|
2512
|
-
label: "MS Teams thread history",
|
|
2513
|
-
work: () => Promise.allSettled([fetchParentMessageCached(graphToken, channelGroupId, conversationId, threadParentId, (token, groupId, requestedChannelId, messageId) => require_messenger.fetchChannelMessage(token, groupId, requestedChannelId, messageId, preprocessingDeadline)), require_messenger.fetchThreadReplies(graphToken, channelGroupId, conversationId, threadParentId, 50, preprocessingDeadline)])
|
|
2514
|
-
});
|
|
2515
|
-
const parentMsg = parentResult.status === "fulfilled" ? parentResult.value : void 0;
|
|
2516
|
-
const replies = repliesResult.status === "fulfilled" ? repliesResult.value : [];
|
|
2517
|
-
if (parentResult.status === "rejected") log.debug?.("failed to fetch parent message", { error: require_setup_surface.formatUnknownError(parentResult.reason) });
|
|
2518
|
-
if (repliesResult.status === "rejected") log.debug?.("failed to fetch thread replies", { error: require_setup_surface.formatUnknownError(repliesResult.reason) });
|
|
2519
|
-
const isThreadSenderAllowed = (msg) => (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
|
|
2520
|
-
isGroup: isChannel,
|
|
2521
|
-
groupPolicy,
|
|
2522
|
-
allowFrom: effectiveGroupAllowFrom,
|
|
2523
|
-
isSenderAllowed: (allowFrom) => require_channel.resolveMSTeamsAllowlistMatch({
|
|
2524
|
-
allowFrom,
|
|
2525
|
-
senderId: msg.from?.user?.id ?? "",
|
|
2526
|
-
senderName: msg.from?.user?.displayName,
|
|
2527
|
-
allowNameMatching
|
|
2528
|
-
}).allowed
|
|
2529
|
-
});
|
|
2530
|
-
const parentSummary = summarizeParentMessage(parentMsg);
|
|
2531
|
-
const visibleParentMessages = parentMsg ? (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
|
|
2532
|
-
items: [parentMsg],
|
|
2533
|
-
mode: contextVisibilityMode,
|
|
2534
|
-
kind: "thread",
|
|
2535
|
-
isSenderAllowed: isThreadSenderAllowed
|
|
2536
|
-
}).items : [];
|
|
2537
|
-
if (parentSummary && visibleParentMessages.length > 0 && shouldInjectParentContext(route.sessionKey, threadParentId)) {
|
|
2538
|
-
core.system.enqueueSystemEvent(formatParentContextEvent(parentSummary), {
|
|
2539
|
-
sessionKey: route.sessionKey,
|
|
2540
|
-
contextKey: `msteams:thread-parent:${conversationId}:${threadParentId}`
|
|
2541
|
-
});
|
|
2542
|
-
markParentContextInjected(route.sessionKey, threadParentId);
|
|
2543
|
-
}
|
|
2544
|
-
const allMessages = parentMsg ? [parentMsg, ...replies] : replies;
|
|
2545
|
-
quoteSenderId = parentMsg?.from?.user?.id ?? parentMsg?.from?.application?.id ?? void 0;
|
|
2546
|
-
quoteSenderName = parentMsg?.from?.user?.displayName ?? parentMsg?.from?.application?.displayName ?? quoteInfo?.sender;
|
|
2547
|
-
const { items: threadMessages } = (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
|
|
2548
|
-
items: allMessages,
|
|
2549
|
-
mode: contextVisibilityMode,
|
|
2550
|
-
kind: "thread",
|
|
2551
|
-
isSenderAllowed: isThreadSenderAllowed
|
|
2552
|
-
});
|
|
2553
|
-
const formatted = require_messenger.formatThreadContext(threadMessages, activity.id);
|
|
2554
|
-
if (formatted) threadContext = formatted;
|
|
2555
|
-
} catch (err) {
|
|
2556
|
-
log.debug?.("failed to fetch thread history", { error: require_setup_surface.formatUnknownError(err) });
|
|
2557
|
-
}
|
|
2558
|
-
quoteSenderName ??= quoteInfo?.sender;
|
|
2559
|
-
const envelopeFrom = isDirectMessage ? senderName : conversationType;
|
|
2560
|
-
const buildEnvelope = (0, openclaw_plugin_sdk_channel_inbound.createChannelInboundEnvelopeBuilder)({
|
|
2561
|
-
cfg,
|
|
2562
|
-
route
|
|
2563
|
-
});
|
|
2564
|
-
let combinedBody = buildEnvelope({
|
|
2565
|
-
channel: "Teams",
|
|
2566
|
-
from: envelopeFrom,
|
|
2567
|
-
timestamp,
|
|
2568
|
-
body: agentBody
|
|
2569
|
-
});
|
|
2570
|
-
const isRoomish = !isDirectMessage;
|
|
2571
|
-
const historyKey = isRoomish ? conversationId : void 0;
|
|
2572
|
-
if (isRoomish && historyKey) combinedBody = (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildPendingContext({
|
|
2573
|
-
historyKey,
|
|
2574
|
-
limit: historyLimit,
|
|
2575
|
-
currentMessage: combinedBody,
|
|
2576
|
-
formatEntry: (entry) => buildEnvelope({
|
|
2577
|
-
channel: "Teams",
|
|
2578
|
-
from: conversationType,
|
|
2579
|
-
timestamp: entry.timestamp,
|
|
2580
|
-
previousTimestamp: null,
|
|
2581
|
-
body: `${entry.sender}: ${entry.body}${entry.messageId ? ` [id:${entry.messageId}]` : ""}`
|
|
2582
|
-
})
|
|
3038
|
+
const content = await prepareMSTeamsInboundContent({
|
|
3039
|
+
entry: params,
|
|
3040
|
+
rawBody,
|
|
3041
|
+
advertisedMedia,
|
|
3042
|
+
htmlSummary: htmlSummary ?? void 0,
|
|
3043
|
+
conversationType,
|
|
3044
|
+
conversationId,
|
|
3045
|
+
conversationMessageId: conversationMessageId ?? void 0,
|
|
3046
|
+
teamAadGroupId: threadRouting.getTeamAadGroupId(),
|
|
3047
|
+
resolveTeamAadGroupId: threadRouting.resolveTeamAadGroupId,
|
|
3048
|
+
mediaMaxBytes,
|
|
3049
|
+
tokenProvider,
|
|
3050
|
+
mediaAllowHosts: msteamsCfg?.mediaAllowHosts,
|
|
3051
|
+
mediaAuthAllowHosts: msteamsCfg?.mediaAuthAllowHosts,
|
|
3052
|
+
graphMediaFallback: msteamsCfg?.graphMediaFallback,
|
|
3053
|
+
deadline: preprocessingDeadline,
|
|
3054
|
+
log
|
|
2583
3055
|
});
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
3056
|
+
if (!content) return;
|
|
3057
|
+
enqueuePrimaryMessageSystemEvent();
|
|
3058
|
+
const thread = await resolveMSTeamsThreadContext({
|
|
3059
|
+
routing: threadRouting,
|
|
3060
|
+
context,
|
|
3061
|
+
tokenProvider,
|
|
3062
|
+
quoteInfo,
|
|
3063
|
+
isDirectMessage,
|
|
3064
|
+
isChannel,
|
|
3065
|
+
conversationId,
|
|
3066
|
+
contextVisibilityMode,
|
|
2591
3067
|
groupPolicy,
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
senderId: quoteSenderId ?? "",
|
|
2596
|
-
senderName: quoteSenderName,
|
|
2597
|
-
allowNameMatching
|
|
2598
|
-
}).allowed
|
|
2599
|
-
}) : true;
|
|
2600
|
-
const bodyForAgent = threadContext ? `[Thread history]\n${threadContext}\n[/Thread history]\n\n${agentBody}` : agentBody;
|
|
2601
|
-
const nativeChannelId = isChannel && teamAadGroupId ? `${teamAadGroupId}/${graphChannelId}` : void 0;
|
|
2602
|
-
const ctxPayload = (0, openclaw_plugin_sdk_channel_inbound.buildChannelInboundEventContext)({
|
|
2603
|
-
channel: "msteams",
|
|
2604
|
-
contextVisibility: contextVisibilityMode,
|
|
2605
|
-
supplemental: { quote: quoteInfo ? {
|
|
2606
|
-
id: quoteInfo.id ?? activity.replyToId ?? void 0,
|
|
2607
|
-
body: quoteBodyFull ?? quoteInfo.body,
|
|
2608
|
-
sender: quoteInfo.sender,
|
|
2609
|
-
senderAllowed: quoteSenderAllowed,
|
|
2610
|
-
isQuote: true
|
|
2611
|
-
} : void 0 },
|
|
2612
|
-
media: (0, openclaw_plugin_sdk_channel_inbound.toInboundMediaFacts)(inboundMedia),
|
|
2613
|
-
messageId: activity.id,
|
|
2614
|
-
timestamp: timestamp?.getTime() ?? Date.now(),
|
|
2615
|
-
from: teamsFrom,
|
|
2616
|
-
sender: {
|
|
2617
|
-
id: senderId,
|
|
2618
|
-
name: senderName
|
|
2619
|
-
},
|
|
2620
|
-
conversation: {
|
|
2621
|
-
kind: isDirectMessage ? "direct" : isChannel ? "channel" : "group",
|
|
2622
|
-
id: conversationId,
|
|
2623
|
-
label: envelopeFrom,
|
|
2624
|
-
spaceId: teamId,
|
|
2625
|
-
nativeChannelId
|
|
2626
|
-
},
|
|
2627
|
-
route: {
|
|
2628
|
-
agentId: route.agentId,
|
|
2629
|
-
dmScope: route.dmScope,
|
|
2630
|
-
accountId: route.accountId,
|
|
2631
|
-
routeSessionKey: route.sessionKey
|
|
2632
|
-
},
|
|
2633
|
-
reply: {
|
|
2634
|
-
to: teamsTo,
|
|
2635
|
-
replyToId: activity.replyToId ?? void 0,
|
|
2636
|
-
nativeChannelId
|
|
2637
|
-
},
|
|
2638
|
-
message: {
|
|
2639
|
-
body: combinedBody,
|
|
2640
|
-
bodyForAgent,
|
|
2641
|
-
inboundHistory,
|
|
2642
|
-
rawBody,
|
|
2643
|
-
commandBody
|
|
2644
|
-
},
|
|
2645
|
-
sessionTranscript: { historyLimit: isRoomish ? historyLimit : 0 },
|
|
2646
|
-
access: {
|
|
2647
|
-
mentions: {
|
|
2648
|
-
canDetectMention: !isDirectMessage,
|
|
2649
|
-
wasMentioned: isDirectMessage || mentionDecision.effectiveWasMentioned
|
|
2650
|
-
},
|
|
2651
|
-
commands: { authorized: commandAuthorized === true }
|
|
2652
|
-
},
|
|
2653
|
-
extra: {
|
|
2654
|
-
GroupSubject: !isDirectMessage ? conversationType : void 0,
|
|
2655
|
-
ReplyToIsQuote: quoteInfo ? true : void 0
|
|
2656
|
-
}
|
|
3068
|
+
effectiveGroupAllowFrom,
|
|
3069
|
+
allowNameMatching,
|
|
3070
|
+
log
|
|
2657
3071
|
});
|
|
2658
|
-
|
|
2659
|
-
const sharePointSiteId = msteamsCfg?.sharePointSiteId;
|
|
2660
|
-
const { dispatcherOptions, delivery, replyOptions } = createMSTeamsReplyDispatcher({
|
|
3072
|
+
await dispatchMSTeamsInboundTurn({
|
|
2661
3073
|
cfg,
|
|
2662
|
-
agentId: route.agentId,
|
|
2663
|
-
sessionKey: route.sessionKey,
|
|
2664
|
-
accountId: route.accountId,
|
|
2665
3074
|
runtime,
|
|
2666
|
-
log,
|
|
2667
|
-
app,
|
|
2668
3075
|
appId,
|
|
2669
|
-
|
|
2670
|
-
context,
|
|
2671
|
-
replyStyle,
|
|
2672
|
-
textLimit,
|
|
2673
|
-
onSentMessageIds: (ids) => {
|
|
2674
|
-
for (const id of ids) recordMSTeamsSentMessage(conversationId, id);
|
|
2675
|
-
},
|
|
3076
|
+
app,
|
|
2676
3077
|
tokenProvider,
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
const turnResult = await core.channel.inbound.run({
|
|
2693
|
-
channel: "msteams",
|
|
2694
|
-
accountId: route.accountId,
|
|
2695
|
-
raw: context,
|
|
2696
|
-
adapter: {
|
|
2697
|
-
ingest: () => ({
|
|
2698
|
-
id: activity.id ?? `${teamsFrom}:${Date.now()}`,
|
|
2699
|
-
timestamp: timestamp?.getTime(),
|
|
2700
|
-
rawText: rawBody,
|
|
2701
|
-
textForAgent: bodyForAgent,
|
|
2702
|
-
textForCommands: commandBody,
|
|
2703
|
-
raw: activity
|
|
2704
|
-
}),
|
|
2705
|
-
resolveTurn: () => ({
|
|
2706
|
-
cfg: turnConfig,
|
|
2707
|
-
channel: "msteams",
|
|
2708
|
-
accountId: route.accountId,
|
|
2709
|
-
route: {
|
|
2710
|
-
agentId: route.agentId,
|
|
2711
|
-
sessionKey: route.sessionKey
|
|
2712
|
-
},
|
|
2713
|
-
ctxPayload,
|
|
2714
|
-
record: { onRecordError: (err) => {
|
|
2715
|
-
logVerboseMessage(`msteams: failed updating session meta: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2716
|
-
} },
|
|
2717
|
-
history: {
|
|
2718
|
-
isGroup: isRoomish,
|
|
2719
|
-
historyKey,
|
|
2720
|
-
historyMap: conversationHistories,
|
|
2721
|
-
limit: historyLimit
|
|
2722
|
-
},
|
|
2723
|
-
dispatcherOptions,
|
|
2724
|
-
delivery,
|
|
2725
|
-
replyOptions: {
|
|
2726
|
-
...replyOptions,
|
|
2727
|
-
...params.turnAdoptionLifecycle ? (0, openclaw_plugin_sdk_channel_outbound.bindIngressLifecycleToReplyOptions)(params.turnAdoptionLifecycle) : {}
|
|
2728
|
-
}
|
|
2729
|
-
})
|
|
2730
|
-
}
|
|
2731
|
-
});
|
|
2732
|
-
const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
|
|
2733
|
-
const queuedFinal = dispatchResult?.queuedFinal ?? false;
|
|
2734
|
-
const counts = (0, openclaw_plugin_sdk_channel_inbound.resolveInboundReplyDispatchCounts)(dispatchResult);
|
|
2735
|
-
const hasFinalResponse = (0, openclaw_plugin_sdk_channel_inbound.hasFinalInboundReplyDispatch)(dispatchResult);
|
|
2736
|
-
log.info("dispatch complete", {
|
|
2737
|
-
queuedFinal,
|
|
2738
|
-
counts
|
|
2739
|
-
});
|
|
2740
|
-
if (!hasFinalResponse) return;
|
|
2741
|
-
const finalCount = counts.final;
|
|
2742
|
-
logVerboseMessage(`msteams: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${teamsTo}`);
|
|
2743
|
-
} catch (err) {
|
|
2744
|
-
log.error("dispatch failed", { error: require_setup_surface.formatUnknownError(err) });
|
|
2745
|
-
runtime.error(`msteams dispatch failed: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2746
|
-
if (params.turnAdoptionLifecycle) throw err;
|
|
2747
|
-
try {
|
|
2748
|
-
await context.sendActivity("⚠️ Something went wrong. Please try again.");
|
|
2749
|
-
} catch {}
|
|
2750
|
-
}
|
|
3078
|
+
textLimit,
|
|
3079
|
+
log,
|
|
3080
|
+
logVerboseMessage,
|
|
3081
|
+
facts,
|
|
3082
|
+
admission,
|
|
3083
|
+
content,
|
|
3084
|
+
routing: threadRouting,
|
|
3085
|
+
thread,
|
|
3086
|
+
replyStyle,
|
|
3087
|
+
timestamp,
|
|
3088
|
+
contextVisibilityMode,
|
|
3089
|
+
mentionWasEffective: mentionDecision.effectiveWasMentioned,
|
|
3090
|
+
conversationHistories,
|
|
3091
|
+
historyLimit
|
|
3092
|
+
});
|
|
2751
3093
|
};
|
|
2752
3094
|
const inboundDebouncer = core.channel.debounce.createInboundDebouncer({
|
|
2753
3095
|
debounceMs: inboundDebounceMs,
|
|
2754
3096
|
buildKey: (entry) => {
|
|
2755
|
-
const conversationId =
|
|
3097
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(entry.context.activity.conversation?.id ?? "");
|
|
2756
3098
|
const senderId = entry.context.activity.from?.aadObjectId ?? entry.context.activity.from?.id ?? "";
|
|
2757
3099
|
if (!senderId || !conversationId) return null;
|
|
2758
3100
|
return `msteams:${appId}:${conversationId}:${senderId}`;
|
|
@@ -2762,65 +3104,53 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2762
3104
|
if (entry.attachments.length > 0) return false;
|
|
2763
3105
|
return !core.channel.commands.isControlCommandMessage(entry.text, cfg);
|
|
2764
3106
|
},
|
|
2765
|
-
onFlush:
|
|
3107
|
+
onFlush: (entries, createFlush) => {
|
|
2766
3108
|
const last = entries.at(-1);
|
|
2767
|
-
if (!last) return;
|
|
2768
3109
|
const { lifecycle, settle } = (0, openclaw_plugin_sdk_channel_ingress_runtime.fanInChannelIngressLifecycles)(entries.map((entry) => entry.turnAdoptionLifecycle));
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
const combinedRawText = entries.map((entry) => entry.rawText).filter(Boolean).join("\n");
|
|
2778
|
-
const wasMentioned = entries.some((entry) => entry.wasMentioned);
|
|
2779
|
-
const implicitMentionKinds = entries.flatMap((entry) => entry.implicitMentionKinds);
|
|
2780
|
-
await handleTeamsMessageNow({
|
|
2781
|
-
context: last.context,
|
|
2782
|
-
rawText: combinedRawText,
|
|
2783
|
-
text: combinedText,
|
|
2784
|
-
attachments: [],
|
|
2785
|
-
wasMentioned,
|
|
2786
|
-
implicitMentionKinds,
|
|
2787
|
-
...lifecycle ? { turnAdoptionLifecycle: lifecycle } : {}
|
|
3110
|
+
return createFlush({
|
|
3111
|
+
lifecycle,
|
|
3112
|
+
dispatch: async (admissionLifecycle) => {
|
|
3113
|
+
if (!last) return;
|
|
3114
|
+
try {
|
|
3115
|
+
if (entries.length === 1) await handleTeamsMessageNow({
|
|
3116
|
+
...last,
|
|
3117
|
+
turnAdoptionLifecycle: admissionLifecycle
|
|
2788
3118
|
});
|
|
3119
|
+
else {
|
|
3120
|
+
const combinedText = entries.map((entry) => entry.text).filter(Boolean).join("\n");
|
|
3121
|
+
if (combinedText.trim()) {
|
|
3122
|
+
const combinedRawText = entries.map((entry) => entry.rawText).filter(Boolean).join("\n");
|
|
3123
|
+
const wasMentioned = entries.some((entry) => entry.wasMentioned);
|
|
3124
|
+
const implicitMentionKinds = entries.flatMap((entry) => entry.implicitMentionKinds);
|
|
3125
|
+
await handleTeamsMessageNow({
|
|
3126
|
+
context: last.context,
|
|
3127
|
+
rawText: combinedRawText,
|
|
3128
|
+
text: combinedText,
|
|
3129
|
+
attachments: [],
|
|
3130
|
+
wasMentioned,
|
|
3131
|
+
implicitMentionKinds,
|
|
3132
|
+
turnAdoptionLifecycle: admissionLifecycle
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
await settle();
|
|
3137
|
+
} catch (err) {
|
|
3138
|
+
await admissionLifecycle.onAbandoned();
|
|
3139
|
+
throw err;
|
|
2789
3140
|
}
|
|
2790
3141
|
}
|
|
2791
|
-
|
|
2792
|
-
} catch (err) {
|
|
2793
|
-
await lifecycle?.onAbandoned();
|
|
2794
|
-
throw err;
|
|
2795
|
-
}
|
|
3142
|
+
});
|
|
2796
3143
|
},
|
|
2797
3144
|
onError: (err) => {
|
|
2798
3145
|
runtime.error(`msteams debounce flush failed: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2799
3146
|
}
|
|
2800
3147
|
});
|
|
2801
3148
|
return async function handleTeamsMessage(context, turnAdoptionLifecycle) {
|
|
2802
|
-
const
|
|
2803
|
-
const attachments = Array.isArray(activity.attachments) ? activity.attachments : [];
|
|
2804
|
-
const rawText = activity.text?.trim() ?? "";
|
|
2805
|
-
const htmlText = extractTextFromHtmlAttachments(attachments);
|
|
2806
|
-
const valueText = rawText || htmlText ? "" : serializeMSTeamsAdaptiveCardActionValue(activity.value);
|
|
2807
|
-
const text = require_channel.stripMSTeamsMentionTags(rawText || htmlText || valueText || "");
|
|
2808
|
-
const wasMentioned = require_channel.wasMSTeamsBotMentioned(activity);
|
|
2809
|
-
const conversationId = require_channel.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
2810
|
-
const replyToId = activity.replyToId ?? void 0;
|
|
2811
|
-
const implicitMentionKinds = conversationId && replyToId && await wasMSTeamsMessageSentWithPersistence({
|
|
2812
|
-
conversationId,
|
|
2813
|
-
messageId: replyToId
|
|
2814
|
-
}) ? ["reply_to_bot"] : [];
|
|
2815
|
-
await inboundDebouncer.enqueue({
|
|
3149
|
+
const entry = await prepareMSTeamsDebounceEntry({
|
|
2816
3150
|
context,
|
|
2817
|
-
rawText,
|
|
2818
|
-
text,
|
|
2819
|
-
attachments,
|
|
2820
|
-
wasMentioned,
|
|
2821
|
-
implicitMentionKinds,
|
|
2822
3151
|
turnAdoptionLifecycle
|
|
2823
3152
|
});
|
|
3153
|
+
await inboundDebouncer.enqueue(entry);
|
|
2824
3154
|
if (turnAdoptionLifecycle) return { kind: "deferred" };
|
|
2825
3155
|
};
|
|
2826
3156
|
}
|
|
@@ -2846,7 +3176,7 @@ function createMSTeamsReactionHandler(deps) {
|
|
|
2846
3176
|
log.debug?.("reaction activity missing from.id; skipping");
|
|
2847
3177
|
return;
|
|
2848
3178
|
}
|
|
2849
|
-
const conversationId =
|
|
3179
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
2850
3180
|
const conversationType = activity.conversation?.conversationType ?? "personal";
|
|
2851
3181
|
const isGroupChat = conversationType === "groupChat" || activity.conversation?.isGroup === true;
|
|
2852
3182
|
const isChannel = conversationType === "channel";
|
|
@@ -3142,7 +3472,7 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
|
|
|
3142
3472
|
userComment = JSON.parse(value.actionValue.feedback).feedbackText || void 0;
|
|
3143
3473
|
} catch {}
|
|
3144
3474
|
const rawConversationId = activity.conversation?.id ?? "unknown";
|
|
3145
|
-
const conversationId =
|
|
3475
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(rawConversationId);
|
|
3146
3476
|
const senderId = activity.from?.aadObjectId ?? activity.from?.id ?? "unknown";
|
|
3147
3477
|
const messageId = value.replyToId ?? activity.replyToId ?? "unknown";
|
|
3148
3478
|
const isNegative = reaction === "dislike";
|
|
@@ -3157,7 +3487,7 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
|
|
|
3157
3487
|
id: isDirectMessage ? senderId : conversationId
|
|
3158
3488
|
}
|
|
3159
3489
|
});
|
|
3160
|
-
const feedbackThreadId = isChannel ?
|
|
3490
|
+
const feedbackThreadId = isChannel ? require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId) ?? activity.replyToId ?? void 0 : void 0;
|
|
3161
3491
|
if (feedbackThreadId) route.sessionKey = (0, openclaw_plugin_sdk_routing.resolveThreadSessionKeys)({
|
|
3162
3492
|
baseSessionKey: route.sessionKey,
|
|
3163
3493
|
threadId: feedbackThreadId,
|
|
@@ -3239,8 +3569,8 @@ async function handleMSTeamsFileConsentInvoke(context, log) {
|
|
|
3239
3569
|
const fsFile = inMemoryFile ? void 0 : await require_messenger.getPendingUploadFs(uploadId);
|
|
3240
3570
|
const pendingFile = inMemoryFile ?? fsFile;
|
|
3241
3571
|
if (pendingFile) {
|
|
3242
|
-
const pendingConversationId =
|
|
3243
|
-
const invokeConversationId =
|
|
3572
|
+
const pendingConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(pendingFile.conversationId);
|
|
3573
|
+
const invokeConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
3244
3574
|
if (!invokeConversationId || pendingConversationId !== invokeConversationId) {
|
|
3245
3575
|
log.info("file consent conversation mismatch", {
|
|
3246
3576
|
uploadId,
|
|
@@ -3335,27 +3665,15 @@ const MSTEAMS_INGRESS_VERSION = 1;
|
|
|
3335
3665
|
const MSTEAMS_INGRESS_DRAIN_INTERVAL_MS = 500;
|
|
3336
3666
|
const MSTEAMS_INGRESS_MAX_CONCURRENT_DELIVERIES = 8;
|
|
3337
3667
|
const MSTEAMS_INGRESS_SCAN_LIMIT = 100;
|
|
3338
|
-
const
|
|
3339
|
-
const MSTEAMS_INGRESS_COMPLETED_MAX_ENTRIES = 2e4;
|
|
3340
|
-
const MSTEAMS_INGRESS_FAILED_MAX_ENTRIES = 4096;
|
|
3341
|
-
var MSTeamsIngressPayloadError = class extends Error {
|
|
3342
|
-
constructor(reason, message, options) {
|
|
3343
|
-
super(message, options);
|
|
3344
|
-
this.reason = reason;
|
|
3345
|
-
this.name = "MSTeamsIngressPayloadError";
|
|
3346
|
-
}
|
|
3347
|
-
};
|
|
3348
|
-
function nonEmptyString(value) {
|
|
3349
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
3350
|
-
}
|
|
3668
|
+
const MSTeamsIngressPayloadError = (0, openclaw_plugin_sdk_channel_outbound.createChannelIngressError)("MSTeamsIngressPayloadError", { withReason: true });
|
|
3351
3669
|
function isDispatchableActivity(activity) {
|
|
3352
3670
|
return activity.type === "message" || activity.type === "invoke" && activity.name === "adaptiveCard/action";
|
|
3353
3671
|
}
|
|
3354
3672
|
function inspectMSTeamsIngressActivity(activity) {
|
|
3355
3673
|
if (!isDispatchableActivity(activity)) return null;
|
|
3356
|
-
const eventId =
|
|
3674
|
+
const eventId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeNullableString)(activity.id);
|
|
3357
3675
|
if (!eventId) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams dispatchable activity is missing activity.id.");
|
|
3358
|
-
const laneKey =
|
|
3676
|
+
const laneKey = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeNullableString)(activity.conversation?.id);
|
|
3359
3677
|
if (!laneKey) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams dispatchable activity is missing conversation.id.");
|
|
3360
3678
|
return {
|
|
3361
3679
|
eventId,
|
|
@@ -3414,10 +3732,7 @@ function createMSTeamsIngress(options) {
|
|
|
3414
3732
|
pollIntervalMs: MSTEAMS_INGRESS_DRAIN_INTERVAL_MS,
|
|
3415
3733
|
retention: {
|
|
3416
3734
|
pruneIntervalMs: 0,
|
|
3417
|
-
|
|
3418
|
-
completedMaxEntries: MSTEAMS_INGRESS_COMPLETED_MAX_ENTRIES,
|
|
3419
|
-
failedTtlMs: MSTEAMS_INGRESS_TOMBSTONE_TTL_MS,
|
|
3420
|
-
failedMaxEntries: MSTEAMS_INGRESS_FAILED_MAX_ENTRIES
|
|
3735
|
+
failedMaxEntries: 4096
|
|
3421
3736
|
},
|
|
3422
3737
|
appendRetryDelaysMs: [0],
|
|
3423
3738
|
waitForDeliveryIdleBeforeRepump: false,
|
|
@@ -3529,13 +3844,13 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3529
3844
|
const configuredAllowFrom = msteamsCfg.allowFrom;
|
|
3530
3845
|
const configuredGroupAllowFrom = msteamsCfg.groupAllowFrom;
|
|
3531
3846
|
let allowFrom = require_resolve_allowlist.projectStableMSTeamsUserAllowlist(configuredAllowFrom);
|
|
3532
|
-
let groupAllowFrom = require_resolve_allowlist.
|
|
3847
|
+
let groupAllowFrom = require_resolve_allowlist.projectStableMSTeamsGroupAllowlist(configuredGroupAllowFrom ?? configuredAllowFrom);
|
|
3533
3848
|
let teamsConfig = require_resolve_allowlist.projectStableMSTeamsTeamsConfig(msteamsCfg.teams);
|
|
3534
3849
|
const allowNameMatching = (0, openclaw_plugin_sdk_dangerous_name_runtime.isDangerousNameMatchingEnabled)(msteamsCfg);
|
|
3535
3850
|
const cleanAllowEntry = (entry) => entry.replace(/^(msteams|teams):/i, "").replace(/^user:/i, "").trim();
|
|
3536
3851
|
const isStableUserId = (entry) => /^[0-9a-fA-F-]{16,}$/.test(entry);
|
|
3537
3852
|
const cleanAllowEntries = (entries) => entries?.map((entry) => cleanAllowEntry(entry)).filter((entry) => entry && entry !== "*") ?? [];
|
|
3538
|
-
const isMutableUserEntry = (entry) => !isStableUserId(entry) && !/^accessGroup:/i.test(entry);
|
|
3853
|
+
const isMutableUserEntry = (entry) => !isStableUserId(entry) && !/^accessGroup:/i.test(entry) && !require_resolve_allowlist.looksLikeMSTeamsConversationId(require_resolve_allowlist.normalizeMSTeamsConversationId(entry));
|
|
3539
3854
|
const resolveAllowlistUsers = async (label, entries) => {
|
|
3540
3855
|
if (entries.length === 0) return {
|
|
3541
3856
|
additions: [],
|
|
@@ -3588,6 +3903,10 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3588
3903
|
} catch (err) {
|
|
3589
3904
|
runtime.error?.(`msteams resolve failed; mutable allowlist entries are disabled. ${require_setup_surface.formatUnknownError(err)}`);
|
|
3590
3905
|
}
|
|
3906
|
+
if (configuredGroupAllowFrom == null && groupAllowFrom) groupAllowFrom = (0, openclaw_plugin_sdk_allow_from.mergeAllowlist)({
|
|
3907
|
+
existing: groupAllowFrom,
|
|
3908
|
+
additions: allowFrom ?? []
|
|
3909
|
+
});
|
|
3591
3910
|
msteamsCfg = {
|
|
3592
3911
|
...msteamsCfg,
|
|
3593
3912
|
allowFrom,
|
|
@@ -3697,8 +4016,8 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3697
4016
|
value: "Poll not found."
|
|
3698
4017
|
};
|
|
3699
4018
|
}
|
|
3700
|
-
const pollConversationId = existingPoll.conversationId ?
|
|
3701
|
-
const activityConversationId =
|
|
4019
|
+
const pollConversationId = existingPoll.conversationId ? require_resolve_allowlist.normalizeMSTeamsConversationId(existingPoll.conversationId) : void 0;
|
|
4020
|
+
const activityConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity?.conversation?.id ?? "");
|
|
3702
4021
|
if (pollConversationId && pollConversationId !== activityConversationId) {
|
|
3703
4022
|
log.info("poll vote ignored (conversation mismatch)", {
|
|
3704
4023
|
pollId: vote.pollId,
|
|
@@ -3930,9 +4249,9 @@ function buildActivityHandler() {
|
|
|
3930
4249
|
}
|
|
3931
4250
|
function createMSTeamsReplayContext(activity, app, serviceUrlBoundary) {
|
|
3932
4251
|
const rawConversationId = activity.conversation?.id ?? "";
|
|
3933
|
-
const conversationId =
|
|
4252
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(rawConversationId);
|
|
3934
4253
|
const conversationType = activity.conversation?.conversationType ?? "personal";
|
|
3935
|
-
const threadActivityId = conversationType.toLowerCase() === "channel" ?
|
|
4254
|
+
const threadActivityId = conversationType.toLowerCase() === "channel" ? require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId) ?? activity.replyToId : void 0;
|
|
3936
4255
|
const tenantId = activity.channelData?.tenant?.id ?? activity.conversation?.tenantId;
|
|
3937
4256
|
const reference = {
|
|
3938
4257
|
activityId: activity.id,
|