@openclaw/msteams 2026.7.2-beta.5 → 2026.8.1-beta.2
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/adapter.runtime-DEYjslFX.cjs +309 -0
- package/dist/api.cjs +2 -2
- package/dist/{channel-DtZFlwhL.cjs → channel-DfCbi0d_.cjs} +209 -301
- 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-BJmueA4q.cjs} +99 -64
- 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 +19 -18
- package/dist/{messenger-BJ0KM2sD.cjs → messenger-BqA8G3pY.cjs} +55 -26
- package/dist/{oauth-CNcnL8of.cjs → oauth-DktsbN22.cjs} +12 -25
- package/dist/{polls-CWExq2xZ.cjs → polls-DvuJvaDG.cjs} +6 -12
- package/dist/qa-runner-api.cjs +36 -0
- package/dist/{resolve-allowlist-Dr_1sOJh.cjs → resolve-allowlist-C6YcNC9a.cjs} +292 -117
- package/dist/rolldown-runtime-D6vf50IK.cjs +28 -0
- package/dist/secret-contract-api.cjs +6 -30
- package/dist/setup-plugin-api.cjs +3 -4
- package/dist/{setup-surface-xVejKCJV.cjs → setup-surface-DAhGL2h8.cjs} +20 -44
- package/dist/{src-CsXzz0x2.cjs → src-Bz9wY0Mr.cjs} +1553 -1070
- package/dist/{sso-token-store-OSg229re.cjs → sso-token-store-ChhO1f4i.cjs} +1 -1
- package/openclaw.plugin.json +34 -8
- package/package.json +5 -6
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.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 require_polls = require("./polls-
|
|
8
|
-
const require_sso_token_store = require("./sso-token-store-
|
|
9
|
-
const require_messenger = require("./messenger-
|
|
4
|
+
const require_resolve_allowlist = require("./resolve-allowlist-C6YcNC9a.cjs");
|
|
5
|
+
const require_channel = require("./channel-DfCbi0d_.cjs");
|
|
6
|
+
const require_setup_surface = require("./setup-surface-DAhGL2h8.cjs");
|
|
7
|
+
const require_polls = require("./polls-DvuJvaDG.cjs");
|
|
8
|
+
const require_sso_token_store = require("./sso-token-store-ChhO1f4i.cjs");
|
|
9
|
+
const require_messenger = require("./messenger-BqA8G3pY.cjs");
|
|
10
10
|
let openclaw_plugin_sdk_channel_outbound = require("openclaw/plugin-sdk/channel-outbound");
|
|
11
11
|
let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
|
|
12
12
|
let openclaw_plugin_sdk_allow_from = require("openclaw/plugin-sdk/allow-from");
|
|
@@ -15,16 +15,21 @@ let openclaw_plugin_sdk_ssrf_runtime = require("openclaw/plugin-sdk/ssrf-runtime
|
|
|
15
15
|
let openclaw_plugin_sdk_provider_http = require("openclaw/plugin-sdk/provider-http");
|
|
16
16
|
let openclaw_plugin_sdk_number_runtime = require("openclaw/plugin-sdk/number-runtime");
|
|
17
17
|
let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text-utility-runtime");
|
|
18
|
+
let openclaw_plugin_sdk_error_runtime = require("openclaw/plugin-sdk/error-runtime");
|
|
18
19
|
let openclaw_plugin_sdk_routing = require("openclaw/plugin-sdk/routing");
|
|
19
20
|
let node_crypto = require("node:crypto");
|
|
20
|
-
node_crypto =
|
|
21
|
+
node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
|
|
21
22
|
let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
|
|
22
23
|
let openclaw_plugin_sdk_channel_ingress_runtime = require("openclaw/plugin-sdk/channel-ingress-runtime");
|
|
23
24
|
let openclaw_plugin_sdk_context_visibility_runtime = require("openclaw/plugin-sdk/context-visibility-runtime");
|
|
24
25
|
let openclaw_plugin_sdk_reply_history = require("openclaw/plugin-sdk/reply-history");
|
|
25
26
|
let openclaw_plugin_sdk_agent_runtime = require("openclaw/plugin-sdk/agent-runtime");
|
|
27
|
+
let openclaw_plugin_sdk_extension_shared = require("openclaw/plugin-sdk/extension-shared");
|
|
28
|
+
let openclaw_plugin_sdk_plugin_runtime = require("openclaw/plugin-sdk/plugin-runtime");
|
|
26
29
|
let openclaw_plugin_sdk_dedupe_runtime = require("openclaw/plugin-sdk/dedupe-runtime");
|
|
30
|
+
let openclaw_plugin_sdk_plugin_state_runtime = require("openclaw/plugin-sdk/plugin-state-runtime");
|
|
27
31
|
let openclaw_plugin_sdk_collection_runtime = require("openclaw/plugin-sdk/collection-runtime");
|
|
32
|
+
let openclaw_plugin_sdk_gateway_runtime = require("openclaw/plugin-sdk/gateway-runtime");
|
|
28
33
|
let openclaw_plugin_sdk_channel_pairing = require("openclaw/plugin-sdk/channel-pairing");
|
|
29
34
|
let openclaw_plugin_sdk_account_id = require("openclaw/plugin-sdk/account-id");
|
|
30
35
|
let openclaw_plugin_sdk_runtime_group_policy = require("openclaw/plugin-sdk/runtime-group-policy");
|
|
@@ -168,20 +173,50 @@ const msteamsIngressIdentity = {
|
|
|
168
173
|
aliases: [{
|
|
169
174
|
key: "sender-name",
|
|
170
175
|
kind: "plugin:msteams-sender-name",
|
|
171
|
-
normalizeEntry:
|
|
172
|
-
normalizeSubject:
|
|
176
|
+
normalizeEntry: normalizeSenderNameIngressValue,
|
|
177
|
+
normalizeSubject: normalizeSenderNameIngressValue,
|
|
173
178
|
dangerous: true
|
|
179
|
+
}, {
|
|
180
|
+
key: "conversation-id",
|
|
181
|
+
kind: "plugin:msteams-conversation-id",
|
|
182
|
+
normalizeEntry: normalizeAllowlistConversationId,
|
|
183
|
+
normalizeSubject: normalizeAllowlistConversationId
|
|
174
184
|
}],
|
|
175
185
|
isWildcardEntry: (entry) => normalizeIngressValue(entry) === "*",
|
|
176
|
-
resolveEntryId: ({ entryIndex, fieldKey }) => `msteams-entry-${entryIndex + 1}:${fieldKey === "sender-name" ? "name" : "id"}`
|
|
186
|
+
resolveEntryId: ({ entryIndex, fieldKey }) => `msteams-entry-${entryIndex + 1}:${fieldKey === "sender-name" ? "name" : fieldKey === "conversation-id" ? "conversation-id" : "id"}`
|
|
177
187
|
};
|
|
178
188
|
function normalizeIngressValue(value) {
|
|
179
189
|
return (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(value) ?? null;
|
|
180
190
|
}
|
|
191
|
+
function normalizeSenderNameIngressValue(value) {
|
|
192
|
+
const normalized = normalizeIngressValue(value);
|
|
193
|
+
if (!normalized) return null;
|
|
194
|
+
return require_resolve_allowlist.looksLikeMSTeamsConversationId(require_resolve_allowlist.normalizeMSTeamsConversationId(normalized)) ? null : normalized;
|
|
195
|
+
}
|
|
196
|
+
function normalizeAllowlistConversationId(value) {
|
|
197
|
+
const trimmed = value?.trim();
|
|
198
|
+
return trimmed ? require_resolve_allowlist.normalizeMSTeamsConversationId(trimmed) : null;
|
|
199
|
+
}
|
|
200
|
+
function formatMSTeamsSenderReason(params) {
|
|
201
|
+
switch (params.reasonCode) {
|
|
202
|
+
case "dm_policy_open": return "dmPolicy=open";
|
|
203
|
+
case "dm_policy_disabled": return "dmPolicy=disabled";
|
|
204
|
+
case "dm_policy_pairing_required": return "dmPolicy=pairing (not allowlisted)";
|
|
205
|
+
case "dm_policy_allowlisted": return `dmPolicy=${params.dmPolicy ?? "allowlist"} (allowlisted)`;
|
|
206
|
+
case "dm_policy_not_allowlisted": return `dmPolicy=${params.dmPolicy ?? "allowlist"} (not allowlisted)`;
|
|
207
|
+
case "group_policy_disabled": return "groupPolicy=disabled";
|
|
208
|
+
case "group_policy_empty_allowlist":
|
|
209
|
+
case "route_sender_empty": return "groupPolicy=allowlist (empty allowlist)";
|
|
210
|
+
case "group_policy_not_allowlisted": return "groupPolicy=allowlist (not allowlisted)";
|
|
211
|
+
case "group_policy_open": return "groupPolicy=open";
|
|
212
|
+
case "group_policy_allowed": return `groupPolicy=${params.groupPolicy ?? "allowlist"}`;
|
|
213
|
+
default: return params.reasonCode;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
181
216
|
async function resolveMSTeamsSenderAccess(params) {
|
|
182
217
|
const activity = params.activity;
|
|
183
218
|
const msteamsCfg = params.cfg.channels?.msteams;
|
|
184
|
-
const conversationId =
|
|
219
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "unknown");
|
|
185
220
|
const convType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(activity.conversation?.conversationType);
|
|
186
221
|
const isDirectMessage = convType === "personal" || !convType && !activity.conversation?.isGroup;
|
|
187
222
|
const senderId = activity.from?.aadObjectId ?? activity.from?.id ?? "unknown";
|
|
@@ -205,47 +240,53 @@ async function resolveMSTeamsSenderAccess(params) {
|
|
|
205
240
|
channelName: activity.channelData?.channel?.name,
|
|
206
241
|
allowNameMatching
|
|
207
242
|
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
conversation: {
|
|
220
|
-
kind: isDirectMessage ? "direct" : convType === "channel" ? "channel" : "group",
|
|
221
|
-
id: conversationId,
|
|
222
|
-
parentId: activity.channelData?.team?.id
|
|
223
|
-
},
|
|
224
|
-
route: (0, openclaw_plugin_sdk_channel_ingress_runtime.channelIngressRoutes)(!isDirectMessage && channelGate.allowlistConfigured && {
|
|
225
|
-
id: "msteams:team-channel",
|
|
226
|
-
kind: "nestedAllowlist",
|
|
227
|
-
allowed: channelGate.allowed,
|
|
228
|
-
precedence: 0,
|
|
229
|
-
matchId: "msteams-route",
|
|
230
|
-
...channelGate.allowed && groupPolicy === "allowlist" ? {
|
|
231
|
-
senderPolicy: "deny-when-empty",
|
|
232
|
-
senderAllowFromSource: "effective-group"
|
|
233
|
-
} : {}
|
|
234
|
-
}),
|
|
235
|
-
dmPolicy,
|
|
236
|
-
groupPolicy,
|
|
237
|
-
policy: {
|
|
238
|
-
groupAllowFromFallbackToAllowFrom: true,
|
|
239
|
-
mutableIdentifierMatching: allowNameMatching ? "enabled" : "disabled"
|
|
240
|
-
},
|
|
241
|
-
allowFrom: configuredDmAllowFrom,
|
|
242
|
-
groupAllowFrom,
|
|
243
|
-
command: {
|
|
244
|
-
allowTextCommands: true,
|
|
245
|
-
hasControlCommand: params.hasControlCommand === true,
|
|
246
|
-
directGroupAllowFrom: isDirectMessage ? "effective" : "none"
|
|
243
|
+
const resolved = await (0, openclaw_plugin_sdk_channel_ingress_runtime.resolveStableChannelMessageIngress)({
|
|
244
|
+
channelId: "msteams",
|
|
245
|
+
accountId: pairing.accountId,
|
|
246
|
+
identity: msteamsIngressIdentity,
|
|
247
|
+
cfg: params.cfg,
|
|
248
|
+
readStoreAllowFrom: pairing.readAllowFromStore,
|
|
249
|
+
subject: {
|
|
250
|
+
stableId: senderId,
|
|
251
|
+
aliases: {
|
|
252
|
+
"sender-name": senderName,
|
|
253
|
+
...!isDirectMessage ? { "conversation-id": conversationId } : {}
|
|
247
254
|
}
|
|
255
|
+
},
|
|
256
|
+
conversation: {
|
|
257
|
+
kind: isDirectMessage ? "direct" : convType === "channel" ? "channel" : "group",
|
|
258
|
+
id: conversationId,
|
|
259
|
+
threadId: params.conversationThreadId
|
|
260
|
+
},
|
|
261
|
+
...params.contextBinding ? { contextBinding: params.contextBinding } : {},
|
|
262
|
+
route: (0, openclaw_plugin_sdk_channel_ingress_runtime.channelIngressRoutes)(!isDirectMessage && channelGate.allowlistConfigured && {
|
|
263
|
+
id: "msteams:team-channel",
|
|
264
|
+
kind: "nestedAllowlist",
|
|
265
|
+
allowed: channelGate.allowed,
|
|
266
|
+
precedence: 0,
|
|
267
|
+
matchId: "msteams-route",
|
|
268
|
+
...channelGate.allowed && groupPolicy === "allowlist" ? {
|
|
269
|
+
senderPolicy: "deny-when-empty",
|
|
270
|
+
senderAllowFromSource: "effective-group"
|
|
271
|
+
} : {}
|
|
248
272
|
}),
|
|
273
|
+
dmPolicy,
|
|
274
|
+
groupPolicy,
|
|
275
|
+
policy: {
|
|
276
|
+
groupAllowFromFallbackToAllowFrom: true,
|
|
277
|
+
mutableIdentifierMatching: allowNameMatching ? "enabled" : "disabled"
|
|
278
|
+
},
|
|
279
|
+
allowFrom: configuredDmAllowFrom,
|
|
280
|
+
groupAllowFrom,
|
|
281
|
+
command: {
|
|
282
|
+
allowTextCommands: true,
|
|
283
|
+
hasControlCommand: params.hasControlCommand === true,
|
|
284
|
+
directGroupAllowFrom: isDirectMessage ? "effective" : "none"
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return {
|
|
288
|
+
...resolved,
|
|
289
|
+
channelIngress: resolved,
|
|
249
290
|
pairing,
|
|
250
291
|
isDirectMessage,
|
|
251
292
|
conversationId,
|
|
@@ -258,6 +299,136 @@ async function resolveMSTeamsSenderAccess(params) {
|
|
|
258
299
|
groupPolicy
|
|
259
300
|
};
|
|
260
301
|
}
|
|
302
|
+
async function admitMSTeamsMessage(params) {
|
|
303
|
+
const core = require_runtime.getMSTeamsRuntime();
|
|
304
|
+
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
|
305
|
+
cfg: params.cfg,
|
|
306
|
+
surface: "msteams"
|
|
307
|
+
});
|
|
308
|
+
const isControlCommand = allowTextCommands && core.channel.commands.isControlCommandMessage(params.text, params.cfg);
|
|
309
|
+
const access = await resolveMSTeamsSenderAccess({
|
|
310
|
+
cfg: params.cfg,
|
|
311
|
+
activity: params.activity,
|
|
312
|
+
hasControlCommand: isControlCommand
|
|
313
|
+
});
|
|
314
|
+
const { dmPolicy, senderId, senderName, pairing, isDirectMessage, channelGate, senderAccess, commandAccess, allowNameMatching, groupPolicy, msteamsCfg } = access;
|
|
315
|
+
const effectiveDmAllowFrom = senderAccess.effectiveAllowFrom;
|
|
316
|
+
const effectiveGroupAllowFrom = senderAccess.effectiveGroupAllowFrom;
|
|
317
|
+
if (isDirectMessage && msteamsCfg && senderAccess.decision !== "allow") {
|
|
318
|
+
if (senderAccess.reasonCode === "dm_policy_disabled") {
|
|
319
|
+
params.log.info("dropping dm (dms disabled)", {
|
|
320
|
+
sender: senderId,
|
|
321
|
+
label: senderName
|
|
322
|
+
});
|
|
323
|
+
params.log.debug?.("dropping dm (dms disabled)");
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
const allowMatch = require_channel.resolveMSTeamsAllowlistMatch({
|
|
327
|
+
allowFrom: effectiveDmAllowFrom,
|
|
328
|
+
senderId,
|
|
329
|
+
senderName,
|
|
330
|
+
allowNameMatching
|
|
331
|
+
});
|
|
332
|
+
if (senderAccess.decision === "pairing") {
|
|
333
|
+
params.conversationStore.upsert(params.conversationId, params.conversationRef).catch((err) => {
|
|
334
|
+
params.log.debug?.("failed to save conversation reference", { error: require_setup_surface.formatUnknownError(err) });
|
|
335
|
+
});
|
|
336
|
+
if (await pairing.upsertPairingRequest({
|
|
337
|
+
id: senderId,
|
|
338
|
+
meta: { name: senderName }
|
|
339
|
+
})) params.log.info("msteams pairing request created", {
|
|
340
|
+
sender: senderId,
|
|
341
|
+
label: senderName
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
params.log.debug?.("dropping dm (not allowlisted)", {
|
|
345
|
+
sender: senderId,
|
|
346
|
+
label: senderName,
|
|
347
|
+
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
348
|
+
});
|
|
349
|
+
params.log.info("dropping dm (not allowlisted)", {
|
|
350
|
+
sender: senderId,
|
|
351
|
+
label: senderName,
|
|
352
|
+
dmPolicy,
|
|
353
|
+
reason: formatMSTeamsSenderReason({
|
|
354
|
+
reasonCode: senderAccess.reasonCode,
|
|
355
|
+
dmPolicy,
|
|
356
|
+
groupPolicy
|
|
357
|
+
}),
|
|
358
|
+
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
359
|
+
});
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
if (!isDirectMessage && msteamsCfg) {
|
|
363
|
+
if (channelGate.allowlistConfigured && !channelGate.allowed) {
|
|
364
|
+
params.log.info("dropping group message (not in team/channel allowlist)", {
|
|
365
|
+
conversationId: params.conversationId,
|
|
366
|
+
teamKey: channelGate.teamKey ?? "none",
|
|
367
|
+
channelKey: channelGate.channelKey ?? "none",
|
|
368
|
+
channelMatchKey: channelGate.channelMatchKey ?? "none",
|
|
369
|
+
channelMatchSource: channelGate.channelMatchSource ?? "none"
|
|
370
|
+
});
|
|
371
|
+
params.log.debug?.("dropping group message (not in team/channel allowlist)", {
|
|
372
|
+
conversationId: params.conversationId,
|
|
373
|
+
teamKey: channelGate.teamKey ?? "none",
|
|
374
|
+
channelKey: channelGate.channelKey ?? "none",
|
|
375
|
+
channelMatchKey: channelGate.channelMatchKey ?? "none",
|
|
376
|
+
channelMatchSource: channelGate.channelMatchSource ?? "none"
|
|
377
|
+
});
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_disabled") {
|
|
381
|
+
params.log.info("dropping group message (groupPolicy: disabled)", { conversationId: params.conversationId });
|
|
382
|
+
params.log.debug?.("dropping group message (groupPolicy: disabled)", { conversationId: params.conversationId });
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
if (!senderAccess.allowed && (senderAccess.reasonCode === "group_policy_empty_allowlist" || senderAccess.reasonCode === "route_sender_empty")) {
|
|
386
|
+
params.log.info("dropping group message (groupPolicy: allowlist, no allowlist)", { conversationId: params.conversationId });
|
|
387
|
+
params.log.debug?.("dropping group message (groupPolicy: allowlist, no allowlist)", { conversationId: params.conversationId });
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
if (!senderAccess.allowed) {
|
|
391
|
+
const allowMatch = require_channel.resolveMSTeamsAllowlistMatch({
|
|
392
|
+
allowFrom: effectiveGroupAllowFrom,
|
|
393
|
+
senderId,
|
|
394
|
+
senderName,
|
|
395
|
+
allowNameMatching
|
|
396
|
+
});
|
|
397
|
+
params.log.debug?.("dropping group message (not in groupAllowFrom)", {
|
|
398
|
+
sender: senderId,
|
|
399
|
+
label: senderName,
|
|
400
|
+
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
401
|
+
});
|
|
402
|
+
params.log.info("dropping group message (not in groupAllowFrom)", {
|
|
403
|
+
sender: senderId,
|
|
404
|
+
label: senderName,
|
|
405
|
+
allowlistMatch: (0, openclaw_plugin_sdk_allow_from.formatAllowlistMatchMeta)(allowMatch)
|
|
406
|
+
});
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (commandAccess.shouldBlockControlCommand) {
|
|
411
|
+
(0, openclaw_plugin_sdk_channel_inbound.logInboundDrop)({
|
|
412
|
+
log: params.logVerboseMessage,
|
|
413
|
+
channel: "msteams",
|
|
414
|
+
reason: "control command (unauthorized)",
|
|
415
|
+
target: senderId
|
|
416
|
+
});
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
params.conversationStore.upsert(params.conversationId, params.conversationRef).catch((err) => {
|
|
420
|
+
params.log.debug?.("failed to save conversation reference", { error: require_setup_surface.formatUnknownError(err) });
|
|
421
|
+
});
|
|
422
|
+
return {
|
|
423
|
+
...access,
|
|
424
|
+
allowTextCommands,
|
|
425
|
+
isControlCommand,
|
|
426
|
+
commandAuthorized: commandAccess.requested ? commandAccess.authorized : void 0,
|
|
427
|
+
effectiveDmAllowFrom,
|
|
428
|
+
effectiveGroupAllowFrom,
|
|
429
|
+
isChannel: params.isChannel
|
|
430
|
+
};
|
|
431
|
+
}
|
|
261
432
|
//#endregion
|
|
262
433
|
//#region extensions/msteams/src/attachments/bot-framework.ts
|
|
263
434
|
/**
|
|
@@ -308,7 +479,7 @@ async function fetchBotFrameworkAttachmentInfo(params) {
|
|
|
308
479
|
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
309
480
|
});
|
|
310
481
|
} catch (err) {
|
|
311
|
-
params.logger?.warn?.("msteams botFramework attachmentInfo fetch failed", { error:
|
|
482
|
+
params.logger?.warn?.("msteams botFramework attachmentInfo fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
312
483
|
return;
|
|
313
484
|
}
|
|
314
485
|
if (!response.ok) {
|
|
@@ -319,7 +490,7 @@ async function fetchBotFrameworkAttachmentInfo(params) {
|
|
|
319
490
|
try {
|
|
320
491
|
return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(response, "msteams botFramework attachmentInfo");
|
|
321
492
|
} catch (err) {
|
|
322
|
-
params.logger?.warn?.("msteams botFramework attachmentInfo parse failed", { error:
|
|
493
|
+
params.logger?.warn?.("msteams botFramework attachmentInfo parse failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
323
494
|
return;
|
|
324
495
|
}
|
|
325
496
|
}
|
|
@@ -341,7 +512,7 @@ async function saveBotFrameworkAttachmentView(params) {
|
|
|
341
512
|
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
342
513
|
});
|
|
343
514
|
} catch (err) {
|
|
344
|
-
params.logger?.warn?.("msteams botFramework attachmentView fetch failed", { error:
|
|
515
|
+
params.logger?.warn?.("msteams botFramework attachmentView fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
345
516
|
return;
|
|
346
517
|
}
|
|
347
518
|
if (!response.ok) {
|
|
@@ -354,7 +525,7 @@ async function saveBotFrameworkAttachmentView(params) {
|
|
|
354
525
|
contentLength = (0, openclaw_plugin_sdk_media_runtime.parseMediaContentLength)(response.headers.get("content-length"));
|
|
355
526
|
} catch (err) {
|
|
356
527
|
await response.body?.cancel().catch(() => void 0);
|
|
357
|
-
params.logger?.warn?.("msteams botFramework attachmentView invalid content-length", { error:
|
|
528
|
+
params.logger?.warn?.("msteams botFramework attachmentView invalid content-length", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
358
529
|
return;
|
|
359
530
|
}
|
|
360
531
|
if (contentLength !== null && contentLength > params.maxBytes) {
|
|
@@ -371,7 +542,7 @@ async function saveBotFrameworkAttachmentView(params) {
|
|
|
371
542
|
originalFilename: params.preserveFilenames ? params.fileNameHint : void 0
|
|
372
543
|
});
|
|
373
544
|
} catch (err) {
|
|
374
|
-
params.logger?.warn?.("msteams botFramework attachmentView save failed", { error:
|
|
545
|
+
params.logger?.warn?.("msteams botFramework attachmentView save failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
375
546
|
return;
|
|
376
547
|
} finally {
|
|
377
548
|
await response.body?.cancel().catch(() => void 0);
|
|
@@ -399,7 +570,7 @@ async function downloadMSTeamsBotFrameworkAttachment(params) {
|
|
|
399
570
|
work: () => tokenProvider.getAccessToken(BOT_FRAMEWORK_SCOPE)
|
|
400
571
|
});
|
|
401
572
|
} catch (err) {
|
|
402
|
-
params.logger?.warn?.("msteams botFramework token acquisition failed", { error:
|
|
573
|
+
params.logger?.warn?.("msteams botFramework token acquisition failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
403
574
|
return;
|
|
404
575
|
}
|
|
405
576
|
if (!accessToken) return;
|
|
@@ -500,7 +671,7 @@ async function downloadMSTeamsBotFrameworkAttachments(params) {
|
|
|
500
671
|
sourceId: attachmentId
|
|
501
672
|
});
|
|
502
673
|
params.logger?.warn?.("msteams botFramework attachment download failed", {
|
|
503
|
-
error:
|
|
674
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
504
675
|
attachmentId
|
|
505
676
|
});
|
|
506
677
|
}
|
|
@@ -713,9 +884,6 @@ function scopeCandidatesForUrl(url) {
|
|
|
713
884
|
return ["https://api.botframework.com", "https://graph.microsoft.com"];
|
|
714
885
|
}
|
|
715
886
|
}
|
|
716
|
-
function isRedirectStatus(status) {
|
|
717
|
-
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
718
|
-
}
|
|
719
887
|
async function resolveInlineDataImageMime(inline) {
|
|
720
888
|
const mime = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(await require_runtime.getMSTeamsRuntime().media.detectMime({
|
|
721
889
|
buffer: inline.data,
|
|
@@ -738,7 +906,7 @@ async function fetchWithAuthFallback(params) {
|
|
|
738
906
|
const tokenProvider = params.tokenProvider;
|
|
739
907
|
if (firstAttempt.status !== 401 && firstAttempt.status !== 403) return firstAttempt;
|
|
740
908
|
if (!require_resolve_allowlist.isUrlAllowed(params.url, params.policy.authAllowHosts)) return firstAttempt;
|
|
741
|
-
|
|
909
|
+
let fallbackAttempt = firstAttempt;
|
|
742
910
|
const scopes = scopeCandidatesForUrl(params.url);
|
|
743
911
|
const fetchFn = params.fetchFn ?? fetch;
|
|
744
912
|
for (const scope of scopes) try {
|
|
@@ -761,15 +929,11 @@ async function fetchWithAuthFallback(params) {
|
|
|
761
929
|
resolveFn: params.resolveFn,
|
|
762
930
|
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
763
931
|
});
|
|
764
|
-
|
|
765
|
-
if (isRedirectStatus(authAttempt.status)) return authAttempt;
|
|
766
|
-
|
|
767
|
-
await authAttempt.body?.cancel();
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
await authAttempt.body?.cancel();
|
|
932
|
+
await fallbackAttempt.body?.cancel().catch(() => void 0);
|
|
933
|
+
if (authAttempt.ok || require_resolve_allowlist.isRedirectStatus(authAttempt.status)) return authAttempt;
|
|
934
|
+
fallbackAttempt = authAttempt;
|
|
771
935
|
} catch {}
|
|
772
|
-
return
|
|
936
|
+
return fallbackAttempt;
|
|
773
937
|
}
|
|
774
938
|
/**
|
|
775
939
|
* Download all file attachments from a Teams message (images, documents, etc.).
|
|
@@ -850,7 +1014,7 @@ async function downloadMSTeamsAttachments(params) {
|
|
|
850
1014
|
}, candidate.sourceId));
|
|
851
1015
|
} catch (err) {
|
|
852
1016
|
out.push(withSourceId({ kind: candidate.mediaKind }, candidate.sourceId));
|
|
853
|
-
params.logger?.warn?.("msteams inline attachment decode failed", { error:
|
|
1017
|
+
params.logger?.warn?.("msteams inline attachment decode failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
854
1018
|
}
|
|
855
1019
|
continue;
|
|
856
1020
|
}
|
|
@@ -882,7 +1046,7 @@ async function downloadMSTeamsAttachments(params) {
|
|
|
882
1046
|
out.push(withSourceId(media, candidate.sourceId));
|
|
883
1047
|
} catch (err) {
|
|
884
1048
|
out.push(withSourceId({ kind: candidate.mediaKind }, candidate.sourceId));
|
|
885
|
-
const msg =
|
|
1049
|
+
const msg = (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err);
|
|
886
1050
|
params.logger?.warn?.(`msteams attachment download failed host=${safeHostForLog(candidate.url)} error=${msg}`);
|
|
887
1051
|
}
|
|
888
1052
|
}
|
|
@@ -921,6 +1085,10 @@ function buildMSTeamsGraphMessageUrl(params) {
|
|
|
921
1085
|
if (!chatId) return;
|
|
922
1086
|
return `${require_resolve_allowlist.GRAPH_ROOT}/chats/${encodeURIComponent(chatId)}/messages/${encodeURIComponent(messageId)}`;
|
|
923
1087
|
}
|
|
1088
|
+
async function releaseGraphResponse(response, release) {
|
|
1089
|
+
if (!response.bodyUsed) response.body?.cancel().catch(() => void 0);
|
|
1090
|
+
await release();
|
|
1091
|
+
}
|
|
924
1092
|
async function fetchGraphCollection(params) {
|
|
925
1093
|
const fetchFn = params.fetchFn ?? fetch;
|
|
926
1094
|
const { response, release } = await (0, openclaw_plugin_sdk_ssrf_runtime.fetchWithSsrFGuard)({
|
|
@@ -949,7 +1117,7 @@ async function fetchGraphCollection(params) {
|
|
|
949
1117
|
};
|
|
950
1118
|
}
|
|
951
1119
|
} finally {
|
|
952
|
-
await release
|
|
1120
|
+
await releaseGraphResponse(response, release);
|
|
953
1121
|
}
|
|
954
1122
|
}
|
|
955
1123
|
function normalizeGraphAttachment(att) {
|
|
@@ -981,7 +1149,7 @@ async function downloadGraphHostedContent(params) {
|
|
|
981
1149
|
deadline: params.deadline
|
|
982
1150
|
});
|
|
983
1151
|
} catch (err) {
|
|
984
|
-
params.logger?.warn?.("msteams graph hostedContents fetch failed", { error:
|
|
1152
|
+
params.logger?.warn?.("msteams graph hostedContents fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
985
1153
|
return {
|
|
986
1154
|
media: [],
|
|
987
1155
|
count: 0
|
|
@@ -1026,11 +1194,11 @@ async function downloadGraphHostedContent(params) {
|
|
|
1026
1194
|
sourceId: item.id
|
|
1027
1195
|
});
|
|
1028
1196
|
} finally {
|
|
1029
|
-
await release
|
|
1197
|
+
await releaseGraphResponse(valRes, release);
|
|
1030
1198
|
}
|
|
1031
1199
|
} catch (err) {
|
|
1032
1200
|
out.push(createGraphHostedContentFact(item));
|
|
1033
|
-
params.logger?.warn?.("msteams graph hostedContent value fetch failed", { error:
|
|
1201
|
+
params.logger?.warn?.("msteams graph hostedContent value fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1034
1202
|
continue;
|
|
1035
1203
|
}
|
|
1036
1204
|
}
|
|
@@ -1059,9 +1227,9 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1059
1227
|
} catch (err) {
|
|
1060
1228
|
params.logger?.debug?.("graph media token acquisition failed", {
|
|
1061
1229
|
messageUrl,
|
|
1062
|
-
error:
|
|
1230
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)
|
|
1063
1231
|
});
|
|
1064
|
-
params.logger?.warn?.("msteams graph token acquisition failed", { error:
|
|
1232
|
+
params.logger?.warn?.("msteams graph token acquisition failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1065
1233
|
return {
|
|
1066
1234
|
media: [],
|
|
1067
1235
|
messageUrl,
|
|
@@ -1092,10 +1260,10 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1092
1260
|
} catch (err) {
|
|
1093
1261
|
params.logger?.debug?.("graph media message parse failed", {
|
|
1094
1262
|
messageUrl,
|
|
1095
|
-
error:
|
|
1263
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)
|
|
1096
1264
|
});
|
|
1097
1265
|
params.logger?.warn?.("msteams graph message parse failed", {
|
|
1098
|
-
error:
|
|
1266
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
1099
1267
|
messageUrl
|
|
1100
1268
|
});
|
|
1101
1269
|
msgData = {};
|
|
@@ -1107,14 +1275,14 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1107
1275
|
status: messageStatus
|
|
1108
1276
|
});
|
|
1109
1277
|
} finally {
|
|
1110
|
-
await release
|
|
1278
|
+
await releaseGraphResponse(msgRes, release);
|
|
1111
1279
|
}
|
|
1112
1280
|
} catch (err) {
|
|
1113
1281
|
params.logger?.debug?.("graph media message fetch failed", {
|
|
1114
1282
|
messageUrl,
|
|
1115
|
-
error:
|
|
1283
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)
|
|
1116
1284
|
});
|
|
1117
|
-
params.logger?.warn?.("msteams graph message fetch failed", { error:
|
|
1285
|
+
params.logger?.warn?.("msteams graph message fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1118
1286
|
}
|
|
1119
1287
|
for (const att of referenceAttachments) {
|
|
1120
1288
|
const name = att.name ?? "file";
|
|
@@ -1180,7 +1348,7 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1180
1348
|
} catch (err) {
|
|
1181
1349
|
sharePointMedia.push(unavailableMedia);
|
|
1182
1350
|
params.logger?.warn?.("msteams SharePoint reference download failed", {
|
|
1183
|
-
error:
|
|
1351
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
1184
1352
|
name
|
|
1185
1353
|
});
|
|
1186
1354
|
}
|
|
@@ -1219,7 +1387,7 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1219
1387
|
});
|
|
1220
1388
|
} catch (err) {
|
|
1221
1389
|
params.logger?.warn?.("msteams graph attachment download failed", {
|
|
1222
|
-
error:
|
|
1390
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
1223
1391
|
messageUrl
|
|
1224
1392
|
});
|
|
1225
1393
|
}
|
|
@@ -1237,132 +1405,296 @@ 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
|
-
|
|
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
|
+
let replacementFinalPending = false;
|
|
1658
|
+
let replacementEmitFailed = false;
|
|
1659
|
+
let replacementSettlementPending = false;
|
|
1660
|
+
let replacementTextAwaitingAcknowledgement;
|
|
1661
|
+
let deferredReplacementEntries = [];
|
|
1662
|
+
let finalMetadataQueued = false;
|
|
1663
|
+
let failedSegmentFallbackPrepared = false;
|
|
1664
|
+
const streamEvents = stream?.events;
|
|
1665
|
+
let streamChunkSubscription;
|
|
1666
|
+
if (typeof streamEvents?.on === "function" && typeof streamEvents.off === "function") streamChunkSubscription = streamEvents.on("chunk", (activity) => {
|
|
1667
|
+
const replacementAcknowledgementPending = replacementTextAwaitingAcknowledgement !== void 0;
|
|
1668
|
+
const replacementAcknowledgement = typeof activity.text === "string" && replacementAcknowledgementPending && activity.text === replacementTextAwaitingAcknowledgement;
|
|
1669
|
+
if (activity.type !== "typing" || activity.channelData?.streamType !== "streaming" || !activity.id || !activity.text || acknowledgedStreamId !== void 0 && activity.id !== acknowledgedStreamId || (replacementAcknowledgementPending ? !replacementAcknowledgement : !activity.text.startsWith(acknowledgedText)) || !emittedText.startsWith(activity.text)) return;
|
|
1670
|
+
acknowledgedStreamId = activity.id;
|
|
1671
|
+
acknowledgedText = activity.text;
|
|
1672
|
+
if (activity.text === replacementTextAwaitingAcknowledgement) replacementTextAwaitingAcknowledgement = void 0;
|
|
1673
|
+
});
|
|
1365
1674
|
const wasCanceled = () => canceledLocally || Boolean(stream?.canceled);
|
|
1675
|
+
const releaseStreamChunkSubscription = () => {
|
|
1676
|
+
if (streamChunkSubscription === void 0) return;
|
|
1677
|
+
streamEvents?.off(streamChunkSubscription);
|
|
1678
|
+
streamChunkSubscription = void 0;
|
|
1679
|
+
};
|
|
1680
|
+
const acknowledgedNativeDelivery = () => {
|
|
1681
|
+
if (!acknowledgedStreamId || !acknowledgedText) return { visibleReplySent: false };
|
|
1682
|
+
return {
|
|
1683
|
+
visibleReplySent: true,
|
|
1684
|
+
content: acknowledgedText,
|
|
1685
|
+
messageId: acknowledgedStreamId
|
|
1686
|
+
};
|
|
1687
|
+
};
|
|
1688
|
+
const fallbackPayloadAfterAcknowledgedText = (payload) => {
|
|
1689
|
+
if (!acknowledgedText || typeof payload.text !== "string" || !payload.text.startsWith(acknowledgedText)) return payload;
|
|
1690
|
+
const remainingText = payload.text.slice(acknowledgedText.length);
|
|
1691
|
+
const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
|
|
1692
|
+
if (!remainingText && !hasMedia) return;
|
|
1693
|
+
return {
|
|
1694
|
+
...payload,
|
|
1695
|
+
text: remainingText || void 0
|
|
1696
|
+
};
|
|
1697
|
+
};
|
|
1366
1698
|
const fallbackPayloadForSuppressedFinal = (payload) => {
|
|
1367
1699
|
return Boolean(payload.mediaUrl || payload.mediaUrls?.length) ? {
|
|
1368
1700
|
...payload,
|
|
@@ -1370,6 +1702,35 @@ function createTeamsReplyStreamController(params) {
|
|
|
1370
1702
|
mediaUrls: void 0
|
|
1371
1703
|
} : payload;
|
|
1372
1704
|
};
|
|
1705
|
+
const finalStreamActivity = (text) => ({
|
|
1706
|
+
type: "message",
|
|
1707
|
+
...text ? { text } : {},
|
|
1708
|
+
entities: [{
|
|
1709
|
+
type: "https://schema.org/Message",
|
|
1710
|
+
"@type": "Message",
|
|
1711
|
+
"@context": "https://schema.org",
|
|
1712
|
+
"@id": "",
|
|
1713
|
+
additionalType: ["AIGeneratedContent"]
|
|
1714
|
+
}],
|
|
1715
|
+
channelData: params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {}
|
|
1716
|
+
});
|
|
1717
|
+
const deferredReplacementLogicalContent = () => {
|
|
1718
|
+
return deferredReplacementEntries.map((entry) => entry.payload.text).filter((text) => Boolean(text)).join("\n") || void 0;
|
|
1719
|
+
};
|
|
1720
|
+
const takeDeferredReplacementPayloads = (replacementFallback) => {
|
|
1721
|
+
const payloads = deferredReplacementEntries.flatMap((entry) => {
|
|
1722
|
+
if (entry.kind === "payload") return [entry.payload];
|
|
1723
|
+
const hasMedia = Boolean(entry.payload.mediaUrl || entry.payload.mediaUrls?.length);
|
|
1724
|
+
const text = replacementFallback?.text;
|
|
1725
|
+
if (!text && !hasMedia) return [];
|
|
1726
|
+
return [{
|
|
1727
|
+
...entry.payload,
|
|
1728
|
+
text: text || void 0
|
|
1729
|
+
}];
|
|
1730
|
+
});
|
|
1731
|
+
deferredReplacementEntries = [];
|
|
1732
|
+
return payloads;
|
|
1733
|
+
};
|
|
1373
1734
|
/**
|
|
1374
1735
|
* Render the current informative status line into the streaming card. Pulls
|
|
1375
1736
|
* the rotating "Thinking..." label from msteams config (or the plugin-sdk
|
|
@@ -1394,14 +1755,19 @@ function createTeamsReplyStreamController(params) {
|
|
|
1394
1755
|
canceledLocally = true;
|
|
1395
1756
|
return;
|
|
1396
1757
|
}
|
|
1397
|
-
params.log?.debug?.(`stream informative update failed: ${
|
|
1758
|
+
params.log?.debug?.(`stream informative update failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
|
|
1398
1759
|
}
|
|
1399
1760
|
};
|
|
1400
1761
|
const progressDraftGate = (0, openclaw_plugin_sdk_channel_outbound.createChannelProgressDraftGate)({ onStart: renderInformativeUpdate });
|
|
1401
1762
|
return {
|
|
1402
1763
|
async onReplyStart() {},
|
|
1403
1764
|
onPartialReply(payload) {
|
|
1404
|
-
if (!stream || !payload.text || wasCanceled() || streamMode !== "partial"
|
|
1765
|
+
if (!stream || !payload.text || wasCanceled() || streamMode !== "partial") return;
|
|
1766
|
+
if (replacementSettlementPending && replacementFinalPending) {
|
|
1767
|
+
pendingFinalPayload = { text: payload.text };
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
if (streamFinalizationPending) return;
|
|
1405
1771
|
const fullText = payload.text;
|
|
1406
1772
|
let prefixLength = 0;
|
|
1407
1773
|
while (prefixLength < emittedText.length && prefixLength < fullText.length && emittedText[prefixLength] === fullText[prefixLength]) prefixLength += 1;
|
|
@@ -1410,7 +1776,13 @@ function createTeamsReplyStreamController(params) {
|
|
|
1410
1776
|
if (!delta) return;
|
|
1411
1777
|
if (previousRemainder.trim()) {
|
|
1412
1778
|
stream.clearText();
|
|
1413
|
-
streamFailed
|
|
1779
|
+
if (streamFailed) {
|
|
1780
|
+
streamFinalizationPending = true;
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
replacementFinalPending = true;
|
|
1784
|
+
replacementSettlementPending = true;
|
|
1785
|
+
pendingFinalPayload = { text: fullText };
|
|
1414
1786
|
streamFinalizationPending = true;
|
|
1415
1787
|
return;
|
|
1416
1788
|
}
|
|
@@ -1418,13 +1790,14 @@ function createTeamsReplyStreamController(params) {
|
|
|
1418
1790
|
stream.emit(delta);
|
|
1419
1791
|
emittedText = fullText;
|
|
1420
1792
|
tokensEmitted = true;
|
|
1793
|
+
nativeDispatchStarted = true;
|
|
1421
1794
|
} catch (err) {
|
|
1422
1795
|
if (isStreamCancelledError(err)) {
|
|
1423
1796
|
canceledLocally = true;
|
|
1424
1797
|
return;
|
|
1425
1798
|
}
|
|
1426
1799
|
streamFailed = true;
|
|
1427
|
-
params.log?.warn?.(`msteams stream emit failed, falling back to block delivery: ${
|
|
1800
|
+
params.log?.warn?.(`msteams stream emit failed, falling back to block delivery: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
|
|
1428
1801
|
}
|
|
1429
1802
|
},
|
|
1430
1803
|
/**
|
|
@@ -1466,6 +1839,49 @@ function createTeamsReplyStreamController(params) {
|
|
|
1466
1839
|
preparePayload(payload) {
|
|
1467
1840
|
if (!stream) return payload;
|
|
1468
1841
|
if (wasCanceled()) return;
|
|
1842
|
+
if (replacementSettlementPending) {
|
|
1843
|
+
if (!replacementFinalPending) {
|
|
1844
|
+
deferredReplacementEntries.push({
|
|
1845
|
+
kind: "payload",
|
|
1846
|
+
payload
|
|
1847
|
+
});
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
if (!payload.text) {
|
|
1851
|
+
deferredReplacementEntries.push({
|
|
1852
|
+
kind: "payload",
|
|
1853
|
+
payload
|
|
1854
|
+
});
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
replacementFinalPending = false;
|
|
1858
|
+
deferredReplacementEntries.push({
|
|
1859
|
+
kind: "replacement",
|
|
1860
|
+
payload
|
|
1861
|
+
});
|
|
1862
|
+
pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
|
|
1863
|
+
try {
|
|
1864
|
+
replacementTextAwaitingAcknowledgement = payload.text;
|
|
1865
|
+
stream.emit(finalStreamActivity(payload.text));
|
|
1866
|
+
finalMetadataQueued = true;
|
|
1867
|
+
emittedText = payload.text;
|
|
1868
|
+
tokensEmitted = false;
|
|
1869
|
+
return;
|
|
1870
|
+
} catch (err) {
|
|
1871
|
+
replacementTextAwaitingAcknowledgement = void 0;
|
|
1872
|
+
tokensEmitted = false;
|
|
1873
|
+
if (isStreamCancelledError(err)) {
|
|
1874
|
+
canceledLocally = true;
|
|
1875
|
+
pendingFinalPayload = void 0;
|
|
1876
|
+
deferredReplacementEntries = [];
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
streamFailed = true;
|
|
1880
|
+
replacementEmitFailed = true;
|
|
1881
|
+
params.log?.warn?.(`msteams stream replacement failed, falling back to block delivery: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1469
1885
|
if (tokensEmitted && !streamFailed) {
|
|
1470
1886
|
const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
|
|
1471
1887
|
pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
|
|
@@ -1476,8 +1892,15 @@ function createTeamsReplyStreamController(params) {
|
|
|
1476
1892
|
text: void 0
|
|
1477
1893
|
} : void 0;
|
|
1478
1894
|
}
|
|
1895
|
+
if (streamFailed) {
|
|
1896
|
+
const fallback = failedSegmentFallbackPrepared ? payload : fallbackPayloadAfterAcknowledgedText(payload);
|
|
1897
|
+
failedSegmentFallbackPrepared = true;
|
|
1898
|
+
pendingFinalPayload = void 0;
|
|
1899
|
+
return fallback;
|
|
1900
|
+
}
|
|
1479
1901
|
if (streamMode === "progress" && payload.text) try {
|
|
1480
1902
|
stream.emit(payload.text);
|
|
1903
|
+
nativeDispatchStarted = true;
|
|
1481
1904
|
pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
|
|
1482
1905
|
streamFinalizationPending = true;
|
|
1483
1906
|
return Boolean(payload.mediaUrl || payload.mediaUrls?.length) ? {
|
|
@@ -1489,49 +1912,104 @@ function createTeamsReplyStreamController(params) {
|
|
|
1489
1912
|
canceledLocally = true;
|
|
1490
1913
|
return;
|
|
1491
1914
|
}
|
|
1492
|
-
params.log?.debug?.(`progress-mode finalize failed: ${
|
|
1915
|
+
params.log?.debug?.(`progress-mode finalize failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
|
|
1493
1916
|
}
|
|
1494
1917
|
return payload;
|
|
1495
1918
|
},
|
|
1919
|
+
claimNativeDelivery() {
|
|
1920
|
+
if (!nativeDispatchStarted || nativeDeliveryClaimed) return false;
|
|
1921
|
+
nativeDeliveryClaimed = true;
|
|
1922
|
+
return true;
|
|
1923
|
+
},
|
|
1496
1924
|
async finalize() {
|
|
1497
1925
|
progressDraftGate.cancel();
|
|
1498
|
-
if (!stream || !
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
"@id": "",
|
|
1504
|
-
additionalType: ["AIGeneratedContent"]
|
|
1505
|
-
}];
|
|
1506
|
-
const finalChannelData = params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {};
|
|
1926
|
+
if (!stream || !nativeDispatchStarted) {
|
|
1927
|
+
releaseStreamChunkSubscription();
|
|
1928
|
+
return { visibleReplySent: false };
|
|
1929
|
+
}
|
|
1930
|
+
let logicalContent;
|
|
1507
1931
|
try {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1932
|
+
if (wasCanceled()) {
|
|
1933
|
+
pendingFinalPayload = void 0;
|
|
1934
|
+
deferredReplacementEntries = [];
|
|
1935
|
+
streamFinalizationPending = false;
|
|
1936
|
+
return acknowledgedNativeDelivery();
|
|
1937
|
+
}
|
|
1938
|
+
if (!streamFinalizationPending) return acknowledgedNativeDelivery();
|
|
1939
|
+
if (replacementSettlementPending && replacementFinalPending && pendingFinalPayload?.text) {
|
|
1940
|
+
replacementFinalPending = false;
|
|
1941
|
+
deferredReplacementEntries.push({
|
|
1942
|
+
kind: "replacement",
|
|
1943
|
+
payload: pendingFinalPayload
|
|
1944
|
+
});
|
|
1945
|
+
replacementTextAwaitingAcknowledgement = pendingFinalPayload.text;
|
|
1946
|
+
logicalContent = deferredReplacementLogicalContent();
|
|
1947
|
+
stream.emit(finalStreamActivity(pendingFinalPayload.text));
|
|
1948
|
+
finalMetadataQueued = true;
|
|
1949
|
+
emittedText = pendingFinalPayload.text;
|
|
1950
|
+
}
|
|
1951
|
+
logicalContent ??= replacementSettlementPending ? deferredReplacementLogicalContent() : void 0;
|
|
1952
|
+
const content = pendingFinalPayload?.text ?? (emittedText || void 0);
|
|
1953
|
+
if (!finalMetadataQueued) stream.emit(finalStreamActivity());
|
|
1513
1954
|
const result = await stream.close();
|
|
1514
1955
|
streamFinalizationPending = false;
|
|
1515
1956
|
if (!result) {
|
|
1516
1957
|
const fallback = pendingFinalPayload;
|
|
1517
1958
|
pendingFinalPayload = void 0;
|
|
1518
|
-
|
|
1959
|
+
const canceled = wasCanceled();
|
|
1960
|
+
const replacementFallback = replacementSettlementPending && fallback && !canceled ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
1961
|
+
const fallbackPayload = !replacementSettlementPending && fallback && !canceled ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
1962
|
+
const postNativePayloads = replacementSettlementPending && !canceled ? takeDeferredReplacementPayloads(replacementFallback) : [];
|
|
1963
|
+
if (canceled) deferredReplacementEntries = [];
|
|
1964
|
+
return {
|
|
1965
|
+
...acknowledgedNativeDelivery(),
|
|
1966
|
+
...!canceled && logicalContent ? { logicalContent } : {},
|
|
1967
|
+
...fallbackPayload ? { fallbackPayload } : {},
|
|
1968
|
+
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
1969
|
+
};
|
|
1519
1970
|
}
|
|
1971
|
+
const replacementFallback = replacementSettlementPending && replacementEmitFailed && pendingFinalPayload ? fallbackPayloadAfterAcknowledgedText(pendingFinalPayload) : void 0;
|
|
1520
1972
|
pendingFinalPayload = void 0;
|
|
1521
|
-
|
|
1973
|
+
const messageId = require_messenger.extractMessageId(result) ?? acknowledgedStreamId;
|
|
1974
|
+
const postNativePayloads = replacementSettlementPending ? takeDeferredReplacementPayloads(replacementFallback) : [];
|
|
1975
|
+
const nativeContent = replacementEmitFailed ? acknowledgedText || void 0 : content;
|
|
1976
|
+
return {
|
|
1977
|
+
visibleReplySent: replacementEmitFailed ? Boolean(nativeContent) : true,
|
|
1978
|
+
...nativeContent === void 0 ? {} : { content: nativeContent },
|
|
1979
|
+
...logicalContent ? { logicalContent } : {},
|
|
1980
|
+
...messageId && (!replacementEmitFailed || nativeContent) ? { messageId } : {},
|
|
1981
|
+
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
1982
|
+
};
|
|
1522
1983
|
} catch (err) {
|
|
1523
1984
|
if (isStreamCancelledError(err)) {
|
|
1524
1985
|
canceledLocally = true;
|
|
1525
1986
|
pendingFinalPayload = void 0;
|
|
1987
|
+
deferredReplacementEntries = [];
|
|
1526
1988
|
streamFinalizationPending = false;
|
|
1527
|
-
return;
|
|
1989
|
+
return acknowledgedNativeDelivery();
|
|
1528
1990
|
}
|
|
1529
1991
|
streamFailed = true;
|
|
1530
1992
|
streamFinalizationPending = false;
|
|
1531
|
-
params.log?.warn?.(`msteams stream finalize failed: ${
|
|
1993
|
+
params.log?.warn?.(`msteams stream finalize failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
|
|
1532
1994
|
const fallback = pendingFinalPayload;
|
|
1533
1995
|
pendingFinalPayload = void 0;
|
|
1534
|
-
|
|
1996
|
+
const replacementFallback = replacementSettlementPending && fallback ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
1997
|
+
const fallbackPayload = !replacementSettlementPending && fallback ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
1998
|
+
const postNativePayloads = replacementSettlementPending ? takeDeferredReplacementPayloads(replacementFallback) : [];
|
|
1999
|
+
return {
|
|
2000
|
+
...acknowledgedNativeDelivery(),
|
|
2001
|
+
...logicalContent ? { logicalContent } : {},
|
|
2002
|
+
...fallbackPayload ? { fallbackPayload } : {},
|
|
2003
|
+
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
2004
|
+
};
|
|
2005
|
+
} finally {
|
|
2006
|
+
finalMetadataQueued = false;
|
|
2007
|
+
replacementEmitFailed = false;
|
|
2008
|
+
replacementFinalPending = false;
|
|
2009
|
+
replacementSettlementPending = false;
|
|
2010
|
+
replacementTextAwaitingAcknowledgement = void 0;
|
|
2011
|
+
deferredReplacementEntries = [];
|
|
2012
|
+
releaseStreamChunkSubscription();
|
|
1535
2013
|
}
|
|
1536
2014
|
},
|
|
1537
2015
|
hasStream() {
|
|
@@ -1616,7 +2094,9 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1616
2094
|
resolveChannelLimitMb: ({ cfg }) => cfg.channels?.msteams?.mediaMaxMb
|
|
1617
2095
|
});
|
|
1618
2096
|
const feedbackLoopEnabled = params.cfg.channels?.msteams?.feedbackEnabled !== false;
|
|
2097
|
+
const hookRunner = (0, openclaw_plugin_sdk_plugin_runtime.getGlobalHookRunner)();
|
|
1619
2098
|
const streamController = createTeamsReplyStreamController({
|
|
2099
|
+
allowProviderPreview: !((hookRunner?.hasHooks("reply_payload_sending") ?? false) || (hookRunner?.hasHooks("message_sending") ?? false)),
|
|
1620
2100
|
conversationType,
|
|
1621
2101
|
context: params.context,
|
|
1622
2102
|
feedbackLoopEnabled,
|
|
@@ -1630,7 +2110,8 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1630
2110
|
const blockStreamingResolved = teamsStreamMode === "block" ? true : (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingBlockEnabled)(msteamsCfg);
|
|
1631
2111
|
const blockStreamingEnabled = blockStreamingResolved ?? false;
|
|
1632
2112
|
const typingIndicatorEnabled = typeof msteamsCfg?.typingIndicator === "boolean" ? msteamsCfg.typingIndicator : true;
|
|
1633
|
-
const
|
|
2113
|
+
const pendingDeliveries = [];
|
|
2114
|
+
const joinAcceptedContents = (contents) => contents.filter((content) => Boolean(content)).join("\n");
|
|
1634
2115
|
const sendMessages = async (messages) => {
|
|
1635
2116
|
return require_messenger.sendMSTeamsMessages({
|
|
1636
2117
|
replyStyle: params.replyStyle,
|
|
@@ -1669,33 +2150,74 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1669
2150
|
contextKey: `msteams:delivery-failure:${params.conversationRef.conversation?.id ?? "unknown"}`
|
|
1670
2151
|
});
|
|
1671
2152
|
};
|
|
1672
|
-
const
|
|
1673
|
-
|
|
2153
|
+
const renderReplyPayload = (payload) => {
|
|
2154
|
+
return require_messenger.renderReplyPayloadsToMessages([payload], {
|
|
1674
2155
|
textChunkLimit: params.textLimit,
|
|
1675
2156
|
chunkText: true,
|
|
1676
2157
|
mediaMode: "split",
|
|
1677
2158
|
tableMode,
|
|
1678
2159
|
chunkMode
|
|
1679
2160
|
});
|
|
1680
|
-
|
|
2161
|
+
};
|
|
2162
|
+
const deliveryOutcome = (delivery) => {
|
|
2163
|
+
const acceptedParts = [...delivery.nativeResult ? [delivery.nativeResult] : [], ...delivery.blockResults];
|
|
2164
|
+
const messageIds = acceptedParts.flatMap((part) => part.messageIds);
|
|
2165
|
+
const content = delivery.errors.length > 0 ? joinAcceptedContents(acceptedParts.map((part) => part.content)) : delivery.content;
|
|
2166
|
+
return {
|
|
2167
|
+
visibleReplySent: acceptedParts.length > 0,
|
|
2168
|
+
...messageIds.length > 0 ? { messageIds } : {},
|
|
2169
|
+
...acceptedParts.length > 0 && content !== void 0 ? { content } : {}
|
|
2170
|
+
};
|
|
2171
|
+
};
|
|
2172
|
+
const settlePendingDelivery = (delivery) => {
|
|
2173
|
+
if (delivery.settled || !delivery.blockSettled || delivery.native && !delivery.nativeSettled) return;
|
|
2174
|
+
delivery.settled = true;
|
|
2175
|
+
const outcome = deliveryOutcome(delivery);
|
|
2176
|
+
if (delivery.errors.length === 0) {
|
|
2177
|
+
delivery.finalization.resolve(outcome);
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
const error = delivery.errors.find((candidate) => !(candidate instanceof openclaw_plugin_sdk_error_runtime.PlatformMessageNotDispatchedError)) ?? delivery.errors[0];
|
|
2181
|
+
delivery.finalization.reject(outcome.visibleReplySent ? (0, openclaw_plugin_sdk_channel_inbound.createChannelPartialDeliveryError)(error, {
|
|
2182
|
+
...outcome,
|
|
2183
|
+
visibleReplySent: true
|
|
2184
|
+
}) : error);
|
|
2185
|
+
};
|
|
2186
|
+
const queueReplyPayload = (payload, messages, native) => {
|
|
2187
|
+
const delivery = {
|
|
2188
|
+
messages,
|
|
2189
|
+
finalization: (0, openclaw_plugin_sdk_extension_shared.createDeferred)(),
|
|
2190
|
+
content: payload.text,
|
|
2191
|
+
blockResults: [],
|
|
2192
|
+
native,
|
|
2193
|
+
nativeSettled: !native,
|
|
2194
|
+
blockSettled: messages.length === 0,
|
|
2195
|
+
settled: false,
|
|
2196
|
+
errors: []
|
|
2197
|
+
};
|
|
2198
|
+
pendingDeliveries.push(delivery);
|
|
2199
|
+
return delivery;
|
|
1681
2200
|
};
|
|
1682
2201
|
const flushPendingMessages = async () => {
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
try {
|
|
1688
|
-
ids = await sendMessages(toSend);
|
|
1689
|
-
} catch (batchError) {
|
|
1690
|
-
ids = [];
|
|
2202
|
+
for (const delivery of pendingDeliveries) {
|
|
2203
|
+
if (delivery.blockSettled) continue;
|
|
2204
|
+
const toSend = delivery.messages.splice(0);
|
|
2205
|
+
const total = toSend.length;
|
|
1691
2206
|
let failed = 0;
|
|
1692
|
-
let lastFailedError
|
|
2207
|
+
let lastFailedError;
|
|
2208
|
+
const sentIds = [];
|
|
1693
2209
|
for (const msg of toSend) try {
|
|
1694
2210
|
const msgIds = await sendMessages([msg]);
|
|
1695
|
-
|
|
2211
|
+
const validIds = msgIds.filter((id) => id.trim() && id !== "unknown");
|
|
2212
|
+
if (msgIds.length > 0) delivery.blockResults.push({
|
|
2213
|
+
messageIds: validIds,
|
|
2214
|
+
...msg.text ? { content: msg.text } : {}
|
|
2215
|
+
});
|
|
2216
|
+
sentIds.push(...validIds);
|
|
1696
2217
|
} catch (msgError) {
|
|
1697
2218
|
failed += 1;
|
|
1698
2219
|
lastFailedError = msgError;
|
|
2220
|
+
delivery.errors.push(msgError);
|
|
1699
2221
|
params.log.debug?.("individual message send failed, continuing with remaining blocks");
|
|
1700
2222
|
}
|
|
1701
2223
|
if (failed > 0) {
|
|
@@ -1709,8 +2231,14 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1709
2231
|
error: lastFailedError
|
|
1710
2232
|
});
|
|
1711
2233
|
}
|
|
2234
|
+
delivery.blockSettled = true;
|
|
2235
|
+
settlePendingDelivery(delivery);
|
|
2236
|
+
if (sentIds.length > 0) try {
|
|
2237
|
+
params.onSentMessageIds?.(sentIds);
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
params.log.warn?.("failed to record sent Teams message ids", { error: require_setup_surface.formatUnknownError(error) });
|
|
2240
|
+
}
|
|
1712
2241
|
}
|
|
1713
|
-
if (ids.length > 0) params.onSentMessageIds?.(ids);
|
|
1714
2242
|
};
|
|
1715
2243
|
const dispatcherOptions = {
|
|
1716
2244
|
...replyPipeline,
|
|
@@ -1722,11 +2250,22 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1722
2250
|
typingCallbacks
|
|
1723
2251
|
};
|
|
1724
2252
|
const delivery = {
|
|
2253
|
+
observeMessageSent: true,
|
|
1725
2254
|
deliver: async (payload) => {
|
|
1726
2255
|
const preparedPayload = streamController.preparePayload(payload);
|
|
1727
|
-
|
|
1728
|
-
|
|
2256
|
+
const native = streamController.claimNativeDelivery();
|
|
2257
|
+
const messages = preparedPayload ? renderReplyPayload(preparedPayload) : [];
|
|
2258
|
+
if (!native && messages.length === 0) return {
|
|
2259
|
+
visibleReplySent: false,
|
|
2260
|
+
suppression: { reason: "no_visible_result" }
|
|
2261
|
+
};
|
|
2262
|
+
const pending = queueReplyPayload(payload, messages, native);
|
|
1729
2263
|
if (blockStreamingEnabled) await flushPendingMessages();
|
|
2264
|
+
settlePendingDelivery(pending);
|
|
2265
|
+
return {
|
|
2266
|
+
visibleReplySent: false,
|
|
2267
|
+
finalization: pending.finalization.promise
|
|
2268
|
+
};
|
|
1730
2269
|
},
|
|
1731
2270
|
onError: (err, info) => {
|
|
1732
2271
|
const errMsg = require_setup_surface.formatUnknownError(err);
|
|
@@ -1741,37 +2280,53 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1741
2280
|
});
|
|
1742
2281
|
}
|
|
1743
2282
|
};
|
|
1744
|
-
const settleDelivery = () => {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
2283
|
+
const settleDelivery = async () => {
|
|
2284
|
+
await flushPendingMessages();
|
|
2285
|
+
const nativeDelivery = pendingDeliveries.find((candidate) => candidate.native && !candidate.nativeSettled);
|
|
2286
|
+
if (!nativeDelivery) {
|
|
2287
|
+
await streamController.finalize();
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
let nativeResult;
|
|
2291
|
+
try {
|
|
2292
|
+
nativeResult = await streamController.finalize();
|
|
2293
|
+
} catch (error) {
|
|
2294
|
+
nativeDelivery.errors.push(error);
|
|
2295
|
+
nativeDelivery.nativeSettled = true;
|
|
2296
|
+
settlePendingDelivery(nativeDelivery);
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
if (nativeResult.visibleReplySent) nativeDelivery.nativeResult = {
|
|
2300
|
+
messageIds: nativeResult.messageId ? [nativeResult.messageId] : [],
|
|
2301
|
+
...nativeResult.content !== void 0 ? { content: nativeResult.content } : {}
|
|
2302
|
+
};
|
|
2303
|
+
const hasPostNativePayloads = Boolean(nativeResult.postNativePayloads?.length);
|
|
2304
|
+
if (nativeResult.logicalContent !== void 0) nativeDelivery.content = nativeResult.logicalContent;
|
|
2305
|
+
else if (nativeResult.content !== void 0 && (!nativeResult.fallbackPayload && !hasPostNativePayloads || nativeDelivery.content === void 0)) nativeDelivery.content = nativeResult.content;
|
|
2306
|
+
const afterNativePayloads = [...nativeResult.fallbackPayload ? [nativeResult.fallbackPayload] : [], ...nativeResult.postNativePayloads ?? []];
|
|
2307
|
+
if (afterNativePayloads.length > 0) {
|
|
2308
|
+
nativeDelivery.messages.push(...afterNativePayloads.flatMap((payload) => renderReplyPayload(payload)));
|
|
2309
|
+
nativeDelivery.blockSettled = nativeDelivery.messages.length === 0;
|
|
2310
|
+
}
|
|
2311
|
+
nativeDelivery.nativeSettled = true;
|
|
2312
|
+
if (!nativeDelivery.blockSettled) await flushPendingMessages();
|
|
2313
|
+
settlePendingDelivery(nativeDelivery);
|
|
2314
|
+
if (nativeResult.messageId) try {
|
|
2315
|
+
params.onSentMessageIds?.([nativeResult.messageId]);
|
|
2316
|
+
} catch (error) {
|
|
2317
|
+
params.log.warn?.("failed to record sent Teams message id", { error: require_setup_surface.formatUnknownError(error) });
|
|
2318
|
+
}
|
|
1764
2319
|
};
|
|
1765
|
-
const previewToolProgressEnabled = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingPreviewToolProgress)(msteamsCfg);
|
|
2320
|
+
const previewToolProgressEnabled = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingPreviewToolProgress)(msteamsCfg, true, teamsStreamMode);
|
|
1766
2321
|
const suppressDefaultToolProgressMessages = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingSuppressDefaultToolProgressMessages)(msteamsCfg);
|
|
1767
|
-
const shouldSuppressDefaultToolProgressMessages = teamsStreamMode === "progress" && suppressDefaultToolProgressMessages && previewToolProgressEnabled;
|
|
2322
|
+
const shouldSuppressDefaultToolProgressMessages = streamController.hasStream() && teamsStreamMode === "progress" && suppressDefaultToolProgressMessages && previewToolProgressEnabled;
|
|
1768
2323
|
const progressCallbacks = streamController.hasStream() ? {
|
|
1769
2324
|
onReasoningStream: async (payload) => {
|
|
1770
2325
|
const text = typeof payload?.text === "string" ? payload.text : void 0;
|
|
1771
|
-
if (!text) return;
|
|
2326
|
+
if (!text) return false;
|
|
1772
2327
|
if (payload?.isReasoningSnapshot !== true) {
|
|
1773
2328
|
await streamController.pushProgressLine(text);
|
|
1774
|
-
return;
|
|
2329
|
+
return false;
|
|
1775
2330
|
}
|
|
1776
2331
|
await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
|
|
1777
2332
|
event: "item",
|
|
@@ -1780,6 +2335,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1780
2335
|
title: "Reasoning",
|
|
1781
2336
|
progressText: text
|
|
1782
2337
|
}));
|
|
2338
|
+
return false;
|
|
1783
2339
|
},
|
|
1784
2340
|
onToolStart: async (payload) => {
|
|
1785
2341
|
const name = typeof payload?.name === "string" ? payload.name : void 0;
|
|
@@ -1792,6 +2348,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1792
2348
|
...typeof payload?.phase === "string" ? { phase: payload.phase } : {},
|
|
1793
2349
|
...payload?.args && typeof payload.args === "object" ? { args: payload.args } : {}
|
|
1794
2350
|
}, detailMode === "explain" || detailMode === "raw" ? { detailMode } : void 0), name ? { toolName: name } : void 0);
|
|
2351
|
+
return false;
|
|
1795
2352
|
},
|
|
1796
2353
|
onItemEvent: async (payload) => {
|
|
1797
2354
|
await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLineForEntry)(msteamsCfg, {
|
|
@@ -1807,13 +2364,15 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1807
2364
|
...typeof payload?.progressText === "string" ? { progressText: payload.progressText } : {},
|
|
1808
2365
|
...typeof payload?.meta === "string" ? { meta: payload.meta } : {}
|
|
1809
2366
|
}));
|
|
2367
|
+
return false;
|
|
1810
2368
|
},
|
|
1811
2369
|
onPlanUpdate: async (payload) => {
|
|
1812
|
-
if (payload?.phase !== "update") return;
|
|
2370
|
+
if (payload?.phase !== "update") return false;
|
|
1813
2371
|
await streamController.pushPlanProgress((0, openclaw_plugin_sdk_channel_outbound.normalizeAgentPlanSteps)(payload.steps), { explanation: typeof payload.explanation === "string" ? payload.explanation : void 0 });
|
|
2372
|
+
return false;
|
|
1814
2373
|
},
|
|
1815
2374
|
onApprovalEvent: async (payload) => {
|
|
1816
|
-
if (payload?.phase !== "requested") return;
|
|
2375
|
+
if (payload?.phase !== "requested") return false;
|
|
1817
2376
|
await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
|
|
1818
2377
|
event: "approval",
|
|
1819
2378
|
phase: payload.phase,
|
|
@@ -1822,10 +2381,11 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1822
2381
|
...typeof payload?.reason === "string" ? { reason: payload.reason } : {},
|
|
1823
2382
|
...typeof payload?.message === "string" ? { message: payload.message } : {}
|
|
1824
2383
|
}));
|
|
2384
|
+
return false;
|
|
1825
2385
|
},
|
|
1826
2386
|
onCommandOutput: async (payload) => {
|
|
1827
|
-
if (payload?.phase !== "end") return;
|
|
1828
|
-
await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.
|
|
2387
|
+
if (payload?.phase !== "end") return false;
|
|
2388
|
+
await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLineForEntry)(msteamsCfg, {
|
|
1829
2389
|
event: "command-output",
|
|
1830
2390
|
...typeof payload?.itemId === "string" ? { itemId: payload.itemId } : {},
|
|
1831
2391
|
...typeof payload?.toolCallId === "string" ? { toolCallId: payload.toolCallId } : {},
|
|
@@ -1835,9 +2395,10 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1835
2395
|
...typeof payload?.status === "string" ? { status: payload.status } : {},
|
|
1836
2396
|
...typeof payload?.exitCode === "number" ? { exitCode: payload.exitCode } : {}
|
|
1837
2397
|
}));
|
|
2398
|
+
return false;
|
|
1838
2399
|
},
|
|
1839
2400
|
onPatchSummary: async (payload) => {
|
|
1840
|
-
if (payload?.phase !== "end") return;
|
|
2401
|
+
if (payload?.phase !== "end") return false;
|
|
1841
2402
|
await streamController.pushProgressLine((0, openclaw_plugin_sdk_channel_outbound.buildChannelProgressDraftLine)({
|
|
1842
2403
|
event: "patch",
|
|
1843
2404
|
...typeof payload?.itemId === "string" ? { itemId: payload.itemId } : {},
|
|
@@ -1850,6 +2411,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1850
2411
|
...Array.isArray(payload?.deleted) && payload.deleted.every((s) => typeof s === "string") ? { deleted: payload.deleted } : {},
|
|
1851
2412
|
...typeof payload?.summary === "string" ? { summary: payload.summary } : {}
|
|
1852
2413
|
}));
|
|
2414
|
+
return false;
|
|
1853
2415
|
}
|
|
1854
2416
|
} : {};
|
|
1855
2417
|
return {
|
|
@@ -1859,7 +2421,10 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1859
2421
|
},
|
|
1860
2422
|
delivery,
|
|
1861
2423
|
replyOptions: {
|
|
1862
|
-
...streamController.hasStream() ? { onPartialReply: (payload) =>
|
|
2424
|
+
...streamController.hasStream() ? { onPartialReply: (payload) => {
|
|
2425
|
+
streamController.onPartialReply(payload);
|
|
2426
|
+
return false;
|
|
2427
|
+
} } : {},
|
|
1863
2428
|
...progressCallbacks,
|
|
1864
2429
|
...shouldSuppressDefaultToolProgressMessages ? { suppressDefaultToolProgressMessages: true } : {},
|
|
1865
2430
|
disableBlockStreaming: blockStreamingResolved == null ? void 0 : !blockStreamingResolved,
|
|
@@ -1877,14 +2442,7 @@ const sentMessages = (0, openclaw_plugin_sdk_dedupe_runtime.createPersistentDedu
|
|
|
1877
2442
|
namespace: "msteams.sent-messages",
|
|
1878
2443
|
maxEntries: 1e3,
|
|
1879
2444
|
openStore: (options) => require_runtime.getOptionalMSTeamsRuntime()?.state.openKeyedStore(options),
|
|
1880
|
-
logError: (
|
|
1881
|
-
try {
|
|
1882
|
-
require_runtime.getOptionalMSTeamsRuntime()?.logging.getChildLogger({
|
|
1883
|
-
plugin: "msteams",
|
|
1884
|
-
feature: "sent-message-state"
|
|
1885
|
-
}).warn("Microsoft Teams persistent sent-message state failed", { error: String(error) });
|
|
1886
|
-
} catch {}
|
|
1887
|
-
},
|
|
2445
|
+
logError: (0, openclaw_plugin_sdk_plugin_state_runtime.createPluginStateErrorReporter)(require_runtime.getOptionalMSTeamsRuntime, "msteams", "sent-message-state", "Microsoft Teams persistent sent-message state failed"),
|
|
1888
2446
|
readTimestamp: (record) => record.sentAt
|
|
1889
2447
|
}
|
|
1890
2448
|
});
|
|
@@ -1901,21 +2459,351 @@ async function wasMSTeamsMessageSentWithPersistence(params) {
|
|
|
1901
2459
|
return await sentMessages.lookup(makeKey(params.conversationId, params.messageId));
|
|
1902
2460
|
}
|
|
1903
2461
|
//#endregion
|
|
1904
|
-
//#region extensions/msteams/src/
|
|
1905
|
-
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
2462
|
+
//#region extensions/msteams/src/monitor-handler/inbound-dispatch.ts
|
|
2463
|
+
async function dispatchMSTeamsInboundTurn(params) {
|
|
2464
|
+
const core = require_runtime.getMSTeamsRuntime();
|
|
2465
|
+
const { cfg, runtime, appId, app, tokenProvider, textLimit, log, logVerboseMessage, facts, admission, content, routing, thread, replyStyle, timestamp, contextVisibilityMode, conversationHistories, historyLimit } = params;
|
|
2466
|
+
const { context, activity, rawBody, text, quoteInfo, conversationRef } = facts;
|
|
2467
|
+
const { senderId, senderName, isDirectMessage, allowNameMatching, groupPolicy, commandAuthorized, effectiveGroupAllowFrom } = admission;
|
|
2468
|
+
const { route } = routing;
|
|
2469
|
+
const { agentBody, inboundMedia } = content;
|
|
2470
|
+
const { teamAadGroupId, quoteBodyFull, quoteSenderId, quoteSenderName, threadContext } = thread;
|
|
2471
|
+
const { conversationId, conversationType, isChannel, teamId, graphChannelId } = facts;
|
|
2472
|
+
const teamsFrom = isDirectMessage ? `msteams:${senderId}` : isChannel ? `msteams:channel:${conversationId}` : `msteams:group:${conversationId}`;
|
|
2473
|
+
const teamsTo = isDirectMessage ? `user:${senderId}` : `conversation:${conversationId}`;
|
|
2474
|
+
const envelopeFrom = isDirectMessage ? senderName : conversationType;
|
|
2475
|
+
const buildEnvelope = (0, openclaw_plugin_sdk_channel_inbound.createChannelInboundEnvelopeBuilder)({
|
|
2476
|
+
cfg,
|
|
2477
|
+
route
|
|
2478
|
+
});
|
|
2479
|
+
let combinedBody = buildEnvelope({
|
|
2480
|
+
channel: "Teams",
|
|
2481
|
+
from: envelopeFrom,
|
|
2482
|
+
timestamp,
|
|
2483
|
+
body: agentBody
|
|
2484
|
+
});
|
|
2485
|
+
const isRoomish = !isDirectMessage;
|
|
2486
|
+
const historyKey = isRoomish ? conversationId : void 0;
|
|
2487
|
+
if (isRoomish && historyKey) combinedBody = (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildPendingContext({
|
|
2488
|
+
historyKey,
|
|
2489
|
+
limit: historyLimit,
|
|
2490
|
+
currentMessage: combinedBody,
|
|
2491
|
+
formatEntry: (entry) => buildEnvelope({
|
|
2492
|
+
channel: "Teams",
|
|
2493
|
+
from: conversationType,
|
|
2494
|
+
timestamp: entry.timestamp,
|
|
2495
|
+
previousTimestamp: null,
|
|
2496
|
+
body: `${entry.sender}: ${entry.body}${entry.messageId ? ` [id:${entry.messageId}]` : ""}`
|
|
2497
|
+
})
|
|
2498
|
+
});
|
|
2499
|
+
const inboundHistory = isRoomish && historyKey && historyLimit > 0 ? (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildInboundHistory({
|
|
2500
|
+
historyKey,
|
|
2501
|
+
limit: historyLimit
|
|
2502
|
+
}) : void 0;
|
|
2503
|
+
const commandBody = text.trim();
|
|
2504
|
+
const quoteSenderAllowed = quoteInfo && quoteInfo.sender ? (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
|
|
2505
|
+
isGroup: !isDirectMessage,
|
|
2506
|
+
groupPolicy,
|
|
2507
|
+
allowFrom: effectiveGroupAllowFrom,
|
|
2508
|
+
isSenderAllowed: (allowFrom) => require_channel.resolveMSTeamsAllowlistMatch({
|
|
2509
|
+
allowFrom,
|
|
2510
|
+
senderId: quoteSenderId ?? "",
|
|
2511
|
+
senderName: quoteSenderName,
|
|
2512
|
+
allowNameMatching
|
|
2513
|
+
}).allowed
|
|
2514
|
+
}) : true;
|
|
2515
|
+
const bodyForAgent = threadContext ? `[Thread history]\n${threadContext}\n[/Thread history]\n\n${agentBody}` : agentBody;
|
|
2516
|
+
const nativeChannelId = isChannel && teamAadGroupId ? `${teamAadGroupId}/${graphChannelId}` : void 0;
|
|
2517
|
+
const boundIngress = await resolveMSTeamsSenderAccess({
|
|
2518
|
+
cfg,
|
|
2519
|
+
activity,
|
|
2520
|
+
hasControlCommand: admission.isControlCommand,
|
|
2521
|
+
conversationThreadId: facts.threadId,
|
|
2522
|
+
contextBinding: {
|
|
2523
|
+
agentId: route.agentId,
|
|
2524
|
+
sessionKey: route.sessionKey,
|
|
2525
|
+
...activity.id ? { messageId: activity.id } : {},
|
|
2526
|
+
...nativeChannelId ? { nativeChannelId } : {},
|
|
2527
|
+
inboundEventKind: "user_request"
|
|
2528
|
+
}
|
|
2529
|
+
});
|
|
2530
|
+
const ctxPayload = core.channel.inbound.buildContext({
|
|
2531
|
+
channelIngress: boundIngress.channelIngress,
|
|
2532
|
+
channel: "msteams",
|
|
2533
|
+
contextVisibility: contextVisibilityMode,
|
|
2534
|
+
supplemental: { quote: quoteInfo ? {
|
|
2535
|
+
id: quoteInfo.id ?? activity.replyToId ?? void 0,
|
|
2536
|
+
body: quoteBodyFull ?? quoteInfo.body,
|
|
2537
|
+
sender: quoteInfo.sender,
|
|
2538
|
+
senderAllowed: quoteSenderAllowed,
|
|
2539
|
+
isQuote: true
|
|
2540
|
+
} : void 0 },
|
|
2541
|
+
media: await (0, openclaw_plugin_sdk_channel_inbound.toInboundMediaFactsWithMetadata)(inboundMedia),
|
|
2542
|
+
messageId: activity.id,
|
|
2543
|
+
timestamp: timestamp?.getTime() ?? Date.now(),
|
|
2544
|
+
from: teamsFrom,
|
|
2545
|
+
sender: {
|
|
2546
|
+
id: senderId,
|
|
2547
|
+
name: senderName
|
|
2548
|
+
},
|
|
2549
|
+
conversation: {
|
|
2550
|
+
kind: isDirectMessage ? "direct" : isChannel ? "channel" : "group",
|
|
2551
|
+
id: conversationId,
|
|
2552
|
+
label: envelopeFrom,
|
|
2553
|
+
spaceId: teamId,
|
|
2554
|
+
nativeChannelId
|
|
2555
|
+
},
|
|
2556
|
+
route: {
|
|
2557
|
+
agentId: route.agentId,
|
|
2558
|
+
dmScope: route.dmScope,
|
|
2559
|
+
accountId: route.accountId,
|
|
2560
|
+
routeSessionKey: route.sessionKey
|
|
2561
|
+
},
|
|
2562
|
+
reply: {
|
|
2563
|
+
to: teamsTo,
|
|
2564
|
+
originatingTo: `conversation:${conversationId}`,
|
|
2565
|
+
messageThreadId: facts.threadId ?? void 0,
|
|
2566
|
+
replyToId: activity.replyToId ?? void 0,
|
|
2567
|
+
nativeChannelId
|
|
2568
|
+
},
|
|
2569
|
+
message: {
|
|
2570
|
+
body: combinedBody,
|
|
2571
|
+
bodyForAgent,
|
|
2572
|
+
inboundHistory,
|
|
2573
|
+
rawBody,
|
|
2574
|
+
commandBody
|
|
2575
|
+
},
|
|
2576
|
+
sessionTranscript: { historyLimit: isRoomish ? historyLimit : 0 },
|
|
2577
|
+
access: {
|
|
2578
|
+
mentions: {
|
|
2579
|
+
canDetectMention: !isDirectMessage,
|
|
2580
|
+
wasMentioned: isDirectMessage || params.mentionWasEffective
|
|
2581
|
+
},
|
|
2582
|
+
commands: { authorized: commandAuthorized === true }
|
|
2583
|
+
},
|
|
2584
|
+
extra: {
|
|
2585
|
+
GroupSubject: !isDirectMessage ? conversationType : void 0,
|
|
2586
|
+
ReplyToIsQuote: quoteInfo ? true : void 0
|
|
2587
|
+
}
|
|
2588
|
+
});
|
|
2589
|
+
const preview = (0, openclaw_plugin_sdk_text_utility_runtime.sliceUtf16Safe)(rawBody.replace(/\s+/g, " "), 0, 160);
|
|
2590
|
+
logVerboseMessage(`msteams inbound: from=${ctxPayload.From} preview="${preview}"`);
|
|
2591
|
+
const { dispatcherOptions, delivery, replyOptions } = createMSTeamsReplyDispatcher({
|
|
2592
|
+
cfg,
|
|
2593
|
+
agentId: route.agentId,
|
|
2594
|
+
sessionKey: route.sessionKey,
|
|
2595
|
+
accountId: route.accountId,
|
|
2596
|
+
runtime,
|
|
2597
|
+
log,
|
|
2598
|
+
app,
|
|
2599
|
+
appId,
|
|
2600
|
+
conversationRef,
|
|
2601
|
+
context,
|
|
2602
|
+
replyStyle,
|
|
2603
|
+
textLimit,
|
|
2604
|
+
onSentMessageIds: (ids) => {
|
|
2605
|
+
for (const id of ids) recordMSTeamsSentMessage(conversationId, id);
|
|
2606
|
+
},
|
|
2607
|
+
tokenProvider,
|
|
2608
|
+
sharePointSiteId: cfg.channels?.msteams?.sharePointSiteId
|
|
2609
|
+
});
|
|
2610
|
+
const senderTimezone = (activity.entities?.find((entity) => entity.type === "clientInfo"))?.timezone || conversationRef.timezone;
|
|
2611
|
+
const turnConfig = senderTimezone && !cfg.agents?.defaults?.userTimezone ? {
|
|
2612
|
+
...cfg,
|
|
2613
|
+
agents: {
|
|
2614
|
+
...cfg.agents,
|
|
2615
|
+
defaults: {
|
|
2616
|
+
...cfg.agents?.defaults,
|
|
2617
|
+
userTimezone: senderTimezone
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
} : cfg;
|
|
2621
|
+
log.info("dispatching to agent", { sessionKey: route.sessionKey });
|
|
2622
|
+
try {
|
|
2623
|
+
const turnResult = await core.channel.inbound.run({
|
|
2624
|
+
channel: "msteams",
|
|
2625
|
+
accountId: route.accountId,
|
|
2626
|
+
raw: context,
|
|
2627
|
+
adapter: {
|
|
2628
|
+
ingest: () => ({
|
|
2629
|
+
id: activity.id ?? `${teamsFrom}:${Date.now()}`,
|
|
2630
|
+
timestamp: timestamp?.getTime(),
|
|
2631
|
+
rawText: rawBody,
|
|
2632
|
+
textForAgent: bodyForAgent,
|
|
2633
|
+
textForCommands: commandBody,
|
|
2634
|
+
raw: activity
|
|
2635
|
+
}),
|
|
2636
|
+
resolveTurn: () => ({
|
|
2637
|
+
cfg: turnConfig,
|
|
2638
|
+
channel: "msteams",
|
|
2639
|
+
accountId: route.accountId,
|
|
2640
|
+
route: {
|
|
2641
|
+
agentId: route.agentId,
|
|
2642
|
+
sessionKey: route.sessionKey
|
|
2643
|
+
},
|
|
2644
|
+
ctxPayload,
|
|
2645
|
+
record: { onRecordError: (err) => {
|
|
2646
|
+
logVerboseMessage(`msteams: failed updating session meta: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2647
|
+
} },
|
|
2648
|
+
history: {
|
|
2649
|
+
isGroup: isRoomish,
|
|
2650
|
+
historyKey,
|
|
2651
|
+
historyMap: conversationHistories,
|
|
2652
|
+
limit: historyLimit
|
|
2653
|
+
},
|
|
2654
|
+
dispatcherOptions,
|
|
2655
|
+
delivery,
|
|
2656
|
+
replyOptions: {
|
|
2657
|
+
...replyOptions,
|
|
2658
|
+
...facts.turnAdoptionLifecycle ? (0, openclaw_plugin_sdk_channel_outbound.bindIngressLifecycleToReplyOptions)(facts.turnAdoptionLifecycle) : {}
|
|
2659
|
+
}
|
|
2660
|
+
})
|
|
2661
|
+
}
|
|
2662
|
+
});
|
|
2663
|
+
const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
|
|
2664
|
+
const counts = (0, openclaw_plugin_sdk_channel_inbound.resolveInboundReplyDispatchCounts)(dispatchResult);
|
|
2665
|
+
log.info("dispatch complete", {
|
|
2666
|
+
queuedFinal: dispatchResult?.queuedFinal ?? false,
|
|
2667
|
+
counts
|
|
2668
|
+
});
|
|
2669
|
+
if ((0, openclaw_plugin_sdk_channel_inbound.hasFinalInboundReplyDispatch)(dispatchResult)) logVerboseMessage(`msteams: delivered ${counts.final} repl${counts.final === 1 ? "y" : "ies"} to ${teamsTo}`);
|
|
2670
|
+
return {
|
|
2671
|
+
kind: "completed",
|
|
2672
|
+
finalResponses: counts.final
|
|
2673
|
+
};
|
|
2674
|
+
} catch (err) {
|
|
2675
|
+
log.error("dispatch failed", { error: require_setup_surface.formatUnknownError(err) });
|
|
2676
|
+
runtime.error(`msteams dispatch failed: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2677
|
+
if (facts.turnAdoptionLifecycle) throw err;
|
|
2678
|
+
try {
|
|
2679
|
+
await context.sendActivity("⚠️ Something went wrong. Please try again.");
|
|
2680
|
+
} catch {}
|
|
2681
|
+
return { kind: "failed" };
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
//#endregion
|
|
2685
|
+
//#region extensions/msteams/src/monitor-handler/inbound-facts.ts
|
|
2686
|
+
function extractTextFromHtmlAttachments(attachments) {
|
|
2687
|
+
for (const attachment of attachments) {
|
|
2688
|
+
const raw = require_resolve_allowlist.extractHtmlFromAttachment(attachment);
|
|
2689
|
+
if (!raw) continue;
|
|
2690
|
+
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();
|
|
2691
|
+
if (text) return text;
|
|
2692
|
+
}
|
|
2693
|
+
return "";
|
|
2694
|
+
}
|
|
2695
|
+
async function prepareMSTeamsDebounceEntry(params) {
|
|
2696
|
+
const activity = params.context.activity;
|
|
2697
|
+
const attachments = Array.isArray(activity.attachments) ? activity.attachments : [];
|
|
2698
|
+
const rawText = activity.text?.trim() ?? "";
|
|
2699
|
+
const htmlText = extractTextFromHtmlAttachments(attachments);
|
|
2700
|
+
const valueText = rawText || htmlText ? "" : serializeMSTeamsAdaptiveCardActionValue(activity.value);
|
|
2701
|
+
const text = require_resolve_allowlist.stripMSTeamsMentionTags(rawText || htmlText || valueText || "");
|
|
2702
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
2703
|
+
const replyToId = activity.replyToId ?? void 0;
|
|
2704
|
+
const implicitMentionKinds = conversationId && replyToId && await wasMSTeamsMessageSentWithPersistence({
|
|
2705
|
+
conversationId,
|
|
2706
|
+
messageId: replyToId
|
|
2707
|
+
}) ? ["reply_to_bot"] : [];
|
|
2708
|
+
return {
|
|
2709
|
+
context: params.context,
|
|
2710
|
+
rawText,
|
|
2711
|
+
text,
|
|
2712
|
+
attachments,
|
|
2713
|
+
wasMentioned: require_resolve_allowlist.wasMSTeamsBotMentioned(activity),
|
|
2714
|
+
implicitMentionKinds,
|
|
2715
|
+
turnAdoptionLifecycle: params.turnAdoptionLifecycle
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
function buildStoredConversationReference(params) {
|
|
2719
|
+
const { activity, conversationId, conversationType, teamId, threadId } = params;
|
|
2720
|
+
const from = activity.from;
|
|
2721
|
+
const conversation = activity.conversation;
|
|
2722
|
+
const clientInfo = activity.entities?.find((entity) => entity.type === "clientInfo");
|
|
2723
|
+
const tenantId = activity.channelData?.tenant?.id ?? conversation?.tenantId;
|
|
2724
|
+
const aadObjectId = from?.aadObjectId;
|
|
2725
|
+
const serviceUrl = require_resolve_allowlist.tryNormalizeBotFrameworkServiceUrl(activity.serviceUrl);
|
|
2726
|
+
return {
|
|
2727
|
+
activityId: activity.id,
|
|
2728
|
+
user: from ? {
|
|
2729
|
+
id: from.id,
|
|
2730
|
+
name: from.name,
|
|
2731
|
+
aadObjectId: from.aadObjectId
|
|
2732
|
+
} : void 0,
|
|
2733
|
+
agent: activity.recipient,
|
|
2734
|
+
conversation: {
|
|
2735
|
+
id: conversationId,
|
|
2736
|
+
conversationType,
|
|
2737
|
+
tenantId
|
|
2738
|
+
},
|
|
2739
|
+
...tenantId ? { tenantId } : {},
|
|
2740
|
+
...aadObjectId ? { aadObjectId } : {},
|
|
2741
|
+
teamId,
|
|
2742
|
+
channelId: activity.channelId,
|
|
2743
|
+
...serviceUrl ? { serviceUrl } : {},
|
|
2744
|
+
locale: activity.locale,
|
|
2745
|
+
...clientInfo?.timezone ? { timezone: clientInfo.timezone } : {},
|
|
2746
|
+
...threadId ? { threadId } : {}
|
|
2747
|
+
};
|
|
2748
|
+
}
|
|
2749
|
+
function assembleMSTeamsInboundFacts(params) {
|
|
2750
|
+
const { entry, mediaMaxBytes } = params;
|
|
2751
|
+
const activity = entry.context.activity;
|
|
2752
|
+
const conversation = activity.conversation;
|
|
2753
|
+
const rawConversationId = conversation?.id ?? "";
|
|
2754
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(rawConversationId);
|
|
2755
|
+
const conversationMessageId = require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId);
|
|
2756
|
+
const conversationType = conversation?.conversationType ?? "personal";
|
|
2757
|
+
const isChannel = conversationType === "channel";
|
|
2758
|
+
const teamId = activity.channelData?.team?.id;
|
|
2759
|
+
const threadId = isChannel ? conversationMessageId ?? activity.replyToId ?? void 0 : void 0;
|
|
2760
|
+
const advertisedMedia = resolveMSTeamsAdvertisedMedia(entry.attachments, {
|
|
2761
|
+
maxInlineBytes: mediaMaxBytes,
|
|
2762
|
+
maxInlineTotalBytes: mediaMaxBytes
|
|
2763
|
+
});
|
|
2764
|
+
return {
|
|
2765
|
+
...entry,
|
|
2766
|
+
activity,
|
|
2767
|
+
from: activity.from,
|
|
2768
|
+
conversation,
|
|
2769
|
+
rawBody: entry.text,
|
|
2770
|
+
advertisedMedia,
|
|
2771
|
+
quoteInfo: require_resolve_allowlist.extractMSTeamsQuoteInfo(entry.attachments),
|
|
2772
|
+
attachmentTypes: entry.attachments.map((attachment) => typeof attachment.contentType === "string" ? attachment.contentType : void 0).filter(Boolean).slice(0, 3),
|
|
2773
|
+
htmlSummary: summarizeMSTeamsHtmlAttachments(entry.attachments),
|
|
2774
|
+
rawConversationId,
|
|
2775
|
+
conversationId,
|
|
2776
|
+
conversationMessageId,
|
|
2777
|
+
conversationType,
|
|
2778
|
+
isChannel,
|
|
2779
|
+
teamId,
|
|
2780
|
+
graphChannelId: activity.channelData?.channel?.id?.trim() || conversationId,
|
|
2781
|
+
threadId,
|
|
2782
|
+
conversationRef: buildStoredConversationReference({
|
|
2783
|
+
activity,
|
|
2784
|
+
conversationId,
|
|
2785
|
+
conversationType,
|
|
2786
|
+
teamId,
|
|
2787
|
+
threadId
|
|
2788
|
+
})
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
//#endregion
|
|
2792
|
+
//#region extensions/msteams/src/team-identity.ts
|
|
2793
|
+
const teamGroupIdCache = /* @__PURE__ */ new Map();
|
|
2794
|
+
const TEAM_GROUP_ID_CACHE_MAX_ENTRIES = 500;
|
|
2795
|
+
function cacheTeamGroupId(conversationTeamId, groupId) {
|
|
2796
|
+
teamGroupIdCache.set(conversationTeamId, groupId);
|
|
2797
|
+
(0, openclaw_plugin_sdk_collection_runtime.pruneMapToMaxSize)(teamGroupIdCache, TEAM_GROUP_ID_CACHE_MAX_ENTRIES);
|
|
2798
|
+
}
|
|
2799
|
+
/** Resolve the Graph team GUID without ever treating a Bot Framework team ID as equivalent. */
|
|
2800
|
+
async function resolveTeamGroupId(params) {
|
|
2801
|
+
const activityGroupId = params.aadGroupId?.trim();
|
|
2802
|
+
if (activityGroupId) {
|
|
2803
|
+
cacheTeamGroupId(params.conversationTeamId, activityGroupId);
|
|
2804
|
+
return activityGroupId;
|
|
2805
|
+
}
|
|
2806
|
+
const cached = teamGroupIdCache.get(params.conversationTeamId);
|
|
1919
2807
|
if (cached) return cached;
|
|
1920
2808
|
const getTeamDetails = params.getTeamDetails;
|
|
1921
2809
|
if (!getTeamDetails) return;
|
|
@@ -1929,210 +2817,229 @@ async function resolveTeamGroupId(params) {
|
|
|
1929
2817
|
return groupId;
|
|
1930
2818
|
}
|
|
1931
2819
|
//#endregion
|
|
1932
|
-
//#region extensions/msteams/src/
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2820
|
+
//#region extensions/msteams/src/thread-parent-context.ts
|
|
2821
|
+
const PARENT_CACHE_TTL_MS = 300 * 1e3;
|
|
2822
|
+
const PARENT_CACHE_MAX = 100;
|
|
2823
|
+
const parentCache = /* @__PURE__ */ new Map();
|
|
2824
|
+
const INJECTED_MAX = 200;
|
|
2825
|
+
const injectedParents = /* @__PURE__ */ new Map();
|
|
2826
|
+
function touchLru(map, key, value, max) {
|
|
2827
|
+
if (map.has(key)) map.delete(key);
|
|
2828
|
+
else if (map.size >= max) {
|
|
2829
|
+
const firstKey = map.keys().next().value;
|
|
2830
|
+
if (firstKey !== void 0) map.delete(firstKey);
|
|
2831
|
+
}
|
|
2832
|
+
map.set(key, value);
|
|
1936
2833
|
}
|
|
1937
|
-
function
|
|
1938
|
-
|
|
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]`
|
|
1943
|
-
});
|
|
2834
|
+
function buildParentCacheKey(groupId, channelId, parentId) {
|
|
2835
|
+
return `${groupId}\u0000${channelId}\u0000${parentId}`;
|
|
1944
2836
|
}
|
|
1945
|
-
function
|
|
1946
|
-
const
|
|
1947
|
-
return
|
|
2837
|
+
function resolveParentCacheExpiresAt(nowRaw) {
|
|
2838
|
+
const nowMs = (0, openclaw_plugin_sdk_number_runtime.asDateTimestampMs)(nowRaw);
|
|
2839
|
+
return nowMs === void 0 ? void 0 : (0, openclaw_plugin_sdk_number_runtime.resolveExpiresAtMsFromDurationMs)(PARENT_CACHE_TTL_MS, { nowMs });
|
|
1948
2840
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
const targetIndex = sourceIndex ?? positionalIndex ?? fallbackIndex;
|
|
1965
|
-
if (targetIndex === void 0) {
|
|
1966
|
-
if (materialized.sourceId) nativeIndexBySourceId.set(materialized.sourceId, merged.length);
|
|
1967
|
-
merged.push(materialized);
|
|
1968
|
-
} else {
|
|
1969
|
-
if (materialized.sourceId) nativeIndexBySourceId.set(materialized.sourceId, targetIndex);
|
|
1970
|
-
const current = merged[targetIndex];
|
|
1971
|
-
if (materialized.path) merged[targetIndex] = materialized;
|
|
1972
|
-
else if (!current?.path) merged[targetIndex] = {
|
|
1973
|
-
...current,
|
|
1974
|
-
...materialized,
|
|
1975
|
-
kind: hasDefinitiveContentType(materialized) || !current?.kind ? materialized.kind : current.kind
|
|
1976
|
-
};
|
|
1977
|
-
}
|
|
2841
|
+
/**
|
|
2842
|
+
* Fetch a channel parent message with an LRU+TTL cache.
|
|
2843
|
+
*
|
|
2844
|
+
* Uses the injected `fetchParent` (defaults to `fetchChannelMessage`) so
|
|
2845
|
+
* tests can swap in a stub without mocking the Graph transport.
|
|
2846
|
+
*/
|
|
2847
|
+
async function fetchParentMessageCached(token, groupId, channelId, parentId, fetchParent = require_messenger.fetchChannelMessage) {
|
|
2848
|
+
const key = buildParentCacheKey(groupId, channelId, parentId);
|
|
2849
|
+
const now = (0, openclaw_plugin_sdk_number_runtime.asDateTimestampMs)(Date.now());
|
|
2850
|
+
const cached = parentCache.get(key);
|
|
2851
|
+
const cachedExpiresAt = cached ? (0, openclaw_plugin_sdk_number_runtime.asDateTimestampMs)(cached.expiresAt) : void 0;
|
|
2852
|
+
if (cached && now !== void 0 && cachedExpiresAt !== void 0 && cachedExpiresAt > now) {
|
|
2853
|
+
parentCache.delete(key);
|
|
2854
|
+
parentCache.set(key, cached);
|
|
2855
|
+
return cached.message;
|
|
1978
2856
|
}
|
|
1979
|
-
|
|
2857
|
+
if (cached) parentCache.delete(key);
|
|
2858
|
+
const message = await fetchParent(token, groupId, channelId, parentId);
|
|
2859
|
+
const expiresAt = resolveParentCacheExpiresAt(Date.now());
|
|
2860
|
+
if (expiresAt !== void 0) touchLru(parentCache, key, {
|
|
2861
|
+
message,
|
|
2862
|
+
expiresAt
|
|
2863
|
+
}, PARENT_CACHE_MAX);
|
|
2864
|
+
return message;
|
|
1980
2865
|
}
|
|
1981
|
-
|
|
1982
|
-
|
|
2866
|
+
const PARENT_TEXT_MAX_CHARS = 400;
|
|
2867
|
+
/**
|
|
2868
|
+
* Extract a compact summary (sender + plain-text body) from a Graph parent
|
|
2869
|
+
* message. Returns undefined when the parent cannot be summarized (missing
|
|
2870
|
+
* or blank body).
|
|
2871
|
+
*/
|
|
2872
|
+
function summarizeParentMessage(message) {
|
|
2873
|
+
if (!message) return;
|
|
2874
|
+
const sender = message.from?.user?.displayName ?? message.from?.application?.displayName ?? "unknown";
|
|
2875
|
+
const contentType = message.body?.contentType ?? "text";
|
|
2876
|
+
const raw = message.body?.content ?? "";
|
|
2877
|
+
const text = contentType === "html" ? require_messenger.stripHtmlFromTeamsMessage(raw) : raw.replace(/\s+/g, " ").trim();
|
|
2878
|
+
if (!text) return;
|
|
2879
|
+
return {
|
|
2880
|
+
sender,
|
|
2881
|
+
text: text.length > PARENT_TEXT_MAX_CHARS ? `${(0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(text, PARENT_TEXT_MAX_CHARS - 1)}…` : text
|
|
2882
|
+
};
|
|
1983
2883
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
deadline: params.deadline
|
|
2020
|
-
});
|
|
2021
|
-
if (bfMedia.media.length > 0) mediaList = mergeMSTeamsMediaFacts(mediaList, bfMedia.media, { positionallyAligned: false });
|
|
2022
|
-
if (!hasMaterializedMedia(bfMedia.media)) log.debug?.("bot framework attachments fetch empty", {
|
|
2023
|
-
conversationType,
|
|
2024
|
-
attachmentCount: bfMedia.attachmentCount ?? attachmentIds.length
|
|
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
|
-
}
|
|
2884
|
+
/**
|
|
2885
|
+
* Build the single-line `Replying to @sender: body` system event text.
|
|
2886
|
+
* Callers should pass this text to `enqueueSystemEvent` together with a
|
|
2887
|
+
* stable contextKey derived from the parent id.
|
|
2888
|
+
*/
|
|
2889
|
+
function formatParentContextEvent(summary) {
|
|
2890
|
+
return `Replying to @${summary.sender}: ${summary.text}`;
|
|
2891
|
+
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Decide whether a parent context event should be enqueued for the current
|
|
2894
|
+
* session. Returns `false` when we already injected the same parent for this
|
|
2895
|
+
* session recently (prevents re-prepending identical context on every reply
|
|
2896
|
+
* in the thread).
|
|
2897
|
+
*/
|
|
2898
|
+
function shouldInjectParentContext(sessionKey, parentId) {
|
|
2899
|
+
const key = sessionKey;
|
|
2900
|
+
return injectedParents.get(key) !== parentId;
|
|
2901
|
+
}
|
|
2902
|
+
/**
|
|
2903
|
+
* Record that `parentId` was just injected for `sessionKey` so subsequent
|
|
2904
|
+
* replies with the same parent can short-circuit via `shouldInjectParentContext`.
|
|
2905
|
+
*/
|
|
2906
|
+
function markParentContextInjected(sessionKey, parentId) {
|
|
2907
|
+
touchLru(injectedParents, sessionKey, parentId, INJECTED_MAX);
|
|
2908
|
+
}
|
|
2909
|
+
//#endregion
|
|
2910
|
+
//#region extensions/msteams/src/monitor-handler/thread-context.ts
|
|
2911
|
+
function prepareMSTeamsThreadRouting(params) {
|
|
2912
|
+
const route = require_runtime.getMSTeamsRuntime().channel.routing.resolveAgentRoute({
|
|
2913
|
+
cfg: params.cfg,
|
|
2914
|
+
channel: "msteams",
|
|
2915
|
+
teamId: params.teamId,
|
|
2916
|
+
peer: {
|
|
2917
|
+
kind: params.isDirectMessage ? "direct" : params.isChannel ? "channel" : "group",
|
|
2918
|
+
id: params.isDirectMessage ? params.senderId : params.conversationId
|
|
2065
2919
|
}
|
|
2066
|
-
}
|
|
2067
|
-
const materializedCount = mediaList.filter((media) => Boolean(media.path)).length;
|
|
2068
|
-
if (materializedCount > 0) log.debug?.("downloaded attachments", { count: materializedCount });
|
|
2069
|
-
else if (htmlSummary?.imgTags) log.debug?.("inline images detected but none downloaded", {
|
|
2070
|
-
imgTags: htmlSummary.imgTags,
|
|
2071
|
-
srcHosts: htmlSummary.srcHosts,
|
|
2072
|
-
dataImages: htmlSummary.dataImages,
|
|
2073
|
-
cidImages: htmlSummary.cidImages
|
|
2074
2920
|
});
|
|
2075
|
-
|
|
2921
|
+
route.sessionKey = require_channel.resolveMSTeamsRouteSessionKey({
|
|
2922
|
+
baseSessionKey: route.sessionKey,
|
|
2923
|
+
isChannel: params.isChannel,
|
|
2924
|
+
conversationMessageId: params.conversationMessageId,
|
|
2925
|
+
replyToId: params.context.activity.replyToId
|
|
2926
|
+
});
|
|
2927
|
+
const deadline = require_resolve_allowlist.createMSTeamsInboundDeadline();
|
|
2928
|
+
let teamAadGroupId = params.context.activity.channelData?.team?.aadGroupId?.trim() || void 0;
|
|
2929
|
+
const conversationTeamId = params.isChannel ? params.teamId : void 0;
|
|
2930
|
+
let teamGroupIdPromise;
|
|
2931
|
+
const resolveTeamAadGroupId = async () => {
|
|
2932
|
+
if (!conversationTeamId) return;
|
|
2933
|
+
teamGroupIdPromise ??= resolveTeamGroupId({
|
|
2934
|
+
conversationTeamId,
|
|
2935
|
+
aadGroupId: teamAadGroupId,
|
|
2936
|
+
getTeamDetails: params.context.getTeamDetails,
|
|
2937
|
+
deadline
|
|
2938
|
+
}).catch((err) => {
|
|
2939
|
+
params.log.debug?.("failed to resolve Teams AAD group ID", {
|
|
2940
|
+
teamId: conversationTeamId,
|
|
2941
|
+
error: require_setup_surface.formatUnknownError(err)
|
|
2942
|
+
});
|
|
2943
|
+
});
|
|
2944
|
+
teamAadGroupId = await teamGroupIdPromise;
|
|
2945
|
+
return teamAadGroupId;
|
|
2946
|
+
};
|
|
2947
|
+
return {
|
|
2948
|
+
route,
|
|
2949
|
+
deadline,
|
|
2950
|
+
resolveTeamAadGroupId,
|
|
2951
|
+
getTeamAadGroupId: () => teamAadGroupId
|
|
2952
|
+
};
|
|
2076
2953
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2954
|
+
async function resolveMSTeamsThreadContext(params) {
|
|
2955
|
+
const core = require_runtime.getMSTeamsRuntime();
|
|
2956
|
+
const activity = params.context.activity;
|
|
2957
|
+
const { route, deadline } = params.routing;
|
|
2958
|
+
const teamAadGroupId = await params.routing.resolveTeamAadGroupId();
|
|
2959
|
+
let quoteBodyFull;
|
|
2960
|
+
let quoteSenderId;
|
|
2961
|
+
let quoteSenderName;
|
|
2962
|
+
const quoteMessageId = params.quoteInfo?.id;
|
|
2963
|
+
if (quoteMessageId && params.isDirectMessage && params.conversationId.startsWith("19:")) try {
|
|
2964
|
+
const graphToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2965
|
+
deadline,
|
|
2966
|
+
label: "MS Teams quote token",
|
|
2967
|
+
work: () => params.tokenProvider.getAccessToken("https://graph.microsoft.com")
|
|
2968
|
+
});
|
|
2969
|
+
quoteBodyFull = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2970
|
+
deadline,
|
|
2971
|
+
label: "MS Teams quote lookup",
|
|
2972
|
+
work: () => require_messenger.fetchChatMessageText(graphToken, params.conversationId, quoteMessageId, deadline)
|
|
2973
|
+
});
|
|
2974
|
+
} catch (err) {
|
|
2975
|
+
params.log.debug?.("failed to fetch full quoted message text", { error: require_setup_surface.formatUnknownError(err) });
|
|
2085
2976
|
}
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2977
|
+
let threadContext;
|
|
2978
|
+
const threadParentId = activity.replyToId;
|
|
2979
|
+
if (threadParentId && params.isChannel && teamAadGroupId) try {
|
|
2980
|
+
const graphToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2981
|
+
deadline,
|
|
2982
|
+
label: "MS Teams thread token",
|
|
2983
|
+
work: () => params.tokenProvider.getAccessToken("https://graph.microsoft.com")
|
|
2984
|
+
});
|
|
2985
|
+
const [parentResult, repliesResult] = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2986
|
+
deadline,
|
|
2987
|
+
label: "MS Teams thread history",
|
|
2988
|
+
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)])
|
|
2989
|
+
});
|
|
2990
|
+
const parentMsg = parentResult.status === "fulfilled" ? parentResult.value : void 0;
|
|
2991
|
+
const replies = repliesResult.status === "fulfilled" ? repliesResult.value : [];
|
|
2992
|
+
if (parentResult.status === "rejected") params.log.debug?.("failed to fetch parent message", { error: require_setup_surface.formatUnknownError(parentResult.reason) });
|
|
2993
|
+
if (repliesResult.status === "rejected") params.log.debug?.("failed to fetch thread replies", { error: require_setup_surface.formatUnknownError(repliesResult.reason) });
|
|
2994
|
+
const isThreadSenderAllowed = (message) => (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
|
|
2995
|
+
isGroup: params.isChannel,
|
|
2996
|
+
groupPolicy: params.groupPolicy,
|
|
2997
|
+
allowFrom: params.effectiveGroupAllowFrom,
|
|
2998
|
+
isSenderAllowed: (allowFrom) => require_channel.resolveMSTeamsAllowlistMatch({
|
|
2999
|
+
allowFrom,
|
|
3000
|
+
senderId: message.from?.user?.id ?? "",
|
|
3001
|
+
senderName: message.from?.user?.displayName,
|
|
3002
|
+
allowNameMatching: params.allowNameMatching
|
|
3003
|
+
}).allowed
|
|
3004
|
+
});
|
|
3005
|
+
const parentSummary = summarizeParentMessage(parentMsg);
|
|
3006
|
+
const visibleParentMessages = parentMsg ? (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
|
|
3007
|
+
items: [parentMsg],
|
|
3008
|
+
mode: params.contextVisibilityMode,
|
|
3009
|
+
kind: "thread",
|
|
3010
|
+
isSenderAllowed: isThreadSenderAllowed
|
|
3011
|
+
}).items : [];
|
|
3012
|
+
if (parentSummary && visibleParentMessages.length > 0 && shouldInjectParentContext(route.sessionKey, threadParentId)) {
|
|
3013
|
+
core.system.enqueueSystemEvent(formatParentContextEvent(parentSummary), {
|
|
3014
|
+
sessionKey: route.sessionKey,
|
|
3015
|
+
contextKey: `msteams:thread-parent:${params.conversationId}:${threadParentId}`
|
|
3016
|
+
});
|
|
3017
|
+
markParentContextInjected(route.sessionKey, threadParentId);
|
|
3018
|
+
}
|
|
3019
|
+
const allMessages = parentMsg ? [parentMsg, ...replies] : replies;
|
|
3020
|
+
quoteSenderId = parentMsg?.from?.user?.id ?? parentMsg?.from?.application?.id ?? void 0;
|
|
3021
|
+
quoteSenderName = parentMsg?.from?.user?.displayName ?? parentMsg?.from?.application?.displayName ?? params.quoteInfo?.sender;
|
|
3022
|
+
const { items: threadMessages } = (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
|
|
3023
|
+
items: allMessages,
|
|
3024
|
+
mode: params.contextVisibilityMode,
|
|
3025
|
+
kind: "thread",
|
|
3026
|
+
isSenderAllowed: isThreadSenderAllowed
|
|
3027
|
+
});
|
|
3028
|
+
threadContext = require_messenger.formatThreadContext(threadMessages, activity.id) || void 0;
|
|
3029
|
+
} catch (err) {
|
|
3030
|
+
params.log.debug?.("failed to fetch thread history", { error: require_setup_surface.formatUnknownError(err) });
|
|
2102
3031
|
}
|
|
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);
|
|
3032
|
+
quoteSenderName ??= params.quoteInfo?.sender;
|
|
2113
3033
|
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 } : {}
|
|
3034
|
+
teamAadGroupId,
|
|
3035
|
+
quoteBodyFull,
|
|
3036
|
+
quoteSenderId,
|
|
3037
|
+
quoteSenderName,
|
|
3038
|
+
threadContext
|
|
2134
3039
|
};
|
|
2135
3040
|
}
|
|
3041
|
+
//#endregion
|
|
3042
|
+
//#region extensions/msteams/src/monitor-handler/message-handler.ts
|
|
2136
3043
|
function createMSTeamsMessageHandler(deps) {
|
|
2137
3044
|
const { cfg, runtime, appId, app, tokenProvider, textLimit, mediaMaxBytes, conversationStore, pollStore, log } = deps;
|
|
2138
3045
|
const core = require_runtime.getMSTeamsRuntime();
|
|
@@ -2151,24 +3058,12 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2151
3058
|
channel: "msteams"
|
|
2152
3059
|
});
|
|
2153
3060
|
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;
|
|
3061
|
+
const facts = assembleMSTeamsInboundFacts({
|
|
3062
|
+
entry: params,
|
|
3063
|
+
mediaMaxBytes
|
|
3064
|
+
});
|
|
3065
|
+
const { context, activity, rawText, text, attachments, advertisedMedia, rawBody, quoteInfo, from, conversation, attachmentTypes, htmlSummary, conversationId, conversationMessageId, conversationType, isChannel, teamId, conversationRef } = facts;
|
|
2164
3066
|
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
3067
|
log.info("received message", {
|
|
2173
3068
|
rawText: (0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(rawText, 50),
|
|
2174
3069
|
text: (0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(text, 50),
|
|
@@ -2182,138 +3077,19 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2182
3077
|
log.debug?.("skipping message without from.id");
|
|
2183
3078
|
return;
|
|
2184
3079
|
}
|
|
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({
|
|
3080
|
+
const admission = await admitMSTeamsMessage({
|
|
2204
3081
|
cfg,
|
|
2205
3082
|
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) });
|
|
3083
|
+
text,
|
|
3084
|
+
conversationId,
|
|
3085
|
+
conversationRef,
|
|
3086
|
+
isChannel,
|
|
3087
|
+
conversationStore,
|
|
3088
|
+
log,
|
|
3089
|
+
logVerboseMessage
|
|
2316
3090
|
});
|
|
3091
|
+
if (!admission) return;
|
|
3092
|
+
const { senderId, senderName, isDirectMessage, channelGate, allowNameMatching, groupPolicy, commandAuthorized, effectiveGroupAllowFrom, allowTextCommands, isControlCommand } = admission;
|
|
2317
3093
|
const pollVote = require_polls.extractMSTeamsPollVote(activity);
|
|
2318
3094
|
if (pollVote) {
|
|
2319
3095
|
try {
|
|
@@ -2335,33 +3111,18 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2335
3111
|
}
|
|
2336
3112
|
return;
|
|
2337
3113
|
}
|
|
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({
|
|
3114
|
+
const threadRouting = prepareMSTeamsThreadRouting({
|
|
2350
3115
|
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,
|
|
3116
|
+
context,
|
|
3117
|
+
isDirectMessage,
|
|
2360
3118
|
isChannel,
|
|
2361
|
-
|
|
2362
|
-
|
|
3119
|
+
senderId,
|
|
3120
|
+
conversationId,
|
|
3121
|
+
conversationMessageId: conversationMessageId ?? void 0,
|
|
3122
|
+
teamId,
|
|
3123
|
+
log
|
|
2363
3124
|
});
|
|
2364
|
-
const
|
|
3125
|
+
const { route, deadline: preprocessingDeadline } = threadRouting;
|
|
2365
3126
|
const inboundLabel = isDirectMessage ? `Teams DM from ${senderName}` : `Teams message in ${conversationType} from ${senderName}`;
|
|
2366
3127
|
const enqueuePrimaryMessageSystemEvent = () => core.system.enqueueSystemEvent(inboundLabel, {
|
|
2367
3128
|
sessionKey: route.sessionKey,
|
|
@@ -2375,7 +3136,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2375
3136
|
teamConfig,
|
|
2376
3137
|
channelConfig
|
|
2377
3138
|
});
|
|
2378
|
-
const timestamp =
|
|
3139
|
+
const timestamp = require_resolve_allowlist.parseMSTeamsActivityTimestamp(activity.timestamp);
|
|
2379
3140
|
const mentionDecision = (0, openclaw_plugin_sdk_channel_inbound.resolveInboundMentionDecision)({
|
|
2380
3141
|
facts: {
|
|
2381
3142
|
canDetectMention: true,
|
|
@@ -2415,344 +3176,66 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2415
3176
|
return;
|
|
2416
3177
|
}
|
|
2417
3178
|
}
|
|
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
|
-
})
|
|
3179
|
+
const content = await prepareMSTeamsInboundContent({
|
|
3180
|
+
entry: params,
|
|
3181
|
+
rawBody,
|
|
3182
|
+
advertisedMedia,
|
|
3183
|
+
htmlSummary: htmlSummary ?? void 0,
|
|
3184
|
+
conversationType,
|
|
3185
|
+
conversationId,
|
|
3186
|
+
conversationMessageId: conversationMessageId ?? void 0,
|
|
3187
|
+
teamAadGroupId: threadRouting.getTeamAadGroupId(),
|
|
3188
|
+
resolveTeamAadGroupId: threadRouting.resolveTeamAadGroupId,
|
|
3189
|
+
mediaMaxBytes,
|
|
3190
|
+
tokenProvider,
|
|
3191
|
+
mediaAllowHosts: msteamsCfg?.mediaAllowHosts,
|
|
3192
|
+
mediaAuthAllowHosts: msteamsCfg?.mediaAuthAllowHosts,
|
|
3193
|
+
graphMediaFallback: msteamsCfg?.graphMediaFallback,
|
|
3194
|
+
deadline: preprocessingDeadline,
|
|
3195
|
+
log
|
|
2583
3196
|
});
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
3197
|
+
if (!content) return;
|
|
3198
|
+
enqueuePrimaryMessageSystemEvent();
|
|
3199
|
+
const thread = await resolveMSTeamsThreadContext({
|
|
3200
|
+
routing: threadRouting,
|
|
3201
|
+
context,
|
|
3202
|
+
tokenProvider,
|
|
3203
|
+
quoteInfo,
|
|
3204
|
+
isDirectMessage,
|
|
3205
|
+
isChannel,
|
|
3206
|
+
conversationId,
|
|
3207
|
+
contextVisibilityMode,
|
|
2591
3208
|
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
|
-
}
|
|
3209
|
+
effectiveGroupAllowFrom,
|
|
3210
|
+
allowNameMatching,
|
|
3211
|
+
log
|
|
2657
3212
|
});
|
|
2658
|
-
|
|
2659
|
-
const sharePointSiteId = msteamsCfg?.sharePointSiteId;
|
|
2660
|
-
const { dispatcherOptions, delivery, replyOptions } = createMSTeamsReplyDispatcher({
|
|
3213
|
+
await dispatchMSTeamsInboundTurn({
|
|
2661
3214
|
cfg,
|
|
2662
|
-
agentId: route.agentId,
|
|
2663
|
-
sessionKey: route.sessionKey,
|
|
2664
|
-
accountId: route.accountId,
|
|
2665
3215
|
runtime,
|
|
2666
|
-
log,
|
|
2667
|
-
app,
|
|
2668
3216
|
appId,
|
|
2669
|
-
|
|
2670
|
-
context,
|
|
2671
|
-
replyStyle,
|
|
2672
|
-
textLimit,
|
|
2673
|
-
onSentMessageIds: (ids) => {
|
|
2674
|
-
for (const id of ids) recordMSTeamsSentMessage(conversationId, id);
|
|
2675
|
-
},
|
|
3217
|
+
app,
|
|
2676
3218
|
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
|
-
}
|
|
3219
|
+
textLimit,
|
|
3220
|
+
log,
|
|
3221
|
+
logVerboseMessage,
|
|
3222
|
+
facts,
|
|
3223
|
+
admission,
|
|
3224
|
+
content,
|
|
3225
|
+
routing: threadRouting,
|
|
3226
|
+
thread,
|
|
3227
|
+
replyStyle,
|
|
3228
|
+
timestamp,
|
|
3229
|
+
contextVisibilityMode,
|
|
3230
|
+
mentionWasEffective: mentionDecision.effectiveWasMentioned,
|
|
3231
|
+
conversationHistories,
|
|
3232
|
+
historyLimit
|
|
3233
|
+
});
|
|
2751
3234
|
};
|
|
2752
3235
|
const inboundDebouncer = core.channel.debounce.createInboundDebouncer({
|
|
2753
3236
|
debounceMs: inboundDebounceMs,
|
|
2754
3237
|
buildKey: (entry) => {
|
|
2755
|
-
const conversationId =
|
|
3238
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(entry.context.activity.conversation?.id ?? "");
|
|
2756
3239
|
const senderId = entry.context.activity.from?.aadObjectId ?? entry.context.activity.from?.id ?? "";
|
|
2757
3240
|
if (!senderId || !conversationId) return null;
|
|
2758
3241
|
return `msteams:${appId}:${conversationId}:${senderId}`;
|
|
@@ -2762,65 +3245,53 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2762
3245
|
if (entry.attachments.length > 0) return false;
|
|
2763
3246
|
return !core.channel.commands.isControlCommandMessage(entry.text, cfg);
|
|
2764
3247
|
},
|
|
2765
|
-
onFlush:
|
|
3248
|
+
onFlush: (entries, createFlush) => {
|
|
2766
3249
|
const last = entries.at(-1);
|
|
2767
|
-
if (!last) return;
|
|
2768
3250
|
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 } : {}
|
|
3251
|
+
return createFlush({
|
|
3252
|
+
lifecycle,
|
|
3253
|
+
dispatch: async (admissionLifecycle) => {
|
|
3254
|
+
if (!last) return;
|
|
3255
|
+
try {
|
|
3256
|
+
if (entries.length === 1) await handleTeamsMessageNow({
|
|
3257
|
+
...last,
|
|
3258
|
+
turnAdoptionLifecycle: admissionLifecycle
|
|
2788
3259
|
});
|
|
3260
|
+
else {
|
|
3261
|
+
const combinedText = entries.map((entry) => entry.text).filter(Boolean).join("\n");
|
|
3262
|
+
if (combinedText.trim()) {
|
|
3263
|
+
const combinedRawText = entries.map((entry) => entry.rawText).filter(Boolean).join("\n");
|
|
3264
|
+
const wasMentioned = entries.some((entry) => entry.wasMentioned);
|
|
3265
|
+
const implicitMentionKinds = entries.flatMap((entry) => entry.implicitMentionKinds);
|
|
3266
|
+
await handleTeamsMessageNow({
|
|
3267
|
+
context: last.context,
|
|
3268
|
+
rawText: combinedRawText,
|
|
3269
|
+
text: combinedText,
|
|
3270
|
+
attachments: [],
|
|
3271
|
+
wasMentioned,
|
|
3272
|
+
implicitMentionKinds,
|
|
3273
|
+
turnAdoptionLifecycle: admissionLifecycle
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
await settle();
|
|
3278
|
+
} catch (err) {
|
|
3279
|
+
await admissionLifecycle.onAbandoned();
|
|
3280
|
+
throw err;
|
|
2789
3281
|
}
|
|
2790
3282
|
}
|
|
2791
|
-
|
|
2792
|
-
} catch (err) {
|
|
2793
|
-
await lifecycle?.onAbandoned();
|
|
2794
|
-
throw err;
|
|
2795
|
-
}
|
|
3283
|
+
});
|
|
2796
3284
|
},
|
|
2797
3285
|
onError: (err) => {
|
|
2798
3286
|
runtime.error(`msteams debounce flush failed: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2799
3287
|
}
|
|
2800
3288
|
});
|
|
2801
3289
|
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({
|
|
3290
|
+
const entry = await prepareMSTeamsDebounceEntry({
|
|
2816
3291
|
context,
|
|
2817
|
-
rawText,
|
|
2818
|
-
text,
|
|
2819
|
-
attachments,
|
|
2820
|
-
wasMentioned,
|
|
2821
|
-
implicitMentionKinds,
|
|
2822
3292
|
turnAdoptionLifecycle
|
|
2823
3293
|
});
|
|
3294
|
+
await inboundDebouncer.enqueue(entry);
|
|
2824
3295
|
if (turnAdoptionLifecycle) return { kind: "deferred" };
|
|
2825
3296
|
};
|
|
2826
3297
|
}
|
|
@@ -2846,7 +3317,7 @@ function createMSTeamsReactionHandler(deps) {
|
|
|
2846
3317
|
log.debug?.("reaction activity missing from.id; skipping");
|
|
2847
3318
|
return;
|
|
2848
3319
|
}
|
|
2849
|
-
const conversationId =
|
|
3320
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
2850
3321
|
const conversationType = activity.conversation?.conversationType ?? "personal";
|
|
2851
3322
|
const isGroupChat = conversationType === "groupChat" || activity.conversation?.isGroup === true;
|
|
2852
3323
|
const isChannel = conversationType === "channel";
|
|
@@ -3142,7 +3613,7 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
|
|
|
3142
3613
|
userComment = JSON.parse(value.actionValue.feedback).feedbackText || void 0;
|
|
3143
3614
|
} catch {}
|
|
3144
3615
|
const rawConversationId = activity.conversation?.id ?? "unknown";
|
|
3145
|
-
const conversationId =
|
|
3616
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(rawConversationId);
|
|
3146
3617
|
const senderId = activity.from?.aadObjectId ?? activity.from?.id ?? "unknown";
|
|
3147
3618
|
const messageId = value.replyToId ?? activity.replyToId ?? "unknown";
|
|
3148
3619
|
const isNegative = reaction === "dislike";
|
|
@@ -3157,7 +3628,7 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
|
|
|
3157
3628
|
id: isDirectMessage ? senderId : conversationId
|
|
3158
3629
|
}
|
|
3159
3630
|
});
|
|
3160
|
-
const feedbackThreadId = isChannel ?
|
|
3631
|
+
const feedbackThreadId = isChannel ? require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId) ?? activity.replyToId ?? void 0 : void 0;
|
|
3161
3632
|
if (feedbackThreadId) route.sessionKey = (0, openclaw_plugin_sdk_routing.resolveThreadSessionKeys)({
|
|
3162
3633
|
baseSessionKey: route.sessionKey,
|
|
3163
3634
|
threadId: feedbackThreadId,
|
|
@@ -3239,8 +3710,8 @@ async function handleMSTeamsFileConsentInvoke(context, log) {
|
|
|
3239
3710
|
const fsFile = inMemoryFile ? void 0 : await require_messenger.getPendingUploadFs(uploadId);
|
|
3240
3711
|
const pendingFile = inMemoryFile ?? fsFile;
|
|
3241
3712
|
if (pendingFile) {
|
|
3242
|
-
const pendingConversationId =
|
|
3243
|
-
const invokeConversationId =
|
|
3713
|
+
const pendingConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(pendingFile.conversationId);
|
|
3714
|
+
const invokeConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
3244
3715
|
if (!invokeConversationId || pendingConversationId !== invokeConversationId) {
|
|
3245
3716
|
log.info("file consent conversation mismatch", {
|
|
3246
3717
|
uploadId,
|
|
@@ -3330,32 +3801,38 @@ async function runMSTeamsFileConsentInvokeHandler(context, log) {
|
|
|
3330
3801
|
}
|
|
3331
3802
|
}
|
|
3332
3803
|
//#endregion
|
|
3804
|
+
//#region extensions/msteams/src/monitor-status.ts
|
|
3805
|
+
function publishMSTeamsBlocked(statusSink, lastError) {
|
|
3806
|
+
statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelBlockedPatch)(lastError, { running: true }));
|
|
3807
|
+
}
|
|
3808
|
+
function publishMSTeamsReady(statusSink, now = Date.now()) {
|
|
3809
|
+
statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelReadyPatch)({ lastConnectedAt: now }));
|
|
3810
|
+
}
|
|
3811
|
+
function publishMSTeamsRecovering(statusSink, lastError) {
|
|
3812
|
+
statusSink?.({
|
|
3813
|
+
connected: false,
|
|
3814
|
+
lifecycle: "recovering",
|
|
3815
|
+
lastError
|
|
3816
|
+
});
|
|
3817
|
+
}
|
|
3818
|
+
function publishMSTeamsStopped(statusSink) {
|
|
3819
|
+
statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelStoppedPatch)());
|
|
3820
|
+
}
|
|
3821
|
+
//#endregion
|
|
3333
3822
|
//#region extensions/msteams/src/msteams-ingress.ts
|
|
3334
3823
|
const MSTEAMS_INGRESS_VERSION = 1;
|
|
3335
3824
|
const MSTEAMS_INGRESS_DRAIN_INTERVAL_MS = 500;
|
|
3336
3825
|
const MSTEAMS_INGRESS_MAX_CONCURRENT_DELIVERIES = 8;
|
|
3337
3826
|
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
|
-
}
|
|
3827
|
+
const MSTeamsIngressPayloadError = (0, openclaw_plugin_sdk_channel_outbound.createChannelIngressError)("MSTeamsIngressPayloadError", { withReason: true });
|
|
3351
3828
|
function isDispatchableActivity(activity) {
|
|
3352
3829
|
return activity.type === "message" || activity.type === "invoke" && activity.name === "adaptiveCard/action";
|
|
3353
3830
|
}
|
|
3354
3831
|
function inspectMSTeamsIngressActivity(activity) {
|
|
3355
3832
|
if (!isDispatchableActivity(activity)) return null;
|
|
3356
|
-
const eventId =
|
|
3833
|
+
const eventId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeNullableString)(activity.id);
|
|
3357
3834
|
if (!eventId) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams dispatchable activity is missing activity.id.");
|
|
3358
|
-
const laneKey =
|
|
3835
|
+
const laneKey = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeNullableString)(activity.conversation?.id);
|
|
3359
3836
|
if (!laneKey) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams dispatchable activity is missing conversation.id.");
|
|
3360
3837
|
return {
|
|
3361
3838
|
eventId,
|
|
@@ -3377,9 +3854,6 @@ function parseClaimedActivity(payload, claimedId) {
|
|
|
3377
3854
|
if (facts.eventId !== claimedId) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams activity id changed after durable admission.");
|
|
3378
3855
|
return parsed;
|
|
3379
3856
|
}
|
|
3380
|
-
function errorText(error) {
|
|
3381
|
-
return error instanceof Error ? error.message : String(error);
|
|
3382
|
-
}
|
|
3383
3857
|
function createMSTeamsIngress(options) {
|
|
3384
3858
|
const queue = options.queue ?? require_runtime.getMSTeamsRuntime().state.openChannelIngressQueue({ accountId: options.accountId });
|
|
3385
3859
|
const liveContexts = /* @__PURE__ */ new Map();
|
|
@@ -3414,10 +3888,7 @@ function createMSTeamsIngress(options) {
|
|
|
3414
3888
|
pollIntervalMs: MSTEAMS_INGRESS_DRAIN_INTERVAL_MS,
|
|
3415
3889
|
retention: {
|
|
3416
3890
|
pruneIntervalMs: 0,
|
|
3417
|
-
|
|
3418
|
-
completedMaxEntries: MSTEAMS_INGRESS_COMPLETED_MAX_ENTRIES,
|
|
3419
|
-
failedTtlMs: MSTEAMS_INGRESS_TOMBSTONE_TTL_MS,
|
|
3420
|
-
failedMaxEntries: MSTEAMS_INGRESS_FAILED_MAX_ENTRIES
|
|
3891
|
+
failedMaxEntries: 4096
|
|
3421
3892
|
},
|
|
3422
3893
|
appendRetryDelaysMs: [0],
|
|
3423
3894
|
waitForDeliveryIdleBeforeRepump: false,
|
|
@@ -3433,13 +3904,13 @@ function createMSTeamsIngress(options) {
|
|
|
3433
3904
|
};
|
|
3434
3905
|
return require_setup_surface.classifyMSTeamsSendError(error).kind === "auth" ? {
|
|
3435
3906
|
reason: "authentication-failed",
|
|
3436
|
-
message:
|
|
3907
|
+
message: (0, openclaw_plugin_sdk_error_runtime.formatErrorMessage)(error)
|
|
3437
3908
|
} : null;
|
|
3438
3909
|
},
|
|
3439
3910
|
onLog: (message) => options.runtime.error?.(`msteams: ${message}`)
|
|
3440
3911
|
},
|
|
3441
3912
|
createStoppedError: () => /* @__PURE__ */ new Error("Microsoft Teams ingress stopped."),
|
|
3442
|
-
onError: (error) => options.runtime.error?.(`msteams ingress drain failed: ${
|
|
3913
|
+
onError: (error) => options.runtime.error?.(`msteams ingress drain failed: ${(0, openclaw_plugin_sdk_error_runtime.formatErrorMessage)(error)}`)
|
|
3443
3914
|
});
|
|
3444
3915
|
let stopTask;
|
|
3445
3916
|
return {
|
|
@@ -3505,6 +3976,7 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3505
3976
|
let msteamsCfg = cfg.channels?.msteams;
|
|
3506
3977
|
if (!msteamsCfg?.enabled) {
|
|
3507
3978
|
log.debug?.("msteams provider disabled");
|
|
3979
|
+
publishMSTeamsBlocked(opts.statusSink, "Microsoft Teams provider is disabled");
|
|
3508
3980
|
return {
|
|
3509
3981
|
app: null,
|
|
3510
3982
|
shutdown: async () => {}
|
|
@@ -3513,6 +3985,7 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3513
3985
|
const creds = require_resolve_allowlist.resolveMSTeamsCredentials(msteamsCfg);
|
|
3514
3986
|
if (!creds) {
|
|
3515
3987
|
log.error("msteams credentials not configured");
|
|
3988
|
+
publishMSTeamsBlocked(opts.statusSink, "Microsoft Teams credentials are not configured");
|
|
3516
3989
|
return {
|
|
3517
3990
|
app: null,
|
|
3518
3991
|
shutdown: async () => {}
|
|
@@ -3529,13 +4002,13 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3529
4002
|
const configuredAllowFrom = msteamsCfg.allowFrom;
|
|
3530
4003
|
const configuredGroupAllowFrom = msteamsCfg.groupAllowFrom;
|
|
3531
4004
|
let allowFrom = require_resolve_allowlist.projectStableMSTeamsUserAllowlist(configuredAllowFrom);
|
|
3532
|
-
let groupAllowFrom = require_resolve_allowlist.
|
|
4005
|
+
let groupAllowFrom = require_resolve_allowlist.projectStableMSTeamsGroupAllowlist(configuredGroupAllowFrom ?? configuredAllowFrom);
|
|
3533
4006
|
let teamsConfig = require_resolve_allowlist.projectStableMSTeamsTeamsConfig(msteamsCfg.teams);
|
|
3534
4007
|
const allowNameMatching = (0, openclaw_plugin_sdk_dangerous_name_runtime.isDangerousNameMatchingEnabled)(msteamsCfg);
|
|
3535
4008
|
const cleanAllowEntry = (entry) => entry.replace(/^(msteams|teams):/i, "").replace(/^user:/i, "").trim();
|
|
3536
4009
|
const isStableUserId = (entry) => /^[0-9a-fA-F-]{16,}$/.test(entry);
|
|
3537
4010
|
const cleanAllowEntries = (entries) => entries?.map((entry) => cleanAllowEntry(entry)).filter((entry) => entry && entry !== "*") ?? [];
|
|
3538
|
-
const isMutableUserEntry = (entry) => !isStableUserId(entry) && !/^accessGroup:/i.test(entry);
|
|
4011
|
+
const isMutableUserEntry = (entry) => !isStableUserId(entry) && !/^accessGroup:/i.test(entry) && !require_resolve_allowlist.looksLikeMSTeamsConversationId(require_resolve_allowlist.normalizeMSTeamsConversationId(entry));
|
|
3539
4012
|
const resolveAllowlistUsers = async (label, entries) => {
|
|
3540
4013
|
if (entries.length === 0) return {
|
|
3541
4014
|
additions: [],
|
|
@@ -3588,6 +4061,10 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3588
4061
|
} catch (err) {
|
|
3589
4062
|
runtime.error?.(`msteams resolve failed; mutable allowlist entries are disabled. ${require_setup_surface.formatUnknownError(err)}`);
|
|
3590
4063
|
}
|
|
4064
|
+
if (configuredGroupAllowFrom == null && groupAllowFrom) groupAllowFrom = (0, openclaw_plugin_sdk_allow_from.mergeAllowlist)({
|
|
4065
|
+
existing: groupAllowFrom,
|
|
4066
|
+
additions: allowFrom ?? []
|
|
4067
|
+
});
|
|
3591
4068
|
msteamsCfg = {
|
|
3592
4069
|
...msteamsCfg,
|
|
3593
4070
|
allowFrom,
|
|
@@ -3603,9 +4080,10 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3603
4080
|
};
|
|
3604
4081
|
const port = msteamsCfg.webhook?.port ?? 3978;
|
|
3605
4082
|
const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "msteams");
|
|
3606
|
-
const
|
|
3607
|
-
|
|
3608
|
-
|
|
4083
|
+
const mediaMaxBytes = (0, openclaw_plugin_sdk_media_runtime.resolveChannelMediaMaxBytes)({
|
|
4084
|
+
cfg,
|
|
4085
|
+
resolveChannelLimitMb: ({ cfg: channelCfg }) => channelCfg.channels?.msteams?.mediaMaxMb
|
|
4086
|
+
}) ?? 8 * 1024 * 1024;
|
|
3609
4087
|
const conversationStore = opts.conversationStore ?? require_polls.createMSTeamsConversationStoreState();
|
|
3610
4088
|
const pollStore = opts.pollStore ?? require_polls.createMSTeamsPollStoreState();
|
|
3611
4089
|
log.info(`starting provider (port ${port})`);
|
|
@@ -3697,8 +4175,8 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3697
4175
|
value: "Poll not found."
|
|
3698
4176
|
};
|
|
3699
4177
|
}
|
|
3700
|
-
const pollConversationId = existingPoll.conversationId ?
|
|
3701
|
-
const activityConversationId =
|
|
4178
|
+
const pollConversationId = existingPoll.conversationId ? require_resolve_allowlist.normalizeMSTeamsConversationId(existingPoll.conversationId) : void 0;
|
|
4179
|
+
const activityConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity?.conversation?.id ?? "");
|
|
3702
4180
|
if (pollConversationId && pollConversationId !== activityConversationId) {
|
|
3703
4181
|
log.info("poll vote ignored (conversation mismatch)", {
|
|
3704
4182
|
pollId: vote.pollId,
|
|
@@ -3852,17 +4330,21 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3852
4330
|
});
|
|
3853
4331
|
await app.initialize();
|
|
3854
4332
|
ingress.start();
|
|
4333
|
+
const privateQaRuntime = require_resolve_allowlist.resolveMSTeamsPrivateQaRuntime();
|
|
3855
4334
|
const httpServer = await new Promise((resolve, reject) => {
|
|
3856
|
-
const
|
|
4335
|
+
const onListen = (err) => err ? reject(err) : resolve(server);
|
|
4336
|
+
const server = privateQaRuntime ? expressApp.listen(port, privateQaRuntime.listenHost, onListen) : expressApp.listen(port, onListen);
|
|
3857
4337
|
}).catch(async (err) => {
|
|
3858
4338
|
log.error("msteams server error", { error: require_setup_surface.formatUnknownError(err) });
|
|
3859
4339
|
await ingress.stop();
|
|
3860
4340
|
throw err;
|
|
3861
4341
|
});
|
|
3862
4342
|
log.info(`msteams provider started on port ${port}`);
|
|
4343
|
+
publishMSTeamsReady(opts.statusSink);
|
|
3863
4344
|
applyMSTeamsWebhookTimeouts(httpServer);
|
|
3864
4345
|
httpServer.on("error", (err) => {
|
|
3865
4346
|
log.error("msteams server error", { error: require_setup_surface.formatUnknownError(err) });
|
|
4347
|
+
publishMSTeamsRecovering(opts.statusSink, require_setup_surface.formatUnknownError(err));
|
|
3866
4348
|
});
|
|
3867
4349
|
const shutdown = async () => {
|
|
3868
4350
|
log.info("shutting down msteams provider");
|
|
@@ -3873,6 +4355,7 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3873
4355
|
});
|
|
3874
4356
|
});
|
|
3875
4357
|
await ingress.stop();
|
|
4358
|
+
publishMSTeamsStopped(opts.statusSink);
|
|
3876
4359
|
};
|
|
3877
4360
|
await (0, openclaw_plugin_sdk_channel_outbound.keepHttpServerTaskAlive)({
|
|
3878
4361
|
server: httpServer,
|
|
@@ -3930,9 +4413,9 @@ function buildActivityHandler() {
|
|
|
3930
4413
|
}
|
|
3931
4414
|
function createMSTeamsReplayContext(activity, app, serviceUrlBoundary) {
|
|
3932
4415
|
const rawConversationId = activity.conversation?.id ?? "";
|
|
3933
|
-
const conversationId =
|
|
4416
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(rawConversationId);
|
|
3934
4417
|
const conversationType = activity.conversation?.conversationType ?? "personal";
|
|
3935
|
-
const threadActivityId = conversationType.toLowerCase() === "channel" ?
|
|
4418
|
+
const threadActivityId = conversationType.toLowerCase() === "channel" ? require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId) ?? activity.replyToId : void 0;
|
|
3936
4419
|
const tenantId = activity.channelData?.tenant?.id ?? activity.conversation?.tenantId;
|
|
3937
4420
|
const reference = {
|
|
3938
4421
|
activityId: activity.id,
|