@openclaw/msteams 2026.9.6 → 2026.9.8
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/assets/icon.png +0 -0
- package/dist/.setup/{adapter.runtime-CQWZeqB5.cjs → adapter.runtime-BiWeMKXE.cjs} +21 -29
- package/dist/.setup/{approval-card-CPNH_q3P.cjs → approval-card-DWPDItvq.cjs} +6 -3
- package/dist/.setup/{approval-handler.runtime-CUnanUdR.cjs → approval-handler.runtime-D1s7UR3U.cjs} +4 -4
- package/dist/.setup/bot-framework-service-url-BUhTe8F3.cjs +55 -0
- package/dist/.setup/{channel-BDp16XRR.cjs → channel-ByzS7QJ0.cjs} +286 -180
- package/dist/.setup/{channel.runtime-Cv5F8JCa.cjs → channel.runtime-Baz8XDFs.cjs} +43 -142
- package/dist/.setup/{channel.setup-BVDfgdCc.cjs → channel.setup-DL8lcWT_.cjs} +6 -6
- package/dist/.setup/{config-schema-D22Lr1Qk.cjs → config-schema-0KoBzb0t.cjs} +24 -38
- package/dist/.setup/{messenger-8RA1FUJa.cjs → conversation-store-state-B7JuyZTm.cjs} +693 -287
- package/dist/.setup/{src-BfOCwlHp.cjs → monitor-BJ7IyH4y.cjs} +422 -592
- package/dist/.setup/{oauth-C6m7dvHj.cjs → oauth-DQjOnJ4l.cjs} +1 -1
- package/dist/.setup/{reaction-types-C7RA7Wxt.cjs → reaction-types-CQ4oSUe7.cjs} +2 -6
- package/dist/.setup/{resolve-allowlist-CHzaaWiC.cjs → resolve-allowlist-Pl_NQFYH.cjs} +55 -312
- package/dist/.setup/{rolldown-runtime-VH7oDXx4.cjs → rolldown-runtime-BloeesfJ.cjs} +22 -0
- package/dist/.setup/{send-CYN_N7O1.cjs → send-VUM7Sryb.cjs} +43 -96
- package/dist/.setup/{setup-plugin-api-DpiaA8z1.cjs → setup-plugin-api-Dyf8kF-l.cjs} +1 -1
- package/dist/.setup/{thread-session-CXgHK-yi.cjs → thread-session--rw3BTal.cjs} +2 -5
- package/dist/api.cjs +2 -2
- package/dist/channel-config-api.cjs +1 -1
- package/dist/channel-plugin-api.cjs +1 -1
- package/dist/configured-state.cjs +0 -1
- package/dist/directory-contract-api.cjs +1 -1
- package/dist/doctor-contract-api.cjs +201 -371
- package/dist/qa-runner-api.cjs +1 -1
- package/dist/runtime-api.cjs +0 -6
- package/dist/setup-entry.cjs +1 -1
- package/dist/setup-plugin-api.cjs +1 -1
- package/openclaw.plugin.json +46 -5
- package/package.json +9 -9
- package/dist/.setup/polls-BJ4-4uZm.cjs +0 -668
- package/dist/.setup/secret-input-C65t6kjM.cjs +0 -1
- package/dist/.setup/sso-token-store-YYLHXgFH.cjs +0 -111
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
const require_rolldown_runtime = require("./rolldown-runtime-
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-BloeesfJ.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
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const require_reaction_types = require("./reaction-types-C7RA7Wxt.cjs");
|
|
4
|
+
const require_resolve_allowlist = require("./resolve-allowlist-Pl_NQFYH.cjs");
|
|
5
|
+
const require_bot_framework_service_url = require("./bot-framework-service-url-BUhTe8F3.cjs");
|
|
6
|
+
const require_channel = require("./channel-ByzS7QJ0.cjs");
|
|
7
|
+
const require_thread_session = require("./thread-session--rw3BTal.cjs");
|
|
8
|
+
const require_channel_setup = require("./channel.setup-DL8lcWT_.cjs");
|
|
9
|
+
const require_approval_card = require("./approval-card-DWPDItvq.cjs");
|
|
10
|
+
const require_conversation_store_state = require("./conversation-store-state-B7JuyZTm.cjs");
|
|
11
|
+
const require_reaction_types = require("./reaction-types-CQ4oSUe7.cjs");
|
|
13
12
|
let openclaw_plugin_sdk_channel_outbound = require("openclaw/plugin-sdk/channel-outbound");
|
|
14
13
|
let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
|
|
15
14
|
let openclaw_plugin_sdk_allow_from = require("openclaw/plugin-sdk/allow-from");
|
|
@@ -17,13 +16,16 @@ let openclaw_plugin_sdk_provider_http = require("openclaw/plugin-sdk/provider-ht
|
|
|
17
16
|
let openclaw_plugin_sdk_account_id = require("openclaw/plugin-sdk/account-id");
|
|
18
17
|
let openclaw_plugin_sdk_media_runtime = require("openclaw/plugin-sdk/media-runtime");
|
|
19
18
|
let openclaw_plugin_sdk_ssrf_runtime = require("openclaw/plugin-sdk/ssrf-runtime");
|
|
19
|
+
let openclaw_plugin_sdk_ssrf_policy = require("openclaw/plugin-sdk/ssrf-policy");
|
|
20
20
|
let openclaw_plugin_sdk_number_runtime = require("openclaw/plugin-sdk/number-runtime");
|
|
21
21
|
let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text-utility-runtime");
|
|
22
22
|
let openclaw_plugin_sdk_error_runtime = require("openclaw/plugin-sdk/error-runtime");
|
|
23
23
|
let openclaw_plugin_sdk_routing = require("openclaw/plugin-sdk/routing");
|
|
24
|
+
let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
|
|
24
25
|
let node_crypto = require("node:crypto");
|
|
25
26
|
node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
|
|
26
|
-
let
|
|
27
|
+
let openclaw_plugin_sdk_security_runtime = require("openclaw/plugin-sdk/security-runtime");
|
|
28
|
+
let openclaw_plugin_sdk_webhook_targets = require("openclaw/plugin-sdk/webhook-targets");
|
|
27
29
|
let openclaw_plugin_sdk_approval_gateway_runtime = require("openclaw/plugin-sdk/approval-gateway-runtime");
|
|
28
30
|
let openclaw_plugin_sdk_channel_ingress_runtime = require("openclaw/plugin-sdk/channel-ingress-runtime");
|
|
29
31
|
let openclaw_plugin_sdk_context_visibility_runtime = require("openclaw/plugin-sdk/context-visibility-runtime");
|
|
@@ -32,10 +34,9 @@ let openclaw_plugin_sdk_runtime_config_snapshot = require("openclaw/plugin-sdk/r
|
|
|
32
34
|
let openclaw_plugin_sdk_agent_runtime = require("openclaw/plugin-sdk/agent-runtime");
|
|
33
35
|
let openclaw_plugin_sdk_extension_shared = require("openclaw/plugin-sdk/extension-shared");
|
|
34
36
|
let openclaw_plugin_sdk_plugin_runtime = require("openclaw/plugin-sdk/plugin-runtime");
|
|
35
|
-
let openclaw_plugin_sdk_dedupe_runtime = require("openclaw/plugin-sdk/dedupe-runtime");
|
|
36
|
-
let openclaw_plugin_sdk_plugin_state_runtime = require("openclaw/plugin-sdk/plugin-state-runtime");
|
|
37
37
|
let openclaw_plugin_sdk_collection_runtime = require("openclaw/plugin-sdk/collection-runtime");
|
|
38
38
|
let openclaw_plugin_sdk_gateway_runtime = require("openclaw/plugin-sdk/gateway-runtime");
|
|
39
|
+
let openclaw_plugin_sdk_time_runtime = require("openclaw/plugin-sdk/time-runtime");
|
|
39
40
|
let openclaw_plugin_sdk_channel_pairing = require("openclaw/plugin-sdk/channel-pairing");
|
|
40
41
|
let openclaw_plugin_sdk_runtime_group_policy = require("openclaw/plugin-sdk/runtime-group-policy");
|
|
41
42
|
let openclaw_plugin_sdk_dangerous_name_runtime = require("openclaw/plugin-sdk/dangerous-name-runtime");
|
|
@@ -150,7 +151,7 @@ async function runFeedbackReflection(params) {
|
|
|
150
151
|
return;
|
|
151
152
|
}
|
|
152
153
|
try {
|
|
153
|
-
await
|
|
154
|
+
await require_conversation_store_state.sendMSTeamsActivityWithReference(params.app, require_conversation_store_state.buildConversationReference(params.conversationRef), {
|
|
154
155
|
type: "message",
|
|
155
156
|
text: reflection.userMessage
|
|
156
157
|
}, { serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(cfg.channels?.msteams) });
|
|
@@ -248,12 +249,8 @@ async function maybeHandleMSTeamsApprovalCardSubmit(params) {
|
|
|
248
249
|
senderId
|
|
249
250
|
});
|
|
250
251
|
await context.updateActivity({
|
|
251
|
-
|
|
252
|
-
id: consumed.activityId
|
|
253
|
-
attachments: [{
|
|
254
|
-
contentType: "application/vnd.microsoft.card.adaptive",
|
|
255
|
-
content: require_approval_card.buildMSTeamsCanonicalApprovalTerminalCard(result)
|
|
256
|
-
}]
|
|
252
|
+
...require_conversation_store_state.buildMSTeamsAdaptiveCardActivity(require_approval_card.buildMSTeamsCanonicalApprovalTerminalCard(result)),
|
|
253
|
+
id: consumed.activityId
|
|
257
254
|
});
|
|
258
255
|
return result;
|
|
259
256
|
});
|
|
@@ -455,7 +452,7 @@ async function admitMSTeamsMessage(params) {
|
|
|
455
452
|
params.log.debug?.("dropping dm (dms disabled)");
|
|
456
453
|
return null;
|
|
457
454
|
}
|
|
458
|
-
const allowMatch =
|
|
455
|
+
const allowMatch = (0, openclaw_plugin_sdk_allow_from.resolveAllowlistMatchSimple)({
|
|
459
456
|
allowFrom: effectiveDmAllowFrom,
|
|
460
457
|
senderId,
|
|
461
458
|
senderName,
|
|
@@ -520,7 +517,7 @@ async function admitMSTeamsMessage(params) {
|
|
|
520
517
|
return null;
|
|
521
518
|
}
|
|
522
519
|
if (!senderAccess.allowed) {
|
|
523
|
-
const allowMatch =
|
|
520
|
+
const allowMatch = (0, openclaw_plugin_sdk_allow_from.resolveAllowlistMatchSimple)({
|
|
524
521
|
allowFrom: effectiveGroupAllowFrom,
|
|
525
522
|
senderId,
|
|
526
523
|
senderName,
|
|
@@ -562,11 +559,19 @@ async function admitMSTeamsMessage(params) {
|
|
|
562
559
|
};
|
|
563
560
|
}
|
|
564
561
|
//#endregion
|
|
562
|
+
//#region src/channels/mention-gating.ts
|
|
563
|
+
function resolveBotThreadMentionPolicy(params) {
|
|
564
|
+
if (!params.isBotOwnedThread || params.requireMentionInBotThreads === void 0) return {
|
|
565
|
+
requireMention: params.requireMention,
|
|
566
|
+
implicitMentionKinds: params.implicitMentionKinds
|
|
567
|
+
};
|
|
568
|
+
return {
|
|
569
|
+
requireMention: params.requireMentionInBotThreads,
|
|
570
|
+
implicitMentionKinds: params.requireMentionInBotThreads ? params.implicitMentionKinds?.filter((kind) => kind === "native") : params.implicitMentionKinds
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
//#endregion
|
|
565
574
|
//#region extensions/msteams/src/attachments/bot-framework.ts
|
|
566
|
-
/**
|
|
567
|
-
* Bot Framework Service token scope for requesting a token used against
|
|
568
|
-
* the Bot Connector (v3) REST endpoints such as `/v3/attachments/{id}`.
|
|
569
|
-
*/
|
|
570
575
|
const BOT_FRAMEWORK_SCOPE = "https://api.botframework.com";
|
|
571
576
|
/**
|
|
572
577
|
* Detect Bot Framework personal chat ("a:") and MSA orgid ("8:orgid:") conversation
|
|
@@ -580,45 +585,39 @@ function isBotFrameworkPersonalChatId(conversationId) {
|
|
|
580
585
|
const trimmed = conversationId.trim();
|
|
581
586
|
return trimmed.startsWith("a:") || trimmed.startsWith("8:orgid:");
|
|
582
587
|
}
|
|
583
|
-
function
|
|
584
|
-
return serviceUrl.replace(/\/+$/, "");
|
|
585
|
-
}
|
|
586
|
-
function buildBotFrameworkAttachmentHeaders(params) {
|
|
587
|
-
const headers = require_resolve_allowlist.ensureUserAgentHeader();
|
|
588
|
-
require_resolve_allowlist.applyAuthorizationHeaderForUrl({
|
|
589
|
-
headers,
|
|
590
|
-
url: params.url,
|
|
591
|
-
authAllowHosts: params.policy.authAllowHosts,
|
|
592
|
-
bearerToken: params.accessToken
|
|
593
|
-
});
|
|
594
|
-
return headers;
|
|
595
|
-
}
|
|
596
|
-
async function fetchBotFrameworkAttachmentInfo(params) {
|
|
597
|
-
const url = `${normalizeServiceUrl(params.serviceUrl)}/v3/attachments/${encodeURIComponent(params.attachmentId)}`;
|
|
588
|
+
async function fetchBotFrameworkAttachment(params, kind) {
|
|
598
589
|
let response;
|
|
599
590
|
try {
|
|
591
|
+
const headers = require_resolve_allowlist.ensureUserAgentHeader();
|
|
592
|
+
require_resolve_allowlist.applyAuthorizationHeaderForUrl({
|
|
593
|
+
headers,
|
|
594
|
+
url: params.url,
|
|
595
|
+
authAllowHosts: params.policy.authAllowHosts,
|
|
596
|
+
bearerToken: params.accessToken
|
|
597
|
+
});
|
|
600
598
|
response = await require_resolve_allowlist.safeFetchWithPolicy({
|
|
601
|
-
url,
|
|
599
|
+
url: params.url,
|
|
602
600
|
policy: params.policy,
|
|
603
601
|
fetchFn: params.fetchFn,
|
|
604
602
|
fetchFnSupportsDispatcher: params.fetchFnSupportsDispatcher,
|
|
605
603
|
resolveFn: params.resolveFn,
|
|
606
|
-
requestInit: { headers
|
|
607
|
-
url,
|
|
608
|
-
accessToken: params.accessToken,
|
|
609
|
-
policy: params.policy
|
|
610
|
-
}) },
|
|
604
|
+
requestInit: { headers },
|
|
611
605
|
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
612
606
|
});
|
|
613
607
|
} catch (err) {
|
|
614
|
-
params.logger?.warn?.(
|
|
608
|
+
params.logger?.warn?.(`msteams botFramework ${kind} fetch failed`, { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
615
609
|
return;
|
|
616
610
|
}
|
|
617
611
|
if (!response.ok) {
|
|
618
612
|
await response.body?.cancel().catch(() => void 0);
|
|
619
|
-
params.logger?.warn?.(
|
|
613
|
+
params.logger?.warn?.(`msteams botFramework ${kind} non-ok`, { status: response.status });
|
|
620
614
|
return;
|
|
621
615
|
}
|
|
616
|
+
return response;
|
|
617
|
+
}
|
|
618
|
+
async function fetchBotFrameworkAttachmentInfo(params) {
|
|
619
|
+
const response = await fetchBotFrameworkAttachment(params, "attachmentInfo");
|
|
620
|
+
if (!response) return;
|
|
622
621
|
try {
|
|
623
622
|
return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(response, "msteams botFramework attachmentInfo");
|
|
624
623
|
} catch (err) {
|
|
@@ -627,31 +626,8 @@ async function fetchBotFrameworkAttachmentInfo(params) {
|
|
|
627
626
|
}
|
|
628
627
|
}
|
|
629
628
|
async function saveBotFrameworkAttachmentView(params) {
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
try {
|
|
633
|
-
response = await require_resolve_allowlist.safeFetchWithPolicy({
|
|
634
|
-
url,
|
|
635
|
-
policy: params.policy,
|
|
636
|
-
fetchFn: params.fetchFn,
|
|
637
|
-
fetchFnSupportsDispatcher: params.fetchFnSupportsDispatcher,
|
|
638
|
-
resolveFn: params.resolveFn,
|
|
639
|
-
requestInit: { headers: buildBotFrameworkAttachmentHeaders({
|
|
640
|
-
url,
|
|
641
|
-
accessToken: params.accessToken,
|
|
642
|
-
policy: params.policy
|
|
643
|
-
}) },
|
|
644
|
-
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
645
|
-
});
|
|
646
|
-
} catch (err) {
|
|
647
|
-
params.logger?.warn?.("msteams botFramework attachmentView fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
if (!response.ok) {
|
|
651
|
-
await response.body?.cancel().catch(() => void 0);
|
|
652
|
-
params.logger?.warn?.("msteams botFramework attachmentView non-ok", { status: response.status });
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
629
|
+
const response = await fetchBotFrameworkAttachment(params, "attachmentView");
|
|
630
|
+
if (!response) return;
|
|
655
631
|
let contentLength;
|
|
656
632
|
try {
|
|
657
633
|
contentLength = (0, openclaw_plugin_sdk_media_runtime.parseMediaContentLength)(response.headers.get("content-length"));
|
|
@@ -666,7 +642,7 @@ async function saveBotFrameworkAttachmentView(params) {
|
|
|
666
642
|
}
|
|
667
643
|
try {
|
|
668
644
|
return await require_runtime.getMSTeamsRuntime().channel.media.saveResponseMedia(response, {
|
|
669
|
-
sourceUrl: url,
|
|
645
|
+
sourceUrl: params.url,
|
|
670
646
|
filePathHint: params.fileNameHint,
|
|
671
647
|
maxBytes: params.maxBytes,
|
|
672
648
|
fallbackContentType: params.contentTypeHint,
|
|
@@ -680,12 +656,6 @@ async function saveBotFrameworkAttachmentView(params) {
|
|
|
680
656
|
await response.body?.cancel().catch(() => void 0);
|
|
681
657
|
}
|
|
682
658
|
}
|
|
683
|
-
/**
|
|
684
|
-
* Download media for a single attachment via the Bot Framework v3 attachments
|
|
685
|
-
* endpoint. Used for personal DM conversations where the Graph `/chats/{id}`
|
|
686
|
-
* path is not usable because the Bot Framework conversation ID (`a:...`) is
|
|
687
|
-
* not a valid Graph chat identifier.
|
|
688
|
-
*/
|
|
689
659
|
async function downloadMSTeamsBotFrameworkAttachment(params) {
|
|
690
660
|
if (!params.serviceUrl || !params.attachmentId || !params.tokenProvider) return;
|
|
691
661
|
const tokenProvider = params.tokenProvider;
|
|
@@ -693,8 +663,8 @@ async function downloadMSTeamsBotFrameworkAttachment(params) {
|
|
|
693
663
|
allowHosts: params.allowHosts,
|
|
694
664
|
authAllowHosts: params.authAllowHosts
|
|
695
665
|
});
|
|
696
|
-
const baseUrl = `${
|
|
697
|
-
if (!
|
|
666
|
+
const baseUrl = `${params.serviceUrl.replace(/\/+$/, "")}/v3/attachments/${encodeURIComponent(params.attachmentId)}`;
|
|
667
|
+
if (!(0, openclaw_plugin_sdk_ssrf_policy.isHttpsUrlAllowedByHostnameSuffixAllowlist)(baseUrl, policy.allowHosts)) return;
|
|
698
668
|
let accessToken;
|
|
699
669
|
try {
|
|
700
670
|
accessToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
@@ -707,9 +677,8 @@ async function downloadMSTeamsBotFrameworkAttachment(params) {
|
|
|
707
677
|
return;
|
|
708
678
|
}
|
|
709
679
|
if (!accessToken) return;
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
attachmentId: params.attachmentId,
|
|
680
|
+
const request = {
|
|
681
|
+
url: baseUrl,
|
|
713
682
|
accessToken,
|
|
714
683
|
policy,
|
|
715
684
|
fetchFn: params.fetchFn,
|
|
@@ -717,7 +686,8 @@ async function downloadMSTeamsBotFrameworkAttachment(params) {
|
|
|
717
686
|
resolveFn: params.resolveFn,
|
|
718
687
|
logger: params.logger,
|
|
719
688
|
deadline: params.deadline
|
|
720
|
-
}
|
|
689
|
+
};
|
|
690
|
+
const info = await fetchBotFrameworkAttachmentInfo(request);
|
|
721
691
|
if (!info) return;
|
|
722
692
|
const views = Array.isArray(info.views) ? info.views : [];
|
|
723
693
|
const candidateView = views.find((view) => view?.viewId === "original") ?? views.find((view) => typeof view?.viewId === "string");
|
|
@@ -727,20 +697,12 @@ async function downloadMSTeamsBotFrameworkAttachment(params) {
|
|
|
727
697
|
const fileNameHint = typeof params.fileNameHint === "string" && params.fileNameHint || typeof info.name === "string" && info.name || void 0;
|
|
728
698
|
const contentTypeHint = typeof params.contentTypeHint === "string" && params.contentTypeHint || typeof info.type === "string" && info.type || void 0;
|
|
729
699
|
const saved = await saveBotFrameworkAttachmentView({
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
viewId,
|
|
733
|
-
accessToken,
|
|
700
|
+
...request,
|
|
701
|
+
url: `${baseUrl}/views/${encodeURIComponent(viewId)}`,
|
|
734
702
|
maxBytes: params.maxBytes,
|
|
735
703
|
fileNameHint,
|
|
736
704
|
contentTypeHint,
|
|
737
|
-
preserveFilenames: params.preserveFilenames
|
|
738
|
-
policy,
|
|
739
|
-
fetchFn: params.fetchFn,
|
|
740
|
-
fetchFnSupportsDispatcher: params.fetchFnSupportsDispatcher,
|
|
741
|
-
resolveFn: params.resolveFn,
|
|
742
|
-
logger: params.logger,
|
|
743
|
-
deadline: params.deadline
|
|
705
|
+
preserveFilenames: params.preserveFilenames
|
|
744
706
|
});
|
|
745
707
|
if (!saved) return;
|
|
746
708
|
return {
|
|
@@ -759,15 +721,7 @@ async function downloadMSTeamsBotFrameworkAttachment(params) {
|
|
|
759
721
|
* reuse the existing logging path.
|
|
760
722
|
*/
|
|
761
723
|
async function downloadMSTeamsBotFrameworkAttachments(params) {
|
|
762
|
-
const
|
|
763
|
-
const unique = [];
|
|
764
|
-
for (const id of params.attachmentIds ?? []) {
|
|
765
|
-
if (typeof id !== "string") continue;
|
|
766
|
-
const trimmed = id.trim();
|
|
767
|
-
if (!trimmed || seen.has(trimmed)) continue;
|
|
768
|
-
seen.add(trimmed);
|
|
769
|
-
unique.push(trimmed);
|
|
770
|
-
}
|
|
724
|
+
const unique = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeUniqueTrimmedStringList)(params.attachmentIds);
|
|
771
725
|
if (unique.length === 0 || !params.serviceUrl || !params.tokenProvider) return {
|
|
772
726
|
media: [],
|
|
773
727
|
attachmentCount: unique.length
|
|
@@ -775,20 +729,8 @@ async function downloadMSTeamsBotFrameworkAttachments(params) {
|
|
|
775
729
|
const media = [];
|
|
776
730
|
for (const attachmentId of unique) try {
|
|
777
731
|
const item = await downloadMSTeamsBotFrameworkAttachment({
|
|
778
|
-
|
|
779
|
-
attachmentId
|
|
780
|
-
tokenProvider: params.tokenProvider,
|
|
781
|
-
maxBytes: params.maxBytes,
|
|
782
|
-
allowHosts: params.allowHosts,
|
|
783
|
-
authAllowHosts: params.authAllowHosts,
|
|
784
|
-
fetchFn: params.fetchFn,
|
|
785
|
-
fetchFnSupportsDispatcher: params.fetchFnSupportsDispatcher,
|
|
786
|
-
resolveFn: params.resolveFn,
|
|
787
|
-
deadline: params.deadline,
|
|
788
|
-
fileNameHint: params.fileNameHint,
|
|
789
|
-
contentTypeHint: params.contentTypeHint,
|
|
790
|
-
preserveFilenames: params.preserveFilenames,
|
|
791
|
-
logger: params.logger
|
|
732
|
+
...params,
|
|
733
|
+
attachmentId
|
|
792
734
|
});
|
|
793
735
|
if (item) media.push({
|
|
794
736
|
...item,
|
|
@@ -828,11 +770,9 @@ function extractMSTeamsHtmlAttachmentIds(attachments) {
|
|
|
828
770
|
const html = require_resolve_allowlist.extractHtmlFromAttachment(att);
|
|
829
771
|
if (!html) continue;
|
|
830
772
|
require_resolve_allowlist.ATTACHMENT_TAG_RE.lastIndex = 0;
|
|
831
|
-
|
|
832
|
-
while (match) {
|
|
773
|
+
for (const match of html.matchAll(require_resolve_allowlist.ATTACHMENT_TAG_RE)) {
|
|
833
774
|
const id = match[1]?.trim();
|
|
834
775
|
if (id) ids.add(id);
|
|
835
|
-
match = require_resolve_allowlist.ATTACHMENT_TAG_RE.exec(html);
|
|
836
776
|
}
|
|
837
777
|
}
|
|
838
778
|
return Array.from(ids);
|
|
@@ -852,8 +792,7 @@ function summarizeMSTeamsHtmlAttachments(attachments) {
|
|
|
852
792
|
if (!html) continue;
|
|
853
793
|
htmlAttachments += 1;
|
|
854
794
|
require_resolve_allowlist.IMG_SRC_RE.lastIndex = 0;
|
|
855
|
-
|
|
856
|
-
while (match) {
|
|
795
|
+
for (const match of html.matchAll(require_resolve_allowlist.IMG_SRC_RE)) {
|
|
857
796
|
imgTags += 1;
|
|
858
797
|
const src = match[1]?.trim();
|
|
859
798
|
if (src) {
|
|
@@ -861,15 +800,12 @@ function summarizeMSTeamsHtmlAttachments(attachments) {
|
|
|
861
800
|
else if (src.startsWith("cid:")) cidImages += 1;
|
|
862
801
|
else srcHosts.add(require_resolve_allowlist.safeHostForUrl(src));
|
|
863
802
|
}
|
|
864
|
-
match = require_resolve_allowlist.IMG_SRC_RE.exec(html);
|
|
865
803
|
}
|
|
866
804
|
require_resolve_allowlist.ATTACHMENT_TAG_RE.lastIndex = 0;
|
|
867
|
-
|
|
868
|
-
while (attachmentMatch) {
|
|
805
|
+
for (const attachmentMatch of html.matchAll(require_resolve_allowlist.ATTACHMENT_TAG_RE)) {
|
|
869
806
|
attachmentTags += 1;
|
|
870
807
|
const id = attachmentMatch[1]?.trim();
|
|
871
808
|
if (id) attachmentIds.add(id);
|
|
872
|
-
attachmentMatch = require_resolve_allowlist.ATTACHMENT_TAG_RE.exec(html);
|
|
873
809
|
}
|
|
874
810
|
}
|
|
875
811
|
if (htmlAttachments === 0) return;
|
|
@@ -893,9 +829,10 @@ function resolveUnrepresentedHtmlAttachmentIds(attachments) {
|
|
|
893
829
|
return extractMSTeamsHtmlAttachmentIds(attachments).filter((id) => !representedIds.has(id));
|
|
894
830
|
}
|
|
895
831
|
function createAdvertisedMediaFact(kind, sourceId) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
832
|
+
return {
|
|
833
|
+
kind,
|
|
834
|
+
...sourceId ? { sourceId } : {}
|
|
835
|
+
};
|
|
899
836
|
}
|
|
900
837
|
function resolveMSTeamsAdvertisedMedia(attachments) {
|
|
901
838
|
const list = Array.isArray(attachments) ? attachments : [];
|
|
@@ -919,49 +856,28 @@ function resolveMSTeamsAdvertisedMedia(attachments) {
|
|
|
919
856
|
* implementation. This lets Teams-specific auth fallback own the request
|
|
920
857
|
* sequence while keeping redirect and DNS pinning inside `safeFetchWithPolicy`.
|
|
921
858
|
*/
|
|
922
|
-
async function
|
|
859
|
+
async function downloadAndStoreMSTeamsRemoteMedia(params) {
|
|
923
860
|
const response = await params.fetchImpl(params.url, { redirect: "follow" });
|
|
924
861
|
try {
|
|
925
|
-
|
|
862
|
+
const saved = await (0, openclaw_plugin_sdk_media_runtime.saveResponseMedia)(response, {
|
|
926
863
|
sourceUrl: params.url,
|
|
927
864
|
filePathHint: params.filePathHint,
|
|
928
865
|
maxBytes: params.maxBytes,
|
|
929
866
|
fallbackContentType: params.contentTypeHint,
|
|
930
|
-
originalFilename: params.
|
|
867
|
+
originalFilename: params.preserveFilenames ? params.filePathHint : void 0
|
|
931
868
|
});
|
|
869
|
+
return {
|
|
870
|
+
path: saved.path,
|
|
871
|
+
contentType: saved.contentType,
|
|
872
|
+
kind: params.kind ?? require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
873
|
+
contentType: saved.contentType,
|
|
874
|
+
fileName: params.filePathHint
|
|
875
|
+
})
|
|
876
|
+
};
|
|
932
877
|
} finally {
|
|
933
878
|
await response.body?.cancel().catch(() => void 0);
|
|
934
879
|
}
|
|
935
880
|
}
|
|
936
|
-
async function downloadAndStoreMSTeamsRemoteMedia(params) {
|
|
937
|
-
const originalFilename = params.preserveFilenames ? params.filePathHint : void 0;
|
|
938
|
-
let saved;
|
|
939
|
-
if (params.useDirectFetch && params.fetchImpl) saved = await saveRemoteMediaDirect({
|
|
940
|
-
url: params.url,
|
|
941
|
-
filePathHint: params.filePathHint,
|
|
942
|
-
fetchImpl: params.fetchImpl,
|
|
943
|
-
maxBytes: params.maxBytes,
|
|
944
|
-
contentTypeHint: params.contentTypeHint,
|
|
945
|
-
originalFilename
|
|
946
|
-
});
|
|
947
|
-
else saved = await require_runtime.getMSTeamsRuntime().channel.media.saveRemoteMedia({
|
|
948
|
-
url: params.url,
|
|
949
|
-
fetchImpl: params.fetchImpl,
|
|
950
|
-
filePathHint: params.filePathHint,
|
|
951
|
-
maxBytes: params.maxBytes,
|
|
952
|
-
ssrfPolicy: params.ssrfPolicy,
|
|
953
|
-
fallbackContentType: params.contentTypeHint,
|
|
954
|
-
originalFilename
|
|
955
|
-
});
|
|
956
|
-
return {
|
|
957
|
-
path: saved.path,
|
|
958
|
-
contentType: saved.contentType,
|
|
959
|
-
kind: params.kind ?? require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
960
|
-
contentType: saved.contentType,
|
|
961
|
-
fileName: params.filePathHint
|
|
962
|
-
})
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
881
|
//#endregion
|
|
966
882
|
//#region extensions/msteams/src/attachments/download.ts
|
|
967
883
|
function withSourceId(media, sourceId) {
|
|
@@ -1021,22 +937,8 @@ function scopeCandidatesForUrl(url) {
|
|
|
1021
937
|
}
|
|
1022
938
|
function canonicalizeInlineBase64Payload(value) {
|
|
1023
939
|
let cleaned = "";
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
1027
|
-
const code = value.charCodeAt(index);
|
|
1028
|
-
if (code <= 32) continue;
|
|
1029
|
-
if (code === 61) {
|
|
1030
|
-
padding += 1;
|
|
1031
|
-
if (padding > 2) return;
|
|
1032
|
-
sawPadding = true;
|
|
1033
|
-
cleaned += "=";
|
|
1034
|
-
continue;
|
|
1035
|
-
}
|
|
1036
|
-
if (sawPadding || !(code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47)) return;
|
|
1037
|
-
cleaned += value[index];
|
|
1038
|
-
}
|
|
1039
|
-
return cleaned && cleaned.length % 4 === 0 ? cleaned : void 0;
|
|
940
|
+
for (const char of value) if (char.charCodeAt(0) > 32) cleaned += char;
|
|
941
|
+
return cleaned.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(cleaned) ? cleaned : void 0;
|
|
1040
942
|
}
|
|
1041
943
|
function decodeInlineDataImage(src, maxBytes, totalBytes) {
|
|
1042
944
|
const match = /^data:(image\/[a-z0-9.+-]+)?(;base64)?,(.*)$/i.exec(src);
|
|
@@ -1079,7 +981,7 @@ async function fetchWithAuthFallback(params) {
|
|
|
1079
981
|
if (!params.tokenProvider) return firstAttempt;
|
|
1080
982
|
const tokenProvider = params.tokenProvider;
|
|
1081
983
|
if (firstAttempt.status !== 401 && firstAttempt.status !== 403) return firstAttempt;
|
|
1082
|
-
if (!
|
|
984
|
+
if (!(0, openclaw_plugin_sdk_ssrf_policy.isHttpsUrlAllowedByHostnameSuffixAllowlist)(params.url, params.policy.authAllowHosts)) return firstAttempt;
|
|
1083
985
|
let fallbackAttempt = firstAttempt;
|
|
1084
986
|
const scopes = scopeCandidatesForUrl(params.url);
|
|
1085
987
|
const fetchFn = params.fetchFn ?? fetch;
|
|
@@ -1109,10 +1011,6 @@ async function fetchWithAuthFallback(params) {
|
|
|
1109
1011
|
} catch {}
|
|
1110
1012
|
return fallbackAttempt;
|
|
1111
1013
|
}
|
|
1112
|
-
/**
|
|
1113
|
-
* Download all file attachments from a Teams message (images, documents, etc.).
|
|
1114
|
-
* Renamed from downloadMSTeamsImageAttachments to support all file types.
|
|
1115
|
-
*/
|
|
1116
1014
|
async function downloadMSTeamsAttachments(params) {
|
|
1117
1015
|
const list = Array.isArray(params.attachments) ? params.attachments : [];
|
|
1118
1016
|
if (list.length === 0) return [];
|
|
@@ -1121,7 +1019,6 @@ async function downloadMSTeamsAttachments(params) {
|
|
|
1121
1019
|
authAllowHosts: params.authAllowHosts
|
|
1122
1020
|
});
|
|
1123
1021
|
const allowHosts = policy.allowHosts;
|
|
1124
|
-
const ssrfPolicy = require_resolve_allowlist.resolveMediaSsrfPolicy(allowHosts);
|
|
1125
1022
|
const candidates = list.filter(require_resolve_allowlist.isAdvertisedFileAttachment).map((attachment) => {
|
|
1126
1023
|
return (require_resolve_allowlist.isDownloadableAttachment(attachment) ? resolveDownloadCandidate(attachment) : null) ?? {
|
|
1127
1024
|
kind: "unavailable",
|
|
@@ -1192,7 +1089,7 @@ async function downloadMSTeamsAttachments(params) {
|
|
|
1192
1089
|
}
|
|
1193
1090
|
continue;
|
|
1194
1091
|
}
|
|
1195
|
-
if (!
|
|
1092
|
+
if (!(0, openclaw_plugin_sdk_ssrf_policy.isHttpsUrlAllowedByHostnameSuffixAllowlist)(candidate.url, allowHosts)) {
|
|
1196
1093
|
out.push(withSourceId({ kind: candidate.mediaKind }, candidate.sourceId));
|
|
1197
1094
|
continue;
|
|
1198
1095
|
}
|
|
@@ -1204,8 +1101,6 @@ async function downloadMSTeamsAttachments(params) {
|
|
|
1204
1101
|
contentTypeHint: candidate.contentTypeHint,
|
|
1205
1102
|
kind: candidate.mediaKind,
|
|
1206
1103
|
preserveFilenames: params.preserveFilenames,
|
|
1207
|
-
ssrfPolicy,
|
|
1208
|
-
useDirectFetch: true,
|
|
1209
1104
|
fetchImpl: (input, init) => fetchWithAuthFallback({
|
|
1210
1105
|
url: require_resolve_allowlist.resolveRequestUrl(input),
|
|
1211
1106
|
tokenProvider: params.tokenProvider,
|
|
@@ -1308,10 +1203,6 @@ function normalizeGraphAttachment(att) {
|
|
|
1308
1203
|
content
|
|
1309
1204
|
};
|
|
1310
1205
|
}
|
|
1311
|
-
/**
|
|
1312
|
-
* Download all hosted content from a Teams message (images, documents, etc.).
|
|
1313
|
-
* Renamed from downloadGraphHostedImages to support all file types.
|
|
1314
|
-
*/
|
|
1315
1206
|
async function downloadGraphHostedContent(params) {
|
|
1316
1207
|
let hosted;
|
|
1317
1208
|
try {
|
|
@@ -1389,7 +1280,7 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1389
1280
|
allowHosts: params.allowHosts,
|
|
1390
1281
|
authAllowHosts: params.authAllowHosts
|
|
1391
1282
|
});
|
|
1392
|
-
const ssrfPolicy =
|
|
1283
|
+
const ssrfPolicy = (0, openclaw_plugin_sdk_ssrf_policy.buildHostnameAllowlistPolicyFromSuffixAllowlist)(policy.allowHosts);
|
|
1393
1284
|
const messageUrl = params.messageUrl;
|
|
1394
1285
|
let accessToken;
|
|
1395
1286
|
try {
|
|
@@ -1476,7 +1367,7 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1476
1367
|
downloadedReferenceUrls.add(shareUrl);
|
|
1477
1368
|
try {
|
|
1478
1369
|
const sharesUrl = `${require_resolve_allowlist.GRAPH_ROOT}/shares/${require_resolve_allowlist.encodeGraphShareId(shareUrl)}/driveItem/content`;
|
|
1479
|
-
if (!
|
|
1370
|
+
if (!(0, openclaw_plugin_sdk_ssrf_policy.isHttpsUrlAllowedByHostnameSuffixAllowlist)(sharesUrl, policy.allowHosts)) {
|
|
1480
1371
|
params.logger?.debug?.("graph media sharepoint url not in allowHosts", {
|
|
1481
1372
|
messageUrl,
|
|
1482
1373
|
sharesUrl
|
|
@@ -1490,8 +1381,6 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1490
1381
|
maxBytes: params.maxBytes,
|
|
1491
1382
|
contentTypeHint: "application/octet-stream",
|
|
1492
1383
|
preserveFilenames: params.preserveFilenames,
|
|
1493
|
-
ssrfPolicy,
|
|
1494
|
-
useDirectFetch: true,
|
|
1495
1384
|
fetchImpl: async (input, init) => {
|
|
1496
1385
|
const requestUrl = require_resolve_allowlist.resolveRequestUrl(input);
|
|
1497
1386
|
const headers = require_resolve_allowlist.ensureUserAgentHeader(init?.headers);
|
|
@@ -1532,7 +1421,6 @@ async function downloadMSTeamsGraphMedia(params) {
|
|
|
1532
1421
|
messageUrl,
|
|
1533
1422
|
maxBytes: params.maxBytes,
|
|
1534
1423
|
fetchFn: params.fetchFn,
|
|
1535
|
-
preserveFilenames: params.preserveFilenames,
|
|
1536
1424
|
ssrfPolicy,
|
|
1537
1425
|
logger: params.logger,
|
|
1538
1426
|
deadline: params.deadline
|
|
@@ -1875,7 +1763,7 @@ function createTeamsReplyStreamController(params) {
|
|
|
1875
1763
|
} : payload;
|
|
1876
1764
|
};
|
|
1877
1765
|
const finalStreamActivity = (text) => ({
|
|
1878
|
-
...
|
|
1766
|
+
...require_conversation_store_state.buildMSTeamsMessageActivity(text === void 0 ? void 0 : require_conversation_store_state.formatMSTeamsMarkdown(text, params.tableMode ?? "code")),
|
|
1879
1767
|
channelData: params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {}
|
|
1880
1768
|
});
|
|
1881
1769
|
const deferredReplacementLogicalContent = () => {
|
|
@@ -1895,6 +1783,19 @@ function createTeamsReplyStreamController(params) {
|
|
|
1895
1783
|
deferredReplacementEntries = [];
|
|
1896
1784
|
return payloads;
|
|
1897
1785
|
};
|
|
1786
|
+
const finalizeWithoutReceipt = (logicalContent, canceled = false) => {
|
|
1787
|
+
const pending = pendingFinalPayload;
|
|
1788
|
+
pendingFinalPayload = void 0;
|
|
1789
|
+
const fallback = pending && !canceled ? fallbackPayloadAfterAcknowledgedText(pending) : void 0;
|
|
1790
|
+
const postNativePayloads = replacementSettlementPending && !canceled ? takeDeferredReplacementPayloads(fallback) : [];
|
|
1791
|
+
if (canceled) deferredReplacementEntries = [];
|
|
1792
|
+
return {
|
|
1793
|
+
...acknowledgedNativeDelivery(),
|
|
1794
|
+
...!canceled && logicalContent ? { logicalContent } : {},
|
|
1795
|
+
...!replacementSettlementPending && fallback ? { fallbackPayload: fallback } : {},
|
|
1796
|
+
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
1797
|
+
};
|
|
1798
|
+
};
|
|
1898
1799
|
const progressDraft = (0, openclaw_plugin_sdk_channel_outbound.createChannelProgressDraftCompositor)({
|
|
1899
1800
|
preparedItems: true,
|
|
1900
1801
|
formatPlainText: (text) => text,
|
|
@@ -1915,7 +1816,6 @@ function createTeamsReplyStreamController(params) {
|
|
|
1915
1816
|
}
|
|
1916
1817
|
});
|
|
1917
1818
|
return {
|
|
1918
|
-
async onReplyStart() {},
|
|
1919
1819
|
onPartialReply(payload) {
|
|
1920
1820
|
if (!stream || !payload.text || wasCanceled() || streamMode !== "partial") return;
|
|
1921
1821
|
if (replacementSettlementPending && replacementFinalPending) {
|
|
@@ -1957,29 +1857,16 @@ function createTeamsReplyStreamController(params) {
|
|
|
1957
1857
|
},
|
|
1958
1858
|
pushItemEvent: progressDraft.pushItemEvent.bind(progressDraft),
|
|
1959
1859
|
pushToolEvent: progressDraft.pushToolEvent,
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
async pushApprovalEvent(payload) {
|
|
1965
|
-
await progressDraft.pushApprovalEvent(payload);
|
|
1966
|
-
},
|
|
1967
|
-
async pushPlanProgress(steps, options) {
|
|
1968
|
-
await progressDraft.pushPlanProgress(steps, options);
|
|
1969
|
-
},
|
|
1860
|
+
pushReasoningProgress: progressDraft.pushReasoningProgress.bind(progressDraft),
|
|
1861
|
+
resetReasoningProgress: progressDraft.resetReasoningProgress,
|
|
1862
|
+
pushApprovalEvent: progressDraft.pushApprovalEvent.bind(progressDraft),
|
|
1863
|
+
pushPlanProgress: progressDraft.pushPlanProgress.bind(progressDraft),
|
|
1970
1864
|
preparePayload(payload) {
|
|
1971
1865
|
if (!stream) return payload;
|
|
1972
1866
|
if (wasCanceled()) return;
|
|
1973
1867
|
if (payload.text) progressDraft.markFinalReplyStarted();
|
|
1974
1868
|
if (replacementSettlementPending) {
|
|
1975
|
-
if (!replacementFinalPending) {
|
|
1976
|
-
deferredReplacementEntries.push({
|
|
1977
|
-
kind: "payload",
|
|
1978
|
-
payload
|
|
1979
|
-
});
|
|
1980
|
-
return;
|
|
1981
|
-
}
|
|
1982
|
-
if (!payload.text) {
|
|
1869
|
+
if (!replacementFinalPending || !payload.text) {
|
|
1983
1870
|
deferredReplacementEntries.push({
|
|
1984
1871
|
kind: "payload",
|
|
1985
1872
|
payload
|
|
@@ -2104,24 +1991,10 @@ function createTeamsReplyStreamController(params) {
|
|
|
2104
1991
|
}
|
|
2105
1992
|
const result = await stream.close();
|
|
2106
1993
|
streamFinalizationPending = false;
|
|
2107
|
-
if (!result)
|
|
2108
|
-
const fallback = pendingFinalPayload;
|
|
2109
|
-
pendingFinalPayload = void 0;
|
|
2110
|
-
const canceled = wasCanceled();
|
|
2111
|
-
const replacementFallback = replacementSettlementPending && fallback && !canceled ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
2112
|
-
const fallbackPayload = !replacementSettlementPending && fallback && !canceled ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
2113
|
-
const postNativePayloads = replacementSettlementPending && !canceled ? takeDeferredReplacementPayloads(replacementFallback) : [];
|
|
2114
|
-
if (canceled) deferredReplacementEntries = [];
|
|
2115
|
-
return {
|
|
2116
|
-
...acknowledgedNativeDelivery(),
|
|
2117
|
-
...!canceled && logicalContent ? { logicalContent } : {},
|
|
2118
|
-
...fallbackPayload ? { fallbackPayload } : {},
|
|
2119
|
-
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
2120
|
-
};
|
|
2121
|
-
}
|
|
1994
|
+
if (!result) return finalizeWithoutReceipt(logicalContent, wasCanceled());
|
|
2122
1995
|
const replacementFallback = replacementSettlementPending && replacementEmitFailed && pendingFinalPayload ? fallbackPayloadAfterAcknowledgedText(pendingFinalPayload) : void 0;
|
|
2123
1996
|
pendingFinalPayload = void 0;
|
|
2124
|
-
const messageId =
|
|
1997
|
+
const messageId = require_conversation_store_state.extractMessageId(result) ?? acknowledgedStreamId;
|
|
2125
1998
|
const postNativePayloads = replacementSettlementPending ? takeDeferredReplacementPayloads(replacementFallback) : [];
|
|
2126
1999
|
const nativeContent = replacementEmitFailed ? acknowledgedText || void 0 : content;
|
|
2127
2000
|
return {
|
|
@@ -2142,17 +2015,7 @@ function createTeamsReplyStreamController(params) {
|
|
|
2142
2015
|
streamFailed = true;
|
|
2143
2016
|
streamFinalizationPending = false;
|
|
2144
2017
|
params.log?.warn?.(`msteams stream finalize failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
|
|
2145
|
-
|
|
2146
|
-
pendingFinalPayload = void 0;
|
|
2147
|
-
const replacementFallback = replacementSettlementPending && fallback ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
2148
|
-
const fallbackPayload = !replacementSettlementPending && fallback ? fallbackPayloadAfterAcknowledgedText(fallback) : void 0;
|
|
2149
|
-
const postNativePayloads = replacementSettlementPending ? takeDeferredReplacementPayloads(replacementFallback) : [];
|
|
2150
|
-
return {
|
|
2151
|
-
...acknowledgedNativeDelivery(),
|
|
2152
|
-
...logicalContent ? { logicalContent } : {},
|
|
2153
|
-
...fallbackPayload ? { fallbackPayload } : {},
|
|
2154
|
-
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
2155
|
-
};
|
|
2018
|
+
return finalizeWithoutReceipt(logicalContent);
|
|
2156
2019
|
} finally {
|
|
2157
2020
|
failedSegmentFallbackPrepared = true;
|
|
2158
2021
|
queuedFinalActivity = void 0;
|
|
@@ -2180,43 +2043,22 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2180
2043
|
const msteamsCfg = params.cfg.channels?.msteams;
|
|
2181
2044
|
const conversationType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(params.conversationRef.conversation?.conversationType);
|
|
2182
2045
|
const isTypingSupported = conversationType === "personal" || conversationType === "groupchat";
|
|
2183
|
-
/**
|
|
2184
|
-
* Keepalive cadence for the typing indicator while the bot is running
|
|
2185
|
-
* (including long tool chains). Bot Framework 1:1 TurnContext proxies
|
|
2186
|
-
* expire after ~30s of inactivity; sending a typing activity every 8s
|
|
2187
|
-
* keeps the proxy alive so the post-tool reply can still land via the
|
|
2188
|
-
* turn context. Sits in the middle of the 5-10s range recommended in
|
|
2189
|
-
* #59731.
|
|
2190
|
-
*/
|
|
2191
2046
|
const TYPING_KEEPALIVE_INTERVAL_MS = 8e3;
|
|
2192
|
-
/**
|
|
2193
|
-
* TTL ceiling for the typing keepalive loop. The default in
|
|
2194
|
-
* createTypingCallbacks is 60s, which is too short for the Teams long tool
|
|
2195
|
-
* chains described in #59731 (60s+ total runs are common). Give tool
|
|
2196
|
-
* chains up to 10 minutes before auto-stopping the keepalive.
|
|
2197
|
-
*/
|
|
2198
2047
|
const TYPING_KEEPALIVE_MAX_DURATION_MS = 6e5;
|
|
2199
|
-
const
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
await require_messenger.withRevokedProxyFallback({
|
|
2048
|
+
const sendTypingIndicator = async () => {
|
|
2049
|
+
if (!isTypingSupported || streamController.isStreamActive() || streamController.wasCanceled()) return;
|
|
2050
|
+
await require_conversation_store_state.withRevokedProxyFallback({
|
|
2203
2051
|
run: async () => {
|
|
2204
2052
|
await params.context.sendActivity({ type: "typing" });
|
|
2205
2053
|
},
|
|
2206
2054
|
onRevoked: async () => {
|
|
2207
|
-
|
|
2208
|
-
await require_messenger.sendMSTeamsActivityWithReference(params.app, baseRef, { type: "typing" }, { serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(msteamsCfg) });
|
|
2055
|
+
await require_conversation_store_state.sendMSTeamsActivityWithReference(params.app, require_conversation_store_state.buildConversationReference(params.conversationRef), { type: "typing" }, { serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(msteamsCfg) });
|
|
2209
2056
|
},
|
|
2210
2057
|
onRevokedLog: () => {
|
|
2211
2058
|
params.log.debug?.("turn context revoked, sending typing via proactive messaging");
|
|
2212
2059
|
}
|
|
2213
2060
|
});
|
|
2214
2061
|
};
|
|
2215
|
-
const sendTypingIndicator = isTypingSupported ? async () => {
|
|
2216
|
-
if (streamActiveRef.current()) return;
|
|
2217
|
-
if (streamCanceledRef.current()) return;
|
|
2218
|
-
await rawSendTypingIndicator();
|
|
2219
|
-
} : async () => {};
|
|
2220
2062
|
const { onModelSelected, typingCallbacks, ...replyPipeline } = (0, openclaw_plugin_sdk_channel_outbound.createChannelMessageReplyPipeline)({
|
|
2221
2063
|
cfg: params.cfg,
|
|
2222
2064
|
agentId: params.agentId,
|
|
@@ -2257,8 +2099,6 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2257
2099
|
msteamsConfig: msteamsCfg,
|
|
2258
2100
|
progressSeed: `${params.accountId ?? "default"}:${params.conversationRef.conversation?.id ?? ""}`
|
|
2259
2101
|
});
|
|
2260
|
-
streamActiveRef.current = () => streamController.isStreamActive();
|
|
2261
|
-
streamCanceledRef.current = () => streamController.wasCanceled();
|
|
2262
2102
|
const teamsStreamMode = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelPreviewStreamMode)(msteamsCfg, "partial");
|
|
2263
2103
|
const blockStreamingResolved = teamsStreamMode === "block" ? true : (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingBlockEnabled)(msteamsCfg);
|
|
2264
2104
|
const blockStreamingEnabled = blockStreamingResolved ?? false;
|
|
@@ -2268,7 +2108,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2268
2108
|
const findPendingNativeDelivery = () => pendingDeliveries.find((candidate) => candidate.native && !candidate.nativeSettled);
|
|
2269
2109
|
const joinAcceptedContents = (contents) => contents.filter((content) => Boolean(content)).join("\n");
|
|
2270
2110
|
const sendMessages = async (messages) => {
|
|
2271
|
-
return
|
|
2111
|
+
return require_conversation_store_state.sendMSTeamsMessages({
|
|
2272
2112
|
replyStyle: params.replyStyle,
|
|
2273
2113
|
app: params.app,
|
|
2274
2114
|
appId: params.appId,
|
|
@@ -2307,7 +2147,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2307
2147
|
});
|
|
2308
2148
|
};
|
|
2309
2149
|
const renderReplyPayload = (payload) => {
|
|
2310
|
-
return
|
|
2150
|
+
return require_conversation_store_state.renderReplyPayloadsToMessages([payload], {
|
|
2311
2151
|
textChunkLimit: params.textLimit,
|
|
2312
2152
|
chunkText: true,
|
|
2313
2153
|
mediaMode: "split",
|
|
@@ -2400,7 +2240,6 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2400
2240
|
...replyPipeline,
|
|
2401
2241
|
humanDelay: (0, openclaw_plugin_sdk_agent_runtime.resolveHumanDelayConfig)(params.cfg, params.agentId),
|
|
2402
2242
|
onReplyStart: async () => {
|
|
2403
|
-
await streamController.onReplyStart();
|
|
2404
2243
|
if (typingIndicatorEnabled) await typingCallbacks?.onReplyStart?.();
|
|
2405
2244
|
},
|
|
2406
2245
|
typingCallbacks
|
|
@@ -2531,32 +2370,6 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2531
2370
|
};
|
|
2532
2371
|
}
|
|
2533
2372
|
//#endregion
|
|
2534
|
-
//#region extensions/msteams/src/sent-message-cache.ts
|
|
2535
|
-
const sentMessages = (0, openclaw_plugin_sdk_dedupe_runtime.createPersistentDedupeCache)({
|
|
2536
|
-
globalKey: Symbol.for("openclaw.msteamsSentMessages"),
|
|
2537
|
-
ttlMs: 864e5,
|
|
2538
|
-
maxSize: 2e4,
|
|
2539
|
-
persistent: {
|
|
2540
|
-
namespace: "msteams.sent-messages",
|
|
2541
|
-
maxEntries: 1e3,
|
|
2542
|
-
openStore: (options) => require_runtime.getOptionalMSTeamsRuntime()?.state.openKeyedStore(options),
|
|
2543
|
-
logError: (0, openclaw_plugin_sdk_plugin_state_runtime.createPluginStateErrorReporter)(require_runtime.getOptionalMSTeamsRuntime, "msteams", "sent-message-state", "Microsoft Teams persistent sent-message state failed"),
|
|
2544
|
-
readTimestamp: (record) => record.sentAt
|
|
2545
|
-
}
|
|
2546
|
-
});
|
|
2547
|
-
function makeKey(conversationId, messageId) {
|
|
2548
|
-
return `${conversationId}:${messageId}`;
|
|
2549
|
-
}
|
|
2550
|
-
function recordMSTeamsSentMessage(conversationId, messageId) {
|
|
2551
|
-
if (!conversationId || !messageId) return;
|
|
2552
|
-
const sentAt = Date.now();
|
|
2553
|
-
sentMessages.register(makeKey(conversationId, messageId), { sentAt }, { at: sentAt });
|
|
2554
|
-
}
|
|
2555
|
-
async function wasMSTeamsMessageSentWithPersistence(params) {
|
|
2556
|
-
if (!params.conversationId || !params.messageId) return false;
|
|
2557
|
-
return await sentMessages.lookup(makeKey(params.conversationId, params.messageId));
|
|
2558
|
-
}
|
|
2559
|
-
//#endregion
|
|
2560
2373
|
//#region extensions/msteams/src/monitor-handler/inbound-dispatch.ts
|
|
2561
2374
|
async function dispatchMSTeamsInboundTurn(params) {
|
|
2562
2375
|
const core = require_runtime.getMSTeamsRuntime();
|
|
@@ -2603,7 +2416,7 @@ async function dispatchMSTeamsInboundTurn(params) {
|
|
|
2603
2416
|
isGroup: !isDirectMessage,
|
|
2604
2417
|
groupPolicy,
|
|
2605
2418
|
allowFrom: effectiveGroupAllowFrom,
|
|
2606
|
-
isSenderAllowed: (allowFrom) =>
|
|
2419
|
+
isSenderAllowed: (allowFrom) => (0, openclaw_plugin_sdk_allow_from.resolveAllowlistMatchSimple)({
|
|
2607
2420
|
allowFrom,
|
|
2608
2421
|
senderId: quoteSenderId ?? "",
|
|
2609
2422
|
senderName: quoteSenderName,
|
|
@@ -2711,7 +2524,7 @@ async function dispatchMSTeamsInboundTurn(params) {
|
|
|
2711
2524
|
replyStyle,
|
|
2712
2525
|
textLimit,
|
|
2713
2526
|
onSentMessageIds: (ids) => {
|
|
2714
|
-
for (const id of ids) recordMSTeamsSentMessage(conversationId, id);
|
|
2527
|
+
for (const id of ids) require_conversation_store_state.recordMSTeamsSentMessage(conversationId, id);
|
|
2715
2528
|
},
|
|
2716
2529
|
tokenProvider,
|
|
2717
2530
|
sharePointSiteId: cfg.channels?.msteams?.sharePointSiteId
|
|
@@ -2805,7 +2618,7 @@ async function prepareMSTeamsDebounceEntry(params) {
|
|
|
2805
2618
|
const text = rawText ? require_resolve_allowlist.stripMSTeamsMentionTags(rawText) : extractTextFromHtmlAttachments(attachments) || require_resolve_allowlist.stripMSTeamsMentionTags(serializeMSTeamsAdaptiveCardActionValue(activity.value) || "");
|
|
2806
2619
|
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
2807
2620
|
const replyToId = activity.replyToId ?? void 0;
|
|
2808
|
-
const implicitMentionKinds = conversationId && replyToId && await wasMSTeamsMessageSentWithPersistence({
|
|
2621
|
+
const implicitMentionKinds = conversationId && replyToId && await require_conversation_store_state.wasMSTeamsMessageSentWithPersistence({
|
|
2809
2622
|
conversationId,
|
|
2810
2623
|
messageId: replyToId
|
|
2811
2624
|
}) ? ["reply_to_bot"] : [];
|
|
@@ -2826,7 +2639,7 @@ function buildStoredConversationReference(params) {
|
|
|
2826
2639
|
const clientInfo = activity.entities?.find((entity) => entity.type === "clientInfo");
|
|
2827
2640
|
const tenantId = activity.channelData?.tenant?.id ?? conversation?.tenantId;
|
|
2828
2641
|
const aadObjectId = from?.aadObjectId;
|
|
2829
|
-
const serviceUrl =
|
|
2642
|
+
const serviceUrl = require_bot_framework_service_url.tryNormalizeBotFrameworkServiceUrl(activity.serviceUrl);
|
|
2830
2643
|
return {
|
|
2831
2644
|
activityId: activity.id,
|
|
2832
2645
|
user: from ? {
|
|
@@ -2925,12 +2738,9 @@ const parentCache = /* @__PURE__ */ new Map();
|
|
|
2925
2738
|
const INJECTED_MAX = 200;
|
|
2926
2739
|
const injectedParents = /* @__PURE__ */ new Map();
|
|
2927
2740
|
function touchLru(map, key, value, max) {
|
|
2928
|
-
|
|
2929
|
-
else if (map.size >= max) {
|
|
2930
|
-
const firstKey = map.keys().next().value;
|
|
2931
|
-
if (firstKey !== void 0) map.delete(firstKey);
|
|
2932
|
-
}
|
|
2741
|
+
map.delete(key);
|
|
2933
2742
|
map.set(key, value);
|
|
2743
|
+
(0, openclaw_plugin_sdk_collection_runtime.pruneMapToMaxSize)(map, max);
|
|
2934
2744
|
}
|
|
2935
2745
|
function buildParentCacheKey(groupId, channelId, parentId) {
|
|
2936
2746
|
return `${groupId}\u0000${channelId}\u0000${parentId}`;
|
|
@@ -2939,12 +2749,6 @@ function resolveParentCacheExpiresAt(nowRaw) {
|
|
|
2939
2749
|
const nowMs = (0, openclaw_plugin_sdk_number_runtime.asDateTimestampMs)(nowRaw);
|
|
2940
2750
|
return nowMs === void 0 ? void 0 : (0, openclaw_plugin_sdk_number_runtime.resolveExpiresAtMsFromDurationMs)(PARENT_CACHE_TTL_MS, { nowMs });
|
|
2941
2751
|
}
|
|
2942
|
-
/**
|
|
2943
|
-
* Fetch a channel parent message with an LRU+TTL cache.
|
|
2944
|
-
*
|
|
2945
|
-
* Uses the injected `fetchParent` (defaults to `fetchChannelMessage`) so
|
|
2946
|
-
* tests can swap in a stub without mocking the Graph transport.
|
|
2947
|
-
*/
|
|
2948
2752
|
async function fetchParentMessageCached(token, groupId, channelId, parentId, fetchParent = require_reaction_types.fetchChannelMessage) {
|
|
2949
2753
|
const key = buildParentCacheKey(groupId, channelId, parentId);
|
|
2950
2754
|
const now = (0, openclaw_plugin_sdk_number_runtime.asDateTimestampMs)(Date.now());
|
|
@@ -2965,11 +2769,6 @@ async function fetchParentMessageCached(token, groupId, channelId, parentId, fet
|
|
|
2965
2769
|
return message;
|
|
2966
2770
|
}
|
|
2967
2771
|
const PARENT_TEXT_MAX_CHARS = 400;
|
|
2968
|
-
/**
|
|
2969
|
-
* Extract a compact summary (sender + plain-text body) from a Graph parent
|
|
2970
|
-
* message. Returns undefined when the parent cannot be summarized (missing
|
|
2971
|
-
* or blank body).
|
|
2972
|
-
*/
|
|
2973
2772
|
function summarizeParentMessage(message) {
|
|
2974
2773
|
if (!message) return;
|
|
2975
2774
|
const sender = message.from?.user?.displayName ?? message.from?.application?.displayName ?? "unknown";
|
|
@@ -2982,28 +2781,12 @@ function summarizeParentMessage(message) {
|
|
|
2982
2781
|
text: text.length > PARENT_TEXT_MAX_CHARS ? `${(0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(text, 399)}…` : text
|
|
2983
2782
|
};
|
|
2984
2783
|
}
|
|
2985
|
-
/**
|
|
2986
|
-
* Build the single-line `Replying to @sender: body` system event text.
|
|
2987
|
-
* Callers should pass this text to `enqueueSystemEvent` together with a
|
|
2988
|
-
* stable contextKey derived from the parent id.
|
|
2989
|
-
*/
|
|
2990
2784
|
function formatParentContextEvent(summary) {
|
|
2991
2785
|
return `Replying to @${summary.sender}: ${summary.text}`;
|
|
2992
2786
|
}
|
|
2993
|
-
/**
|
|
2994
|
-
* Decide whether a parent context event should be enqueued for the current
|
|
2995
|
-
* session. Returns `false` when we already injected the same parent for this
|
|
2996
|
-
* session recently (prevents re-prepending identical context on every reply
|
|
2997
|
-
* in the thread).
|
|
2998
|
-
*/
|
|
2999
2787
|
function shouldInjectParentContext(sessionKey, parentId) {
|
|
3000
|
-
|
|
3001
|
-
return injectedParents.get(key) !== parentId;
|
|
2788
|
+
return injectedParents.get(sessionKey) !== parentId;
|
|
3002
2789
|
}
|
|
3003
|
-
/**
|
|
3004
|
-
* Record that `parentId` was just injected for `sessionKey` so subsequent
|
|
3005
|
-
* replies with the same parent can short-circuit via `shouldInjectParentContext`.
|
|
3006
|
-
*/
|
|
3007
2790
|
function markParentContextInjected(sessionKey, parentId) {
|
|
3008
2791
|
touchLru(injectedParents, sessionKey, parentId, INJECTED_MAX);
|
|
3009
2792
|
}
|
|
@@ -3097,7 +2880,7 @@ async function resolveMSTeamsThreadContext(params) {
|
|
|
3097
2880
|
isGroup: params.isChannel,
|
|
3098
2881
|
groupPolicy: params.groupPolicy,
|
|
3099
2882
|
allowFrom: params.effectiveGroupAllowFrom,
|
|
3100
|
-
isSenderAllowed: (allowFrom) =>
|
|
2883
|
+
isSenderAllowed: (allowFrom) => (0, openclaw_plugin_sdk_allow_from.resolveAllowlistMatchSimple)({
|
|
3101
2884
|
allowFrom,
|
|
3102
2885
|
senderId: message.from?.user?.id ?? "",
|
|
3103
2886
|
senderName: message.from?.user?.displayName,
|
|
@@ -3177,8 +2960,35 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
3177
2960
|
log.debug?.("skipping message without from.id");
|
|
3178
2961
|
return;
|
|
3179
2962
|
}
|
|
2963
|
+
let currentCfg = readConfig();
|
|
2964
|
+
let isBotOwnedThread = false;
|
|
2965
|
+
if (isChannel && facts.threadId && activity.recipient?.id) {
|
|
2966
|
+
const channelConfig = currentCfg.channels?.msteams;
|
|
2967
|
+
const routeConfig = require_channel.resolveMSTeamsRouteConfig({
|
|
2968
|
+
cfg: channelConfig,
|
|
2969
|
+
teamId,
|
|
2970
|
+
teamName: activity.channelData?.team?.name,
|
|
2971
|
+
conversationId,
|
|
2972
|
+
channelName: activity.channelData?.channel?.name,
|
|
2973
|
+
allowNameMatching: channelConfig?.dangerouslyAllowNameMatching === true
|
|
2974
|
+
});
|
|
2975
|
+
if (require_channel.resolveMSTeamsReplyPolicy({
|
|
2976
|
+
isDirectMessage: false,
|
|
2977
|
+
globalConfig: channelConfig,
|
|
2978
|
+
...routeConfig
|
|
2979
|
+
}).requireMentionInBotThreads !== void 0) {
|
|
2980
|
+
isBotOwnedThread = await require_conversation_store_state.wasMSTeamsMessageSentWithPersistence({
|
|
2981
|
+
conversationId,
|
|
2982
|
+
messageId: facts.threadId,
|
|
2983
|
+
botId: activity.recipient.id
|
|
2984
|
+
});
|
|
2985
|
+
currentCfg = readConfig();
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
const currentMSTeamsCfg = currentCfg.channels?.msteams;
|
|
2989
|
+
if (currentMSTeamsCfg?.enabled === false || currentMSTeamsCfg?.appId && currentMSTeamsCfg.appId !== appId) return;
|
|
3180
2990
|
const admission = await admitMSTeamsMessage({
|
|
3181
|
-
cfg,
|
|
2991
|
+
cfg: currentCfg,
|
|
3182
2992
|
activity,
|
|
3183
2993
|
text,
|
|
3184
2994
|
conversationId,
|
|
@@ -3190,7 +3000,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
3190
3000
|
});
|
|
3191
3001
|
if (!admission) return;
|
|
3192
3002
|
const { senderId, senderName, isDirectMessage, channelGate, allowNameMatching, groupPolicy, commandAuthorized, effectiveGroupAllowFrom, allowTextCommands, isControlCommand } = admission;
|
|
3193
|
-
const pollVote =
|
|
3003
|
+
const pollVote = require_conversation_store_state.extractMSTeamsPollVote(activity);
|
|
3194
3004
|
if (pollVote) {
|
|
3195
3005
|
try {
|
|
3196
3006
|
if (!await pollStore.recordVote({
|
|
@@ -3212,7 +3022,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
3212
3022
|
return;
|
|
3213
3023
|
}
|
|
3214
3024
|
const threadRouting = prepareMSTeamsThreadRouting({
|
|
3215
|
-
cfg,
|
|
3025
|
+
cfg: currentCfg,
|
|
3216
3026
|
context,
|
|
3217
3027
|
isDirectMessage,
|
|
3218
3028
|
isChannel,
|
|
@@ -3230,18 +3040,25 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
3230
3040
|
});
|
|
3231
3041
|
const channelId = conversationId;
|
|
3232
3042
|
const { teamConfig, channelConfig } = channelGate;
|
|
3233
|
-
const
|
|
3043
|
+
const replyPolicy = require_channel.resolveMSTeamsReplyPolicy({
|
|
3234
3044
|
isDirectMessage,
|
|
3235
|
-
globalConfig:
|
|
3045
|
+
globalConfig: currentMSTeamsCfg,
|
|
3236
3046
|
teamConfig,
|
|
3237
3047
|
channelConfig
|
|
3238
3048
|
});
|
|
3049
|
+
const { requireMention, implicitMentionKinds } = resolveBotThreadMentionPolicy({
|
|
3050
|
+
isBotOwnedThread,
|
|
3051
|
+
requireMention: replyPolicy.requireMention,
|
|
3052
|
+
requireMentionInBotThreads: replyPolicy.requireMentionInBotThreads,
|
|
3053
|
+
implicitMentionKinds: params.implicitMentionKinds
|
|
3054
|
+
});
|
|
3055
|
+
const { replyStyle } = replyPolicy;
|
|
3239
3056
|
const timestamp = require_resolve_allowlist.parseMSTeamsActivityTimestamp(activity.timestamp);
|
|
3240
3057
|
const mentionDecision = (0, openclaw_plugin_sdk_channel_inbound.resolveInboundMentionDecision)({
|
|
3241
3058
|
facts: {
|
|
3242
3059
|
canDetectMention: true,
|
|
3243
3060
|
wasMentioned: params.wasMentioned,
|
|
3244
|
-
implicitMentionKinds
|
|
3061
|
+
implicitMentionKinds
|
|
3245
3062
|
},
|
|
3246
3063
|
policy: {
|
|
3247
3064
|
isGroup: !isDirectMessage,
|
|
@@ -3288,9 +3105,9 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
3288
3105
|
resolveTeamAadGroupId: threadRouting.resolveTeamAadGroupId,
|
|
3289
3106
|
mediaMaxBytes,
|
|
3290
3107
|
tokenProvider,
|
|
3291
|
-
mediaAllowHosts:
|
|
3292
|
-
mediaAuthAllowHosts:
|
|
3293
|
-
graphMediaFallback:
|
|
3108
|
+
mediaAllowHosts: currentMSTeamsCfg?.mediaAllowHosts,
|
|
3109
|
+
mediaAuthAllowHosts: currentMSTeamsCfg?.mediaAuthAllowHosts,
|
|
3110
|
+
graphMediaFallback: currentMSTeamsCfg?.graphMediaFallback,
|
|
3294
3111
|
deadline: preprocessingDeadline,
|
|
3295
3112
|
log
|
|
3296
3113
|
});
|
|
@@ -3311,7 +3128,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
3311
3128
|
log
|
|
3312
3129
|
});
|
|
3313
3130
|
await dispatchMSTeamsInboundTurn({
|
|
3314
|
-
cfg,
|
|
3131
|
+
cfg: currentCfg,
|
|
3315
3132
|
runtime,
|
|
3316
3133
|
appId,
|
|
3317
3134
|
app,
|
|
@@ -3336,10 +3153,19 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
3336
3153
|
debounceMs: resolveDebounceMs(),
|
|
3337
3154
|
resolveDebounceMs,
|
|
3338
3155
|
buildKey: (entry) => {
|
|
3339
|
-
const
|
|
3156
|
+
const activity = entry.context.activity;
|
|
3157
|
+
const rawConversationId = activity.conversation?.id ?? "";
|
|
3158
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(rawConversationId);
|
|
3159
|
+
const threadId = activity.conversation?.conversationType === "channel" ? require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId) ?? activity.replyToId : void 0;
|
|
3340
3160
|
const senderId = entry.context.activity.from?.aadObjectId ?? entry.context.activity.from?.id ?? "";
|
|
3341
3161
|
if (!senderId || !conversationId) return null;
|
|
3342
|
-
return
|
|
3162
|
+
return JSON.stringify([
|
|
3163
|
+
"msteams",
|
|
3164
|
+
appId,
|
|
3165
|
+
conversationId,
|
|
3166
|
+
threadId ?? null,
|
|
3167
|
+
senderId
|
|
3168
|
+
]);
|
|
3343
3169
|
},
|
|
3344
3170
|
shouldDebounce: (entry) => {
|
|
3345
3171
|
if (!entry.text.trim()) return false;
|
|
@@ -3612,56 +3438,10 @@ async function isCardActionInvokeAuthorized(context, deps) {
|
|
|
3612
3438
|
includeInvokeName: true
|
|
3613
3439
|
});
|
|
3614
3440
|
}
|
|
3615
|
-
function
|
|
3441
|
+
function createMSTeamsActivityHandler(deps) {
|
|
3616
3442
|
const handleTeamsMessage = createMSTeamsMessageHandler(deps);
|
|
3617
3443
|
const handleReaction = createMSTeamsReactionHandler(deps);
|
|
3618
|
-
const
|
|
3619
|
-
if (originalRun) handler.run = async (context, turnAdoptionLifecycle) => {
|
|
3620
|
-
const ctx = context;
|
|
3621
|
-
if (ctx.activity?.type === "invoke" && ctx.activity?.name === "adaptiveCard/action") {
|
|
3622
|
-
if (await maybeHandleMSTeamsApprovalCardSubmit({
|
|
3623
|
-
context: ctx,
|
|
3624
|
-
deps
|
|
3625
|
-
})) return;
|
|
3626
|
-
const text = serializeMSTeamsAdaptiveCardActionValue(ctx.activity?.value);
|
|
3627
|
-
if (text) return await handleTeamsMessage({
|
|
3628
|
-
...ctx,
|
|
3629
|
-
activity: {
|
|
3630
|
-
...ctx.activity,
|
|
3631
|
-
type: "message",
|
|
3632
|
-
text
|
|
3633
|
-
}
|
|
3634
|
-
}, turnAdoptionLifecycle);
|
|
3635
|
-
return;
|
|
3636
|
-
}
|
|
3637
|
-
return originalRun.call(handler, context, turnAdoptionLifecycle);
|
|
3638
|
-
};
|
|
3639
|
-
handler.onMessage(async (context, next, turnAdoptionLifecycle) => {
|
|
3640
|
-
let nextRan = false;
|
|
3641
|
-
const runNext = async () => {
|
|
3642
|
-
nextRan = true;
|
|
3643
|
-
await next();
|
|
3644
|
-
};
|
|
3645
|
-
try {
|
|
3646
|
-
const ctx = context;
|
|
3647
|
-
if (await maybeHandleMSTeamsApprovalCardSubmit({
|
|
3648
|
-
context: ctx,
|
|
3649
|
-
deps
|
|
3650
|
-
})) {
|
|
3651
|
-
await runNext();
|
|
3652
|
-
return;
|
|
3653
|
-
}
|
|
3654
|
-
const result = await handleTeamsMessage(ctx, turnAdoptionLifecycle);
|
|
3655
|
-
await runNext();
|
|
3656
|
-
return result;
|
|
3657
|
-
} catch (err) {
|
|
3658
|
-
if (turnAdoptionLifecycle) throw err;
|
|
3659
|
-
deps.runtime.error(`msteams handler failed: ${require_channel_setup.formatUnknownError(err)}`);
|
|
3660
|
-
}
|
|
3661
|
-
if (!nextRan) await runNext();
|
|
3662
|
-
});
|
|
3663
|
-
handler.onMembersAdded(async (context, next) => {
|
|
3664
|
-
const ctx = context;
|
|
3444
|
+
const handleMembersAdded = async (ctx) => {
|
|
3665
3445
|
const membersAdded = ctx.activity?.membersAdded ?? [];
|
|
3666
3446
|
const botId = ctx.activity?.recipient?.id;
|
|
3667
3447
|
const msteamsCfg = deps.cfg.channels?.msteams;
|
|
@@ -3673,13 +3453,7 @@ function registerMSTeamsHandlers(handler, deps) {
|
|
|
3673
3453
|
promptStarters: msteamsCfg?.promptStarters
|
|
3674
3454
|
});
|
|
3675
3455
|
try {
|
|
3676
|
-
await ctx.sendActivity(
|
|
3677
|
-
type: "message",
|
|
3678
|
-
attachments: [{
|
|
3679
|
-
contentType: "application/vnd.microsoft.card.adaptive",
|
|
3680
|
-
content: card
|
|
3681
|
-
}]
|
|
3682
|
-
});
|
|
3456
|
+
await ctx.sendActivity(require_conversation_store_state.buildMSTeamsAdaptiveCardActivity(card));
|
|
3683
3457
|
deps.log.info("sent welcome card");
|
|
3684
3458
|
} catch (err) {
|
|
3685
3459
|
deps.log.debug?.("failed to send welcome card", { error: require_channel_setup.formatUnknownError(err) });
|
|
@@ -3694,25 +3468,45 @@ function registerMSTeamsHandlers(handler, deps) {
|
|
|
3694
3468
|
}
|
|
3695
3469
|
} else deps.log.debug?.("skipping welcome (disabled by config or conversation type)");
|
|
3696
3470
|
} else deps.log.debug?.("member added", { member: member.id });
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
await
|
|
3702
|
-
|
|
3703
|
-
|
|
3471
|
+
};
|
|
3472
|
+
return async (context, turnAdoptionLifecycle) => {
|
|
3473
|
+
const activity = context.activity;
|
|
3474
|
+
if (activity?.type === "invoke" && activity.name === "adaptiveCard/action") {
|
|
3475
|
+
if (await maybeHandleMSTeamsApprovalCardSubmit({
|
|
3476
|
+
context,
|
|
3477
|
+
deps
|
|
3478
|
+
})) return;
|
|
3479
|
+
const text = serializeMSTeamsAdaptiveCardActionValue(activity.value);
|
|
3480
|
+
if (text) return handleTeamsMessage({
|
|
3481
|
+
...context,
|
|
3482
|
+
activity: {
|
|
3483
|
+
...activity,
|
|
3484
|
+
type: "message",
|
|
3485
|
+
text
|
|
3486
|
+
}
|
|
3487
|
+
}, turnAdoptionLifecycle);
|
|
3488
|
+
return;
|
|
3704
3489
|
}
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3490
|
+
if (activity?.type === "message") try {
|
|
3491
|
+
if (await maybeHandleMSTeamsApprovalCardSubmit({
|
|
3492
|
+
context,
|
|
3493
|
+
deps
|
|
3494
|
+
})) return;
|
|
3495
|
+
return await handleTeamsMessage(context, turnAdoptionLifecycle);
|
|
3710
3496
|
} catch (err) {
|
|
3711
|
-
|
|
3497
|
+
if (turnAdoptionLifecycle) throw err;
|
|
3498
|
+
deps.runtime.error(`msteams handler failed: ${require_channel_setup.formatUnknownError(err)}`);
|
|
3712
3499
|
}
|
|
3713
|
-
await
|
|
3714
|
-
|
|
3715
|
-
|
|
3500
|
+
else if (activity?.type === "conversationUpdate") await handleMembersAdded(context);
|
|
3501
|
+
else if (activity?.type === "messageReaction") for (const direction of ["added", "removed"]) {
|
|
3502
|
+
if (!(direction === "added" ? activity.reactionsAdded : activity.reactionsRemoved)?.length) continue;
|
|
3503
|
+
try {
|
|
3504
|
+
await handleReaction(context, direction);
|
|
3505
|
+
} catch (err) {
|
|
3506
|
+
deps.runtime.error(`msteams reaction handler failed: ${String(err)}`);
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
};
|
|
3716
3510
|
}
|
|
3717
3511
|
//#endregion
|
|
3718
3512
|
//#region extensions/msteams/src/feedback-invoke.ts
|
|
@@ -3839,14 +3633,14 @@ async function handleMSTeamsFileConsentInvoke(context, log) {
|
|
|
3839
3633
|
const expiredUploadMessage = "The file upload request has expired. Please try sending the file again.";
|
|
3840
3634
|
const activity = context.activity;
|
|
3841
3635
|
if (activity.type !== "invoke" || activity.name !== "fileConsent/invoke") return false;
|
|
3842
|
-
const consentResponse =
|
|
3636
|
+
const consentResponse = require_conversation_store_state.parseFileConsentInvoke(activity);
|
|
3843
3637
|
if (!consentResponse) {
|
|
3844
3638
|
log.debug?.("invalid file consent invoke", { value: activity.value });
|
|
3845
3639
|
return false;
|
|
3846
3640
|
}
|
|
3847
3641
|
const uploadId = typeof consentResponse.context?.uploadId === "string" ? consentResponse.context.uploadId : void 0;
|
|
3848
|
-
const inMemoryFile =
|
|
3849
|
-
const fsFile = inMemoryFile ? void 0 : await
|
|
3642
|
+
const inMemoryFile = require_conversation_store_state.getPendingUpload(uploadId);
|
|
3643
|
+
const fsFile = inMemoryFile ? void 0 : await require_conversation_store_state.getPendingUploadFs(uploadId);
|
|
3850
3644
|
const pendingFile = inMemoryFile ?? fsFile;
|
|
3851
3645
|
if (pendingFile) {
|
|
3852
3646
|
const pendingConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(pendingFile.conversationId);
|
|
@@ -3869,33 +3663,29 @@ async function handleMSTeamsFileConsentInvoke(context, log) {
|
|
|
3869
3663
|
size: pendingFile.buffer.length
|
|
3870
3664
|
});
|
|
3871
3665
|
try {
|
|
3872
|
-
await
|
|
3666
|
+
await require_conversation_store_state.uploadToConsentUrl({
|
|
3873
3667
|
url: consentResponse.uploadInfo.uploadUrl,
|
|
3874
3668
|
buffer: pendingFile.buffer,
|
|
3875
3669
|
contentType: pendingFile.contentType
|
|
3876
3670
|
});
|
|
3877
|
-
const
|
|
3878
|
-
filename: consentResponse.uploadInfo.name,
|
|
3879
|
-
contentUrl: consentResponse.uploadInfo.contentUrl,
|
|
3880
|
-
uniqueId: consentResponse.uploadInfo.uniqueId,
|
|
3881
|
-
fileType: consentResponse.uploadInfo.fileType
|
|
3882
|
-
});
|
|
3883
|
-
if (!pendingFile.consentCardActivityId) await context.sendActivity({
|
|
3671
|
+
const fileActivity = {
|
|
3884
3672
|
type: "message",
|
|
3885
|
-
attachments: [
|
|
3886
|
-
|
|
3673
|
+
attachments: [require_conversation_store_state.buildFileInfoCard({
|
|
3674
|
+
filename: consentResponse.uploadInfo.name,
|
|
3675
|
+
contentUrl: consentResponse.uploadInfo.contentUrl,
|
|
3676
|
+
uniqueId: consentResponse.uploadInfo.uniqueId,
|
|
3677
|
+
fileType: consentResponse.uploadInfo.fileType
|
|
3678
|
+
})]
|
|
3679
|
+
};
|
|
3887
3680
|
if (pendingFile.consentCardActivityId) try {
|
|
3888
3681
|
await context.updateActivity({
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
attachments: [fileInfoCard]
|
|
3682
|
+
...fileActivity,
|
|
3683
|
+
id: pendingFile.consentCardActivityId
|
|
3892
3684
|
});
|
|
3893
3685
|
} catch {
|
|
3894
|
-
await context.sendActivity(
|
|
3895
|
-
type: "message",
|
|
3896
|
-
attachments: [fileInfoCard]
|
|
3897
|
-
});
|
|
3686
|
+
await context.sendActivity(fileActivity);
|
|
3898
3687
|
}
|
|
3688
|
+
else await context.sendActivity(fileActivity);
|
|
3899
3689
|
log.info("file upload complete", {
|
|
3900
3690
|
uploadId,
|
|
3901
3691
|
filename: consentResponse.uploadInfo.name,
|
|
@@ -3908,8 +3698,8 @@ async function handleMSTeamsFileConsentInvoke(context, log) {
|
|
|
3908
3698
|
});
|
|
3909
3699
|
await context.sendActivity("File upload failed. Please try again.");
|
|
3910
3700
|
} finally {
|
|
3911
|
-
|
|
3912
|
-
await
|
|
3701
|
+
require_conversation_store_state.removePendingUpload(uploadId);
|
|
3702
|
+
await require_conversation_store_state.removePendingUploadFs(uploadId);
|
|
3913
3703
|
}
|
|
3914
3704
|
} else {
|
|
3915
3705
|
log.debug?.("pending file not found for consent", { uploadId });
|
|
@@ -3917,8 +3707,8 @@ async function handleMSTeamsFileConsentInvoke(context, log) {
|
|
|
3917
3707
|
}
|
|
3918
3708
|
} else {
|
|
3919
3709
|
log.debug?.("user declined file consent", { uploadId });
|
|
3920
|
-
|
|
3921
|
-
await
|
|
3710
|
+
require_conversation_store_state.removePendingUpload(uploadId);
|
|
3711
|
+
await require_conversation_store_state.removePendingUploadFs(uploadId);
|
|
3922
3712
|
}
|
|
3923
3713
|
return true;
|
|
3924
3714
|
}
|
|
@@ -3929,7 +3719,7 @@ async function handleMSTeamsFileConsentInvoke(context, log) {
|
|
|
3929
3719
|
*/
|
|
3930
3720
|
async function runMSTeamsFileConsentInvokeHandler(context, log) {
|
|
3931
3721
|
try {
|
|
3932
|
-
await
|
|
3722
|
+
await require_conversation_store_state.withRevokedProxyFallback({
|
|
3933
3723
|
run: async () => await handleMSTeamsFileConsentInvoke(context, log),
|
|
3934
3724
|
onRevoked: async () => true,
|
|
3935
3725
|
onRevokedLog: () => {
|
|
@@ -3948,13 +3738,6 @@ function publishMSTeamsBlocked(statusSink, lastError) {
|
|
|
3948
3738
|
function publishMSTeamsReady(statusSink, now = Date.now()) {
|
|
3949
3739
|
statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelReadyPatch)({ lastConnectedAt: now }));
|
|
3950
3740
|
}
|
|
3951
|
-
function publishMSTeamsRecovering(statusSink, lastError) {
|
|
3952
|
-
statusSink?.({
|
|
3953
|
-
connected: false,
|
|
3954
|
-
lifecycle: "recovering",
|
|
3955
|
-
lastError
|
|
3956
|
-
});
|
|
3957
|
-
}
|
|
3958
3741
|
function publishMSTeamsStopped(statusSink) {
|
|
3959
3742
|
statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelStoppedPatch)());
|
|
3960
3743
|
}
|
|
@@ -4124,7 +3907,7 @@ function createMSTeamsReplayContext(activity, app, serviceUrlBoundary) {
|
|
|
4124
3907
|
...threadActivityId ? { threadActivityId } : {},
|
|
4125
3908
|
serviceUrlBoundary
|
|
4126
3909
|
};
|
|
4127
|
-
const sendActivity = (outbound) =>
|
|
3910
|
+
const sendActivity = (outbound) => require_conversation_store_state.sendMSTeamsActivityWithReference(app, reference, outbound, proactiveOptions);
|
|
4128
3911
|
return {
|
|
4129
3912
|
activity,
|
|
4130
3913
|
sendActivity,
|
|
@@ -4134,26 +3917,108 @@ function createMSTeamsReplayContext(activity, app, serviceUrlBoundary) {
|
|
|
4134
3917
|
return results;
|
|
4135
3918
|
},
|
|
4136
3919
|
updateActivity: async (outbound) => {
|
|
4137
|
-
return await
|
|
3920
|
+
return await require_conversation_store_state.updateMSTeamsActivityWithReference(app, reference, typeof outbound.id === "string" ? outbound.id : "", outbound, proactiveOptions);
|
|
4138
3921
|
},
|
|
4139
3922
|
deleteActivity: async (activityId) => {
|
|
4140
|
-
await
|
|
3923
|
+
await require_conversation_store_state.deleteMSTeamsActivityWithReference(app, reference, activityId, proactiveOptions);
|
|
4141
3924
|
},
|
|
4142
3925
|
getTeamDetails: (teamId) => app.api.teams.getById(teamId)
|
|
4143
3926
|
};
|
|
4144
3927
|
}
|
|
4145
3928
|
//#endregion
|
|
4146
|
-
//#region extensions/msteams/src/
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
3929
|
+
//#region extensions/msteams/src/sso-token-store.ts
|
|
3930
|
+
/**
|
|
3931
|
+
* SQLite-backed store for Bot Framework OAuth SSO tokens.
|
|
3932
|
+
*
|
|
3933
|
+
* Tokens are keyed by (connectionName, userId). `userId` should be the
|
|
3934
|
+
* stable AAD object ID (`activity.from.aadObjectId`) when available,
|
|
3935
|
+
* falling back to the Bot Framework `activity.from.id`.
|
|
3936
|
+
*
|
|
3937
|
+
* The store is intentionally minimal: it persists the exchanged user
|
|
3938
|
+
* token plus its expiration so consumers (for example tool handlers
|
|
3939
|
+
* that call Microsoft Graph with delegated permissions) can fetch a
|
|
3940
|
+
* valid token without reaching back into Bot Framework every turn.
|
|
3941
|
+
*/
|
|
3942
|
+
const MSTEAMS_SSO_TOKENS_NAMESPACE = "sso-tokens";
|
|
3943
|
+
const SSO_TOKEN_MUTATION_KEY = "sso-tokens";
|
|
3944
|
+
const MSTEAMS_MAX_SSO_TOKENS = 5e3;
|
|
3945
|
+
const STORE_KEY_VERSION_PREFIX = "v2:";
|
|
3946
|
+
function makeMSTeamsSsoTokenStoreKey(connectionName, userId) {
|
|
3947
|
+
return `${STORE_KEY_VERSION_PREFIX}${(0, node_crypto.createHash)("sha256").update(JSON.stringify([connectionName, userId])).digest("hex")}`;
|
|
3948
|
+
}
|
|
3949
|
+
function createTokenStore(params) {
|
|
3950
|
+
return require_runtime.getMSTeamsRuntime().state.openKeyedStore({
|
|
3951
|
+
namespace: MSTEAMS_SSO_TOKENS_NAMESPACE,
|
|
3952
|
+
maxEntries: MSTEAMS_MAX_SSO_TOKENS,
|
|
3953
|
+
env: require_conversation_store_state.resolveMSTeamsSqliteStateEnv(params)
|
|
3954
|
+
});
|
|
3955
|
+
}
|
|
3956
|
+
function createMSTeamsSsoTokenStoreFs(params) {
|
|
3957
|
+
const tokenStore = createTokenStore(params);
|
|
3958
|
+
return {
|
|
3959
|
+
async get({ connectionName, userId }) {
|
|
3960
|
+
return await tokenStore.lookup(makeMSTeamsSsoTokenStoreKey(connectionName, userId)) ?? null;
|
|
3961
|
+
},
|
|
3962
|
+
async save(token) {
|
|
3963
|
+
await require_conversation_store_state.withMSTeamsSqliteMutationLock(params, SSO_TOKEN_MUTATION_KEY, async () => {
|
|
3964
|
+
await tokenStore.register(makeMSTeamsSsoTokenStoreKey(token.connectionName, token.userId), require_conversation_store_state.toPluginJsonValue({ ...token }));
|
|
3965
|
+
});
|
|
3966
|
+
},
|
|
3967
|
+
async remove({ connectionName, userId }) {
|
|
3968
|
+
let removed = false;
|
|
3969
|
+
await require_conversation_store_state.withMSTeamsSqliteMutationLock(params, SSO_TOKEN_MUTATION_KEY, async () => {
|
|
3970
|
+
removed = await tokenStore.delete(makeMSTeamsSsoTokenStoreKey(connectionName, userId));
|
|
3971
|
+
});
|
|
3972
|
+
return removed;
|
|
3973
|
+
}
|
|
3974
|
+
};
|
|
3975
|
+
}
|
|
3976
|
+
//#endregion
|
|
3977
|
+
//#region extensions/msteams/src/webhook-handler.ts
|
|
3978
|
+
/** Keep Express response ownership until the channel's route can be released. */
|
|
3979
|
+
function createMSTeamsWebhookHandler(expressApp, warn) {
|
|
3980
|
+
const responses = /* @__PURE__ */ new Map();
|
|
3981
|
+
let closing = false;
|
|
3982
|
+
let closeTask;
|
|
3983
|
+
return {
|
|
3984
|
+
handler: ((req, res) => {
|
|
3985
|
+
if (closing) {
|
|
3986
|
+
res.writeHead(503, { "Retry-After": "1" });
|
|
3987
|
+
res.end("Service Unavailable");
|
|
3988
|
+
return Promise.resolve();
|
|
3989
|
+
}
|
|
3990
|
+
const response = (0, openclaw_plugin_sdk_extension_shared.createDeferred)();
|
|
3991
|
+
responses.set(res, response.promise);
|
|
3992
|
+
const done = () => {
|
|
3993
|
+
res.off("finish", done);
|
|
3994
|
+
res.off("close", done);
|
|
3995
|
+
responses.delete(res);
|
|
3996
|
+
response.resolve();
|
|
3997
|
+
};
|
|
3998
|
+
res.once("finish", done);
|
|
3999
|
+
res.once("close", done);
|
|
4000
|
+
try {
|
|
4001
|
+
expressApp(req, res);
|
|
4002
|
+
} catch (error) {
|
|
4003
|
+
done();
|
|
4004
|
+
throw error;
|
|
4005
|
+
}
|
|
4006
|
+
return response.promise;
|
|
4007
|
+
}),
|
|
4008
|
+
close: () => {
|
|
4009
|
+
closing = true;
|
|
4010
|
+
closeTask ??= (async () => {
|
|
4011
|
+
if (responses.size === 0) return;
|
|
4012
|
+
try {
|
|
4013
|
+
await (0, openclaw_plugin_sdk_time_runtime.withTimeout)(Promise.all(responses.values()), require_resolve_allowlist.MSTEAMS_REQUEST_TIMEOUT_MS, "Microsoft Teams webhook shutdown");
|
|
4014
|
+
} catch {
|
|
4015
|
+
warn("Microsoft Teams webhook shutdown exceeded 30 seconds; closing active responses");
|
|
4016
|
+
for (const response of responses.keys()) response.destroy();
|
|
4017
|
+
}
|
|
4018
|
+
})();
|
|
4019
|
+
return closeTask;
|
|
4020
|
+
}
|
|
4021
|
+
};
|
|
4157
4022
|
}
|
|
4158
4023
|
//#endregion
|
|
4159
4024
|
//#region extensions/msteams/src/monitor.ts
|
|
@@ -4267,20 +4132,26 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4267
4132
|
msteams: msteamsCfg
|
|
4268
4133
|
}
|
|
4269
4134
|
};
|
|
4270
|
-
const
|
|
4135
|
+
const legacyListener = require_channel.resolveMSTeamsLegacyWebhook(msteamsCfg);
|
|
4136
|
+
const pathIssue = require_channel.resolveMSTeamsWebhookPathIssue({ cfg });
|
|
4137
|
+
if (pathIssue) {
|
|
4138
|
+
if (!legacyListener) throw new Error(pathIssue);
|
|
4139
|
+
log.warn?.(pathIssue);
|
|
4140
|
+
}
|
|
4271
4141
|
const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "msteams");
|
|
4272
4142
|
const mediaMaxBytes = (0, openclaw_plugin_sdk_account_helpers.resolveChannelMediaMaxBytes)({
|
|
4273
4143
|
cfg,
|
|
4274
4144
|
resolveChannelLimitMb: ({ cfg: channelCfg }) => channelCfg.channels?.msteams?.mediaMaxMb
|
|
4275
4145
|
}) ?? 8388608;
|
|
4276
|
-
const conversationStore = opts.conversationStore ??
|
|
4277
|
-
const pollStore = opts.pollStore ??
|
|
4278
|
-
log.info(
|
|
4146
|
+
const conversationStore = opts.conversationStore ?? require_conversation_store_state.createMSTeamsConversationStoreState();
|
|
4147
|
+
const pollStore = opts.pollStore ?? require_conversation_store_state.createMSTeamsPollStoreState();
|
|
4148
|
+
log.info("starting provider on Gateway HTTP routes");
|
|
4279
4149
|
const express = await import("express");
|
|
4280
4150
|
const expressApp = express.default();
|
|
4151
|
+
const privateQaToken = await require_resolve_allowlist.resolveMSTeamsPrivateQaRuntime()?.token();
|
|
4281
4152
|
expressApp.use((req, res, next) => {
|
|
4282
4153
|
const auth = req.headers.authorization;
|
|
4283
|
-
if (!auth || !auth.startsWith("Bearer ")) {
|
|
4154
|
+
if (!auth || !auth.startsWith("Bearer ") || privateQaToken && !(0, openclaw_plugin_sdk_security_runtime.safeEqualSecret)(auth.slice(7), privateQaToken)) {
|
|
4284
4155
|
res.status(401).json({ error: "Unauthorized" });
|
|
4285
4156
|
return;
|
|
4286
4157
|
}
|
|
@@ -4294,7 +4165,7 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4294
4165
|
}
|
|
4295
4166
|
next(err);
|
|
4296
4167
|
});
|
|
4297
|
-
const configuredPath = msteamsCfg.webhook?.path
|
|
4168
|
+
const configuredPath = msteamsCfg.webhook?.path || "/api/messages";
|
|
4298
4169
|
const ssoConnectionName = msteamsCfg.sso?.enabled && msteamsCfg.sso.connectionName ? msteamsCfg.sso.connectionName : void 0;
|
|
4299
4170
|
const { app } = await require_resolve_allowlist.loadMSTeamsSdkWithAuth(creds, {
|
|
4300
4171
|
...require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(msteamsCfg),
|
|
@@ -4315,11 +4186,10 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4315
4186
|
}
|
|
4316
4187
|
const tokenProvider = require_resolve_allowlist.createMSTeamsTokenProvider(app);
|
|
4317
4188
|
const ssoDeps = ssoConnectionName ? {
|
|
4318
|
-
tokenStore:
|
|
4189
|
+
tokenStore: createMSTeamsSsoTokenStoreFs(),
|
|
4319
4190
|
connectionName: ssoConnectionName
|
|
4320
4191
|
} : void 0;
|
|
4321
4192
|
if (ssoDeps) log.debug?.("msteams sso enabled", { connectionName: ssoDeps.connectionName });
|
|
4322
|
-
const handler = buildActivityHandler();
|
|
4323
4193
|
const handlerDeps = {
|
|
4324
4194
|
cfg,
|
|
4325
4195
|
runtime,
|
|
@@ -4332,37 +4202,29 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4332
4202
|
pollStore,
|
|
4333
4203
|
log
|
|
4334
4204
|
};
|
|
4335
|
-
|
|
4205
|
+
const handleActivity = createMSTeamsActivityHandler(handlerDeps);
|
|
4336
4206
|
const ingress = createMSTeamsIngress({
|
|
4337
4207
|
accountId: appId,
|
|
4338
4208
|
runtime,
|
|
4339
4209
|
dispatch: async (activity, lifecycle, liveContext) => {
|
|
4340
4210
|
if (liveContext) liveContext.activity = activity;
|
|
4341
4211
|
const context = liveContext ?? createMSTeamsReplayContext(activity, app, require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(msteamsCfg));
|
|
4342
|
-
return await
|
|
4212
|
+
return await handleActivity(context, lifecycle);
|
|
4343
4213
|
}
|
|
4344
4214
|
});
|
|
4345
4215
|
app.on("card.action", async (ctx) => {
|
|
4346
4216
|
const adaptedCtx = adaptSdkContext(ctx, app);
|
|
4347
4217
|
try {
|
|
4348
4218
|
const activity = adaptedCtx.activity;
|
|
4349
|
-
const vote =
|
|
4219
|
+
const vote = require_conversation_store_state.extractMSTeamsPollVote(activity);
|
|
4350
4220
|
if (vote) {
|
|
4351
4221
|
const voterId = activity?.from?.aadObjectId ?? activity?.from?.id ?? "unknown";
|
|
4352
4222
|
try {
|
|
4353
|
-
if (!await isCardActionInvokeAuthorized(adaptedCtx, handlerDeps)) return
|
|
4354
|
-
statusCode: 200,
|
|
4355
|
-
type: "application/vnd.microsoft.activity.message",
|
|
4356
|
-
value: "Not authorized."
|
|
4357
|
-
};
|
|
4223
|
+
if (!await isCardActionInvokeAuthorized(adaptedCtx, handlerDeps)) return cardActionMessage("Not authorized.");
|
|
4358
4224
|
const existingPoll = await pollStore.getPoll(vote.pollId);
|
|
4359
4225
|
if (!existingPoll) {
|
|
4360
4226
|
log.debug?.("poll vote ignored (poll not found)", { pollId: vote.pollId });
|
|
4361
|
-
return
|
|
4362
|
-
statusCode: 200,
|
|
4363
|
-
type: "application/vnd.microsoft.activity.message",
|
|
4364
|
-
value: "Poll not found."
|
|
4365
|
-
};
|
|
4227
|
+
return cardActionMessage("Poll not found.");
|
|
4366
4228
|
}
|
|
4367
4229
|
const pollConversationId = existingPoll.conversationId ? require_resolve_allowlist.normalizeMSTeamsConversationId(existingPoll.conversationId) : void 0;
|
|
4368
4230
|
const activityConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity?.conversation?.id ?? "");
|
|
@@ -4372,11 +4234,7 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4372
4234
|
expectedConversationId: pollConversationId,
|
|
4373
4235
|
receivedConversationId: activityConversationId || void 0
|
|
4374
4236
|
});
|
|
4375
|
-
return
|
|
4376
|
-
statusCode: 200,
|
|
4377
|
-
type: "application/vnd.microsoft.activity.message",
|
|
4378
|
-
value: "Poll not found."
|
|
4379
|
-
};
|
|
4237
|
+
return cardActionMessage("Poll not found.");
|
|
4380
4238
|
}
|
|
4381
4239
|
if (await pollStore.recordVote({
|
|
4382
4240
|
pollId: vote.pollId,
|
|
@@ -4387,18 +4245,10 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4387
4245
|
pollId: vote.pollId,
|
|
4388
4246
|
voterId
|
|
4389
4247
|
});
|
|
4390
|
-
return
|
|
4391
|
-
statusCode: 200,
|
|
4392
|
-
type: "application/vnd.microsoft.activity.message",
|
|
4393
|
-
value: "Vote recorded."
|
|
4394
|
-
};
|
|
4248
|
+
return cardActionMessage("Vote recorded.");
|
|
4395
4249
|
}
|
|
4396
4250
|
log.debug?.("poll vote ignored (poll not found)", { pollId: vote.pollId });
|
|
4397
|
-
return
|
|
4398
|
-
statusCode: 200,
|
|
4399
|
-
type: "application/vnd.microsoft.activity.message",
|
|
4400
|
-
value: "Poll not found."
|
|
4401
|
-
};
|
|
4251
|
+
return cardActionMessage("Poll not found.");
|
|
4402
4252
|
} catch (err) {
|
|
4403
4253
|
log.error("failed to record poll vote", {
|
|
4404
4254
|
pollId: vote.pollId,
|
|
@@ -4419,11 +4269,7 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4419
4269
|
}
|
|
4420
4270
|
}
|
|
4421
4271
|
await ingress.accept(activity, adaptedCtx);
|
|
4422
|
-
return
|
|
4423
|
-
statusCode: 200,
|
|
4424
|
-
type: "application/vnd.microsoft.activity.message",
|
|
4425
|
-
value: "OK"
|
|
4426
|
-
};
|
|
4272
|
+
return cardActionMessage("OK");
|
|
4427
4273
|
} catch (err) {
|
|
4428
4274
|
log.error("msteams card.action failed", { error: require_channel_setup.formatUnknownError(err) });
|
|
4429
4275
|
return {
|
|
@@ -4446,26 +4292,27 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4446
4292
|
app.on("file.consent.decline", (ctx) => {
|
|
4447
4293
|
runMSTeamsFileConsentInvokeHandler(adaptSdkContext(ctx, app), log);
|
|
4448
4294
|
});
|
|
4449
|
-
const
|
|
4450
|
-
|
|
4451
|
-
|
|
4295
|
+
const processActivity = app.process.bind(app);
|
|
4296
|
+
app.process = async (event) => {
|
|
4297
|
+
const activity = event.body;
|
|
4298
|
+
if (activity.type !== "invoke" || !("name" in activity) || activity.name !== "signin/tokenExchange" && activity.name !== "signin/verifyState") return processActivity(event);
|
|
4299
|
+
if (!await isSigninInvokeAuthorized({ activity: {
|
|
4300
|
+
...activity,
|
|
4301
|
+
type: activity.type,
|
|
4302
|
+
name: activity.name
|
|
4303
|
+
} }, handlerDeps)) return {
|
|
4452
4304
|
status: 200,
|
|
4453
4305
|
body: {}
|
|
4454
4306
|
};
|
|
4455
4307
|
if (!ssoDeps) {
|
|
4456
|
-
log.debug?.("signin invoke received but msteams.sso is not configured", { name:
|
|
4308
|
+
log.debug?.("signin invoke received but msteams.sso is not configured", { name: activity.name });
|
|
4457
4309
|
return {
|
|
4458
4310
|
status: 200,
|
|
4459
4311
|
body: {}
|
|
4460
4312
|
};
|
|
4461
4313
|
}
|
|
4462
|
-
|
|
4463
|
-
const delegate = sdkSigninApp[delegateName];
|
|
4464
|
-
if (typeof delegate !== "function") throw new Error(`Teams SDK ${delegateName} handler is unavailable`);
|
|
4465
|
-
return delegate.call(sdkSigninApp, ctx);
|
|
4314
|
+
return processActivity(event);
|
|
4466
4315
|
};
|
|
4467
|
-
app.on("signin.token-exchange", (ctx) => handleSdkSigninInvoke(ctx, "onTokenExchange"));
|
|
4468
|
-
app.on("signin.verify-state", (ctx) => handleSdkSigninInvoke(ctx, "onVerifyState"));
|
|
4469
4316
|
if (ssoDeps) app.event("signin", (ctx) => {
|
|
4470
4317
|
(async () => {
|
|
4471
4318
|
if (!await isSigninInvokeAuthorized(adaptSdkContext(ctx, app), handlerDeps)) return;
|
|
@@ -4512,93 +4359,76 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4512
4359
|
return;
|
|
4513
4360
|
}
|
|
4514
4361
|
try {
|
|
4515
|
-
await
|
|
4362
|
+
await handleActivity(adaptedCtx);
|
|
4516
4363
|
} catch (err) {
|
|
4517
4364
|
log.error("msteams non-turn activity failed", { error: require_channel_setup.formatUnknownError(err) });
|
|
4518
4365
|
}
|
|
4519
4366
|
});
|
|
4520
4367
|
await app.initialize();
|
|
4521
4368
|
ingress.start();
|
|
4522
|
-
const
|
|
4523
|
-
const
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4369
|
+
const unregisterRoutes = [];
|
|
4370
|
+
const webhook = createMSTeamsWebhookHandler(expressApp, (message) => log.warn?.(message));
|
|
4371
|
+
try {
|
|
4372
|
+
unregisterRoutes.push((0, openclaw_plugin_sdk_webhook_targets.registerPluginHttpRoute)({
|
|
4373
|
+
path: configuredPath,
|
|
4374
|
+
auth: "plugin",
|
|
4375
|
+
pluginId: "msteams",
|
|
4376
|
+
source: "msteams-webhook",
|
|
4377
|
+
accountId: appId,
|
|
4378
|
+
handler: webhook.handler,
|
|
4379
|
+
legacyListener: legacyListener ? {
|
|
4380
|
+
...legacyListener,
|
|
4381
|
+
timeouts: {
|
|
4382
|
+
headers: 15e3,
|
|
4383
|
+
request: 3e4,
|
|
4384
|
+
socket: 3e4
|
|
4385
|
+
}
|
|
4386
|
+
} : void 0,
|
|
4387
|
+
throwOnFailure: true,
|
|
4388
|
+
log: (message) => log.warn?.(message)
|
|
4389
|
+
}));
|
|
4390
|
+
if (configuredPath !== "/api/messages") unregisterRoutes.push((0, openclaw_plugin_sdk_webhook_targets.registerPluginHttpRoute)({
|
|
4391
|
+
path: "/api/messages",
|
|
4392
|
+
auth: "plugin",
|
|
4393
|
+
pluginId: "msteams",
|
|
4394
|
+
source: "msteams-webhook-alias",
|
|
4395
|
+
accountId: appId,
|
|
4396
|
+
handler: webhook.handler,
|
|
4397
|
+
log: (message) => log.warn?.(message)
|
|
4398
|
+
}));
|
|
4399
|
+
} catch (error) {
|
|
4400
|
+
for (const unregister of unregisterRoutes) unregister();
|
|
4528
4401
|
await ingress.stop();
|
|
4529
|
-
throw
|
|
4530
|
-
}
|
|
4531
|
-
log.info(`msteams provider started on
|
|
4402
|
+
throw error;
|
|
4403
|
+
}
|
|
4404
|
+
log.info(`msteams provider started on Gateway route ${configuredPath}`);
|
|
4532
4405
|
publishMSTeamsReady(opts.statusSink);
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
if (err) log.debug?.("msteams server close error", { error: require_channel_setup.formatUnknownError(err) });
|
|
4543
|
-
resolve();
|
|
4544
|
-
});
|
|
4545
|
-
});
|
|
4546
|
-
await ingress.stop();
|
|
4547
|
-
publishMSTeamsStopped(opts.statusSink);
|
|
4406
|
+
let shutdownTask;
|
|
4407
|
+
const shutdown = () => {
|
|
4408
|
+
shutdownTask ??= (async () => {
|
|
4409
|
+
await webhook.close();
|
|
4410
|
+
for (const unregister of unregisterRoutes.splice(0)) unregister();
|
|
4411
|
+
await ingress.stop();
|
|
4412
|
+
publishMSTeamsStopped(opts.statusSink);
|
|
4413
|
+
})();
|
|
4414
|
+
return shutdownTask;
|
|
4548
4415
|
};
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
}
|
|
4416
|
+
try {
|
|
4417
|
+
await (0, openclaw_plugin_sdk_channel_outbound.waitUntilAbort)(opts.abortSignal, shutdown);
|
|
4418
|
+
} finally {
|
|
4419
|
+
await shutdown();
|
|
4420
|
+
}
|
|
4554
4421
|
return {
|
|
4555
4422
|
app: expressApp,
|
|
4556
4423
|
shutdown
|
|
4557
4424
|
};
|
|
4558
4425
|
}
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
const messageHandlers = [];
|
|
4565
|
-
const membersAddedHandlers = [];
|
|
4566
|
-
const reactionsAddedHandlers = [];
|
|
4567
|
-
const reactionsRemovedHandlers = [];
|
|
4568
|
-
const handler = {
|
|
4569
|
-
onMessage(cb) {
|
|
4570
|
-
messageHandlers.push(cb);
|
|
4571
|
-
return handler;
|
|
4572
|
-
},
|
|
4573
|
-
onMembersAdded(cb) {
|
|
4574
|
-
membersAddedHandlers.push(cb);
|
|
4575
|
-
return handler;
|
|
4576
|
-
},
|
|
4577
|
-
onReactionsAdded(cb) {
|
|
4578
|
-
reactionsAddedHandlers.push(cb);
|
|
4579
|
-
return handler;
|
|
4580
|
-
},
|
|
4581
|
-
onReactionsRemoved(cb) {
|
|
4582
|
-
reactionsRemovedHandlers.push(cb);
|
|
4583
|
-
return handler;
|
|
4584
|
-
},
|
|
4585
|
-
async run(context, turnAdoptionLifecycle) {
|
|
4586
|
-
const ctx = context;
|
|
4587
|
-
const activityType = ctx?.activity?.type;
|
|
4588
|
-
const noop = async () => {};
|
|
4589
|
-
if (activityType === "message") for (const h of messageHandlers) {
|
|
4590
|
-
const result = await h(context, noop, turnAdoptionLifecycle);
|
|
4591
|
-
if (result) return result;
|
|
4592
|
-
}
|
|
4593
|
-
else if (activityType === "conversationUpdate") for (const h of membersAddedHandlers) await h(context, noop);
|
|
4594
|
-
else if (activityType === "messageReaction") {
|
|
4595
|
-
const activity = ctx?.activity;
|
|
4596
|
-
if (activity?.reactionsAdded?.length) for (const h of reactionsAddedHandlers) await h(context, noop);
|
|
4597
|
-
if (activity?.reactionsRemoved?.length) for (const h of reactionsRemovedHandlers) await h(context, noop);
|
|
4598
|
-
}
|
|
4599
|
-
}
|
|
4426
|
+
function cardActionMessage(value) {
|
|
4427
|
+
return {
|
|
4428
|
+
statusCode: 200,
|
|
4429
|
+
type: "application/vnd.microsoft.activity.message",
|
|
4430
|
+
value
|
|
4600
4431
|
};
|
|
4601
|
-
return handler;
|
|
4602
4432
|
}
|
|
4603
4433
|
/**
|
|
4604
4434
|
* Adapt a new @microsoft/teams.apps SDK context to the MSTeamsTurnContext interface
|