@openclaw/msteams 2026.11.3 → 2026.11.5-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/.setup/{adapter.runtime-B-6Q3nMI.cjs → adapter.runtime-BB38Rdj_.cjs} +20 -23
- package/dist/.setup/{approval-card-DWPDItvq.cjs → approval-card-BHAKsdAM.cjs} +5 -9
- package/dist/.setup/{approval-handler.runtime-D0jbDypb.cjs → approval-handler.runtime-C16UvawQ.cjs} +4 -4
- package/dist/.setup/{channel-Ci6K9Njb.cjs → channel-BaTw0JW3.cjs} +35 -254
- package/dist/.setup/{channel.runtime-DeHVAVxL.cjs → channel.runtime-Ch2M_-jN.cjs} +65 -107
- package/dist/.setup/{channel.setup-BN3uhq3r.cjs → channel.setup-B9yKGEse.cjs} +161 -262
- package/dist/.setup/{config-schema-CkKdphSp.cjs → config-schema-3lVZ45cB.cjs} +120 -125
- package/dist/.setup/{conversation-store-state-B-4g4uq5.cjs → conversation-store-state-CVhMM0QY.cjs} +411 -601
- package/dist/.setup/{delegated-state-Bv5cWuec.cjs → delegated-state-ilYzKwD8.cjs} +68 -6
- package/dist/.setup/{monitor-cuifGELM.cjs → monitor-b0zPpnmW.cjs} +1883 -2310
- package/dist/.setup/{oauth-BBUQCwmq.cjs → oauth-CUtfkgKE.cjs} +10 -21
- package/dist/.setup/presentation-Duoi9Ijn.cjs +213 -0
- package/dist/.setup/{reaction-types-CwP5RVQB.cjs → reaction-types-C6F6g2nk.cjs} +1 -1
- package/dist/.setup/{resolve-allowlist-gI34MnpE.cjs → resolve-allowlist-7mIEepXB.cjs} +214 -90
- package/dist/.setup/{secret-contract-api-wAyY5UHv.cjs → secret-contract-api-D554HIrH.cjs} +4 -6
- package/dist/.setup/{secret-contract-api-YY3v6MP8.cjs → secret-contract-api-r3hRtbBW.cjs} +1 -1
- package/dist/.setup/{send-C9JY42T6.cjs → send-D1w7a_ip.cjs} +86 -182
- package/dist/.setup/{setup-plugin-api-B-NYBLkF.cjs → setup-plugin-api-ChovZJU6.cjs} +1 -1
- package/dist/.setup/{thread-session-BWVCoYza.cjs → thread-session-CeWYiol_.cjs} +4 -6
- package/dist/.setup/{token-config-bEpua_Q7.cjs → token-config-DICiuJWr.cjs} +0 -2
- package/dist/.source-module-urls/7e43ee3ae4faab430a47269c8217f8d99ec60ecd85233f40e81d03efeb543068.json +1 -0
- package/dist/api.cjs +2 -2
- package/dist/channel-config-api.cjs +1 -1
- package/dist/channel-plugin-api.cjs +1 -1
- package/dist/directory-contract-api.cjs +1 -1
- package/dist/doctor-contract-api.cjs +3 -11
- package/dist/qa-runner-api.cjs +1 -1
- package/dist/secret-contract-api.cjs +1 -1
- package/dist/setup-entry.cjs +2 -2
- package/dist/setup-plugin-api.cjs +1 -1
- package/openclaw.plugin.json +0 -30
- package/package.json +11 -11
|
@@ -1,157 +1,49 @@
|
|
|
1
|
-
const require_rolldown_runtime = require("./rolldown-runtime-BloeesfJ.cjs");
|
|
2
1
|
const require_bot_framework_service_url = require("./bot-framework-service-url-B3FWn2SV.cjs");
|
|
3
|
-
const require_resolve_allowlist = require("./resolve-allowlist-
|
|
2
|
+
const require_resolve_allowlist = require("./resolve-allowlist-7mIEepXB.cjs");
|
|
4
3
|
const require_runtime = require("./runtime-gZTAuFux.cjs");
|
|
5
4
|
require("../runtime-api.cjs");
|
|
6
|
-
const require_delegated_state = require("./delegated-state-
|
|
7
|
-
const require_channel = require("./channel-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
5
|
+
const require_delegated_state = require("./delegated-state-ilYzKwD8.cjs");
|
|
6
|
+
const require_channel = require("./channel-BaTw0JW3.cjs");
|
|
7
|
+
const require_presentation = require("./presentation-Duoi9Ijn.cjs");
|
|
8
|
+
const require_thread_session = require("./thread-session-CeWYiol_.cjs");
|
|
9
|
+
const require_channel_setup = require("./channel.setup-B9yKGEse.cjs");
|
|
10
|
+
const require_approval_card = require("./approval-card-BHAKsdAM.cjs");
|
|
11
|
+
const require_conversation_store_state = require("./conversation-store-state-CVhMM0QY.cjs");
|
|
12
|
+
const require_reaction_types = require("./reaction-types-C6F6g2nk.cjs");
|
|
13
13
|
let openclaw_plugin_sdk_channel_outbound = require("openclaw/plugin-sdk/channel-outbound");
|
|
14
|
+
let openclaw_plugin_sdk_interactive_runtime = require("openclaw/plugin-sdk/interactive-runtime");
|
|
14
15
|
let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
|
|
15
16
|
let openclaw_plugin_sdk_account_id = require("openclaw/plugin-sdk/account-id");
|
|
16
17
|
let openclaw_plugin_sdk_allow_from = require("openclaw/plugin-sdk/allow-from");
|
|
17
18
|
let openclaw_plugin_sdk_provider_http = require("openclaw/plugin-sdk/provider-http");
|
|
18
19
|
let openclaw_plugin_sdk_media_runtime = require("openclaw/plugin-sdk/media-runtime");
|
|
19
20
|
let openclaw_plugin_sdk_ssrf_runtime = require("openclaw/plugin-sdk/ssrf-runtime");
|
|
20
|
-
let node_dns_promises = require("node:dns/promises");
|
|
21
21
|
let openclaw_plugin_sdk_ssrf_policy = require("openclaw/plugin-sdk/ssrf-policy");
|
|
22
22
|
let openclaw_plugin_sdk_number_runtime = require("openclaw/plugin-sdk/number-runtime");
|
|
23
23
|
let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text-utility-runtime");
|
|
24
24
|
let openclaw_plugin_sdk_error_runtime = require("openclaw/plugin-sdk/error-runtime");
|
|
25
|
-
let
|
|
26
|
-
node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
|
|
25
|
+
let openclaw_plugin_sdk_node_crypto_runtime = require("openclaw/plugin-sdk/node-crypto-runtime");
|
|
27
26
|
let openclaw_plugin_sdk_webhook_targets = require("openclaw/plugin-sdk/webhook-targets");
|
|
28
|
-
let openclaw_plugin_sdk_routing = require("openclaw/plugin-sdk/routing");
|
|
29
27
|
let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
|
|
28
|
+
let node_dns_promises = require("node:dns/promises");
|
|
29
|
+
let openclaw_plugin_sdk_gateway_runtime = require("openclaw/plugin-sdk/gateway-runtime");
|
|
30
30
|
let openclaw_plugin_sdk_security_runtime = require("openclaw/plugin-sdk/security-runtime");
|
|
31
31
|
let openclaw_plugin_sdk_approval_gateway_runtime = require("openclaw/plugin-sdk/approval-gateway-runtime");
|
|
32
32
|
let openclaw_plugin_sdk_channel_ingress_runtime = require("openclaw/plugin-sdk/channel-ingress-runtime");
|
|
33
33
|
let openclaw_plugin_sdk_context_visibility_runtime = require("openclaw/plugin-sdk/context-visibility-runtime");
|
|
34
34
|
let openclaw_plugin_sdk_reply_history = require("openclaw/plugin-sdk/reply-history");
|
|
35
35
|
let openclaw_plugin_sdk_runtime_config_snapshot = require("openclaw/plugin-sdk/runtime-config-snapshot");
|
|
36
|
-
let node_util = require("node:util");
|
|
37
|
-
let openclaw_plugin_sdk_request_url = require("openclaw/plugin-sdk/request-url");
|
|
38
36
|
let openclaw_plugin_sdk_agent_runtime = require("openclaw/plugin-sdk/agent-runtime");
|
|
39
37
|
let openclaw_plugin_sdk_extension_shared = require("openclaw/plugin-sdk/extension-shared");
|
|
40
38
|
let openclaw_plugin_sdk_plugin_runtime = require("openclaw/plugin-sdk/plugin-runtime");
|
|
41
39
|
let openclaw_plugin_sdk_collection_runtime = require("openclaw/plugin-sdk/collection-runtime");
|
|
42
|
-
let
|
|
40
|
+
let openclaw_plugin_sdk_request_url = require("openclaw/plugin-sdk/request-url");
|
|
43
41
|
let openclaw_plugin_sdk_time_runtime = require("openclaw/plugin-sdk/time-runtime");
|
|
44
42
|
let openclaw_plugin_sdk_channel_pairing = require("openclaw/plugin-sdk/channel-pairing");
|
|
45
43
|
let openclaw_plugin_sdk_runtime_group_policy = require("openclaw/plugin-sdk/runtime-group-policy");
|
|
46
44
|
let openclaw_plugin_sdk_dangerous_name_runtime = require("openclaw/plugin-sdk/dangerous-name-runtime");
|
|
47
45
|
let openclaw_plugin_sdk_account_helpers = require("openclaw/plugin-sdk/account-helpers");
|
|
48
46
|
let openclaw_plugin_sdk_webhook_ingress = require("openclaw/plugin-sdk/webhook-ingress");
|
|
49
|
-
//#region extensions/msteams/src/feedback-reflection-store.ts
|
|
50
|
-
const LEARNINGS_NAMESPACE = "feedback-learnings";
|
|
51
|
-
const MAX_LEARNING_ENTRIES = 1e4;
|
|
52
|
-
function learningStoreKey(storePath, sessionKey) {
|
|
53
|
-
return node_crypto.default.createHash("sha256").update(`${storePath}\0${sessionKey}`, "utf8").digest("hex");
|
|
54
|
-
}
|
|
55
|
-
async function storeSessionLearning(params) {
|
|
56
|
-
const store = require_runtime.getMSTeamsRuntime().state.openKeyedStore({
|
|
57
|
-
namespace: LEARNINGS_NAMESPACE,
|
|
58
|
-
maxEntries: MAX_LEARNING_ENTRIES
|
|
59
|
-
});
|
|
60
|
-
const key = learningStoreKey(params.storePath, params.sessionKey);
|
|
61
|
-
if (!store.observe || !store.compareAndApply) throw new Error("plugin state atomic comparison is unavailable");
|
|
62
|
-
const prepared = {
|
|
63
|
-
sessionKey: params.sessionKey,
|
|
64
|
-
learning: params.learning,
|
|
65
|
-
updatedAt: Date.now()
|
|
66
|
-
};
|
|
67
|
-
let observed = await store.observe(key);
|
|
68
|
-
while (true) {
|
|
69
|
-
const result = await store.compareAndApply(key, observed.comparison, {
|
|
70
|
-
operation: "update",
|
|
71
|
-
action: "set",
|
|
72
|
-
value: {
|
|
73
|
-
sessionKey: prepared.sessionKey,
|
|
74
|
-
learnings: [...observed.value?.learnings ?? [], prepared.learning].slice(-10),
|
|
75
|
-
updatedAt: prepared.updatedAt
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
if (result.status !== "conflict") return;
|
|
79
|
-
observed = result.current;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
//#endregion
|
|
83
|
-
//#region extensions/msteams/src/feedback-reflection.ts
|
|
84
|
-
/**
|
|
85
|
-
* Run a background reflection after negative feedback.
|
|
86
|
-
* This is designed to be called fire-and-forget (don't await in the invoke handler).
|
|
87
|
-
*/
|
|
88
|
-
async function runFeedbackReflection(params) {
|
|
89
|
-
const { cfg, log, sessionKey } = params;
|
|
90
|
-
const msteamsCfg = require_resolve_allowlist.resolveMSTeamsAccountConfig(cfg, params.accountId);
|
|
91
|
-
const cooldownMs = msteamsCfg.feedbackReflectionCooldownMs ?? openclaw_plugin_sdk_channel_inbound.DEFAULT_CHANNEL_FEEDBACK_REFLECTION_COOLDOWN_MS;
|
|
92
|
-
let reflection;
|
|
93
|
-
try {
|
|
94
|
-
reflection = await (0, openclaw_plugin_sdk_channel_inbound.runChannelFeedbackReflection)({
|
|
95
|
-
cfg,
|
|
96
|
-
channel: "msteams",
|
|
97
|
-
channelLabel: "Teams",
|
|
98
|
-
agentId: params.agentId,
|
|
99
|
-
sessionKey,
|
|
100
|
-
conversationId: params.conversationId,
|
|
101
|
-
conversationKind: params.conversationKind,
|
|
102
|
-
userComment: params.userComment,
|
|
103
|
-
cooldownMs,
|
|
104
|
-
onRecordError: (err) => log.debug?.("reflection session record failed", { error: require_channel_setup.formatUnknownError(err) }),
|
|
105
|
-
onDispatchError: (err) => log.debug?.("reflection reply error", { error: require_channel_setup.formatUnknownError(err) })
|
|
106
|
-
});
|
|
107
|
-
} catch (err) {
|
|
108
|
-
log.error("reflection dispatch failed", { error: require_channel_setup.formatUnknownError(err) });
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (reflection.status === "cooldown") {
|
|
112
|
-
log.debug?.("skipping reflection (cooldown active)", { sessionKey });
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (reflection.status === "empty") {
|
|
116
|
-
log.debug?.("reflection produced no output");
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
log.info("reflection complete", {
|
|
120
|
-
sessionKey,
|
|
121
|
-
responseLength: reflection.responseLength,
|
|
122
|
-
followUp: reflection.followUp
|
|
123
|
-
});
|
|
124
|
-
try {
|
|
125
|
-
await storeSessionLearning({
|
|
126
|
-
storePath: reflection.storePath,
|
|
127
|
-
sessionKey,
|
|
128
|
-
learning: reflection.learning
|
|
129
|
-
});
|
|
130
|
-
} catch (err) {
|
|
131
|
-
log.debug?.("failed to store reflection learning", { error: require_channel_setup.formatUnknownError(err) });
|
|
132
|
-
}
|
|
133
|
-
const conversationType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(params.conversationRef.conversation?.conversationType);
|
|
134
|
-
if (!(conversationType === "personal" && reflection.followUp && Boolean(reflection.userMessage))) {
|
|
135
|
-
if (reflection.followUp && conversationType !== "personal") log.debug?.("skipping reflection follow-up outside direct message", {
|
|
136
|
-
sessionKey,
|
|
137
|
-
conversationType
|
|
138
|
-
});
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
await require_conversation_store_state.sendMSTeamsActivityWithReference(params.app, require_conversation_store_state.buildConversationReference(params.conversationRef), {
|
|
143
|
-
type: "message",
|
|
144
|
-
text: reflection.userMessage
|
|
145
|
-
}, {
|
|
146
|
-
accountId: params.accountId,
|
|
147
|
-
serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(msteamsCfg)
|
|
148
|
-
});
|
|
149
|
-
log.info("sent reflection follow-up", { sessionKey });
|
|
150
|
-
} catch (err) {
|
|
151
|
-
log.debug?.("failed to send reflection follow-up", { error: require_channel_setup.formatUnknownError(err) });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
//#endregion
|
|
155
47
|
//#region extensions/msteams/src/adaptive-card-submit.ts
|
|
156
48
|
function extractAdaptiveCardSubmittedData(value) {
|
|
157
49
|
if (!(0, openclaw_plugin_sdk_string_coerce_runtime.isRecord)(value)) return value;
|
|
@@ -337,7 +229,7 @@ async function resolveMSTeamsSenderAccess(params) {
|
|
|
337
229
|
const defaultGroupPolicy = (0, openclaw_plugin_sdk_runtime_group_policy.resolveDefaultGroupPolicy)(params.cfg);
|
|
338
230
|
const groupPolicy = !isDirectMessage && msteamsCfg ? msteamsCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist" : "disabled";
|
|
339
231
|
const allowNameMatching = (0, openclaw_plugin_sdk_dangerous_name_runtime.isDangerousNameMatchingEnabled)(msteamsCfg);
|
|
340
|
-
const channelGate =
|
|
232
|
+
const channelGate = require_presentation.resolveMSTeamsRouteConfig({
|
|
341
233
|
cfg: msteamsCfg,
|
|
342
234
|
teamId: activity.channelData?.team?.id,
|
|
343
235
|
teamName: activity.channelData?.team?.name,
|
|
@@ -538,7 +430,6 @@ async function admitMSTeamsMessage(params) {
|
|
|
538
430
|
allowTextCommands,
|
|
539
431
|
isControlCommand,
|
|
540
432
|
commandAuthorized: commandAccess.requested ? commandAccess.authorized : void 0,
|
|
541
|
-
effectiveDmAllowFrom,
|
|
542
433
|
effectiveGroupAllowFrom,
|
|
543
434
|
isChannel: params.isChannel
|
|
544
435
|
};
|
|
@@ -556,1904 +447,1543 @@ function resolveBotThreadMentionPolicy(params) {
|
|
|
556
447
|
};
|
|
557
448
|
}
|
|
558
449
|
//#endregion
|
|
559
|
-
//#region extensions/msteams/src/
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
return () => {
|
|
580
|
-
const current = read();
|
|
581
|
-
if (current === lastSource) return lastProjection;
|
|
582
|
-
lastSource = current;
|
|
583
|
-
if (current === source && deps.accountPolicyCfg) return lastProjection = deps.cfg;
|
|
584
|
-
const retired = originallyConfigured && !require_resolve_allowlist.listMSTeamsAccountIds(current).includes(deps.accountId);
|
|
585
|
-
const accountConfig = require_resolve_allowlist.resolveMSTeamsAccountConfig(current, deps.accountId);
|
|
586
|
-
const keepStartup = current.channels?.msteams !== void 0 && (0, node_util.isDeepStrictEqual)(allowlistInputs(accountConfig), startupInputs);
|
|
587
|
-
if (deps.accountId === "default" && !current.channels?.msteams?.accounts && !retired && !keepStartup) return lastProjection = current;
|
|
588
|
-
return lastProjection = require_resolve_allowlist.withAccountScopedMSTeamsConfig({
|
|
589
|
-
cfg: current,
|
|
590
|
-
accountId: deps.accountId,
|
|
591
|
-
accountConfig: {
|
|
592
|
-
...accountConfig,
|
|
593
|
-
...keepStartup ? startupAllowlists : {},
|
|
594
|
-
...current.channels?.msteams?.enabled === false || retired ? { enabled: false } : {}
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
//#endregion
|
|
600
|
-
//#region extensions/msteams/src/attachments/bot-framework.ts
|
|
601
|
-
const BOT_FRAMEWORK_SCOPE = "https://api.botframework.com";
|
|
602
|
-
/**
|
|
603
|
-
* Detect Bot Framework personal chat ("a:") and MSA orgid ("8:orgid:") conversation
|
|
604
|
-
* IDs. These identifiers are not recognized by Graph's `/chats/{id}` endpoint, so we
|
|
605
|
-
* must fetch media via the Bot Framework v3 attachments endpoint instead.
|
|
606
|
-
*
|
|
607
|
-
* Graph-compatible IDs start with `19:` and are left untouched by this detector.
|
|
608
|
-
*/
|
|
609
|
-
function isBotFrameworkPersonalChatId(conversationId) {
|
|
610
|
-
if (typeof conversationId !== "string") return false;
|
|
611
|
-
const trimmed = conversationId.trim();
|
|
612
|
-
return trimmed.startsWith("a:") || trimmed.startsWith("8:orgid:");
|
|
613
|
-
}
|
|
614
|
-
async function fetchBotFrameworkAttachment(params, kind) {
|
|
615
|
-
let response;
|
|
616
|
-
try {
|
|
617
|
-
const headers = require_resolve_allowlist.ensureUserAgentHeader();
|
|
618
|
-
require_resolve_allowlist.applyAuthorizationHeaderForUrl({
|
|
619
|
-
headers,
|
|
620
|
-
url: params.url,
|
|
621
|
-
authAllowHosts: params.policy.authAllowHosts,
|
|
622
|
-
bearerToken: params.accessToken
|
|
623
|
-
});
|
|
624
|
-
response = await require_resolve_allowlist.safeFetchWithPolicy({
|
|
625
|
-
url: params.url,
|
|
626
|
-
policy: params.policy,
|
|
627
|
-
resolveFn: node_dns_promises.lookup,
|
|
628
|
-
requestInit: { headers },
|
|
629
|
-
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
630
|
-
});
|
|
631
|
-
} catch (err) {
|
|
632
|
-
params.logger?.warn?.(`msteams botFramework ${kind} fetch failed`, { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
if (!response.ok) {
|
|
636
|
-
await response.body?.cancel().catch(() => void 0);
|
|
637
|
-
params.logger?.warn?.(`msteams botFramework ${kind} non-ok`, { status: response.status });
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
return response;
|
|
641
|
-
}
|
|
642
|
-
async function fetchBotFrameworkAttachmentInfo(params) {
|
|
643
|
-
const response = await fetchBotFrameworkAttachment(params, "attachmentInfo");
|
|
644
|
-
if (!response) return;
|
|
645
|
-
try {
|
|
646
|
-
return await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(response, "msteams botFramework attachmentInfo");
|
|
647
|
-
} catch (err) {
|
|
648
|
-
params.logger?.warn?.("msteams botFramework attachmentInfo parse failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
649
|
-
return;
|
|
450
|
+
//#region extensions/msteams/src/informative-status.ts
|
|
451
|
+
const INFORMATIVE_MAX_CHARS = 1e3;
|
|
452
|
+
const INFORMATIVE_MAX_BYTES = 1024;
|
|
453
|
+
const ELLIPSIS = "…";
|
|
454
|
+
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
455
|
+
const utf8 = new TextEncoder();
|
|
456
|
+
function flattenInformativeStatus(text) {
|
|
457
|
+
const joined = text.split("\n").map((line) => line.trim().replace(/^[•-]\s+/u, "")).filter(Boolean).join(" · ");
|
|
458
|
+
if (joined.length <= INFORMATIVE_MAX_CHARS && utf8.encode(joined).length <= INFORMATIVE_MAX_BYTES) return joined;
|
|
459
|
+
const graphemes = Array.from(segmenter.segment(joined), (s) => s.segment);
|
|
460
|
+
let chars = 1;
|
|
461
|
+
let bytes = utf8.encode(ELLIPSIS).length;
|
|
462
|
+
let start = graphemes.length;
|
|
463
|
+
while (start > 0) {
|
|
464
|
+
const next = graphemes[start - 1];
|
|
465
|
+
const nextBytes = utf8.encode(next).length;
|
|
466
|
+
if (chars + next.length > INFORMATIVE_MAX_CHARS || bytes + nextBytes > INFORMATIVE_MAX_BYTES) break;
|
|
467
|
+
chars += next.length;
|
|
468
|
+
bytes += nextBytes;
|
|
469
|
+
start -= 1;
|
|
650
470
|
}
|
|
471
|
+
return ELLIPSIS + graphemes.slice(start).join("");
|
|
651
472
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
return await require_runtime.getMSTeamsRuntime().channel.media.saveResponseMedia(response, {
|
|
657
|
-
sourceUrl: params.url,
|
|
658
|
-
filePathHint: params.fileNameHint,
|
|
659
|
-
maxBytes: params.maxBytes,
|
|
660
|
-
fallbackContentType: params.contentTypeHint,
|
|
661
|
-
subdir: "inbound"
|
|
662
|
-
});
|
|
663
|
-
} catch (err) {
|
|
664
|
-
params.logger?.warn?.("msteams botFramework attachmentView save failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
665
|
-
return;
|
|
666
|
-
} finally {
|
|
667
|
-
await response.body?.cancel().catch(() => void 0);
|
|
668
|
-
}
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region extensions/msteams/src/reply-stream-controller.ts
|
|
475
|
+
function hasUnstreamedContent(payload) {
|
|
476
|
+
return Boolean(payload.presentation || payload.mediaUrl || payload.mediaUrls?.length);
|
|
669
477
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
478
|
+
/** Bridge reply callbacks to the SDK stream; shared conversations use block delivery. */
|
|
479
|
+
function createTeamsReplyStreamController(params) {
|
|
480
|
+
const isPersonal = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(params.conversationType) === "personal";
|
|
481
|
+
const streamMode = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelPreviewStreamMode)(params.msteamsConfig, "partial");
|
|
482
|
+
const stream = params.allowProviderPreview && isPersonal && (streamMode === "partial" || streamMode === "progress") ? params.context.stream : void 0;
|
|
483
|
+
let tokensEmitted = false;
|
|
484
|
+
let nativeDispatchStarted = false;
|
|
485
|
+
let nativeDeliveryClaimed = false;
|
|
486
|
+
let streamFinalizationPending = false;
|
|
487
|
+
let pendingFinalPayload;
|
|
488
|
+
let emittedText = "";
|
|
489
|
+
let acknowledgedText = "";
|
|
490
|
+
let acknowledgedLogicalText = "";
|
|
491
|
+
let acknowledgedStreamId;
|
|
492
|
+
let replacementFinalPending = false;
|
|
493
|
+
let replacementSettlementPending = false;
|
|
494
|
+
let replacementTextAwaitingAcknowledgement;
|
|
495
|
+
let deferredReplacementEntries = [];
|
|
496
|
+
let queuedFinalActivity;
|
|
497
|
+
const streamEvents = stream?.events;
|
|
498
|
+
let streamChunkSubscription;
|
|
499
|
+
if (typeof streamEvents?.on === "function" && typeof streamEvents.off === "function") streamChunkSubscription = streamEvents.on("chunk", (activity) => {
|
|
500
|
+
const replacementAcknowledgementPending = replacementTextAwaitingAcknowledgement !== void 0;
|
|
501
|
+
const replacementAcknowledgement = typeof activity.text === "string" && replacementAcknowledgementPending && activity.text === replacementTextAwaitingAcknowledgement?.text;
|
|
502
|
+
if (activity.type !== "typing" || activity.channelData?.streamType !== "streaming" || !activity.id || !activity.text || acknowledgedStreamId !== void 0 && activity.id !== acknowledgedStreamId || (replacementAcknowledgementPending ? !replacementAcknowledgement : !activity.text.startsWith(acknowledgedText)) || !replacementAcknowledgement && !emittedText.startsWith(activity.text)) return;
|
|
503
|
+
acknowledgedStreamId = activity.id;
|
|
504
|
+
acknowledgedText = activity.text;
|
|
505
|
+
acknowledgedLogicalText = replacementAcknowledgement ? replacementTextAwaitingAcknowledgement.logicalText : activity.text;
|
|
506
|
+
if (replacementAcknowledgement) replacementTextAwaitingAcknowledgement = void 0;
|
|
676
507
|
});
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
deadline: params.deadline,
|
|
683
|
-
label: "MS Teams Bot Framework token",
|
|
684
|
-
work: () => tokenProvider.getAccessToken(BOT_FRAMEWORK_SCOPE)
|
|
685
|
-
});
|
|
686
|
-
} catch (err) {
|
|
687
|
-
params.logger?.warn?.("msteams botFramework token acquisition failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
if (!accessToken) return;
|
|
691
|
-
const request = {
|
|
692
|
-
url: baseUrl,
|
|
693
|
-
accessToken,
|
|
694
|
-
policy,
|
|
695
|
-
logger: params.logger,
|
|
696
|
-
deadline: params.deadline
|
|
508
|
+
const wasCanceled = () => Boolean(stream?.canceled);
|
|
509
|
+
const releaseStreamChunkSubscription = () => {
|
|
510
|
+
if (streamChunkSubscription === void 0) return;
|
|
511
|
+
streamEvents?.off(streamChunkSubscription);
|
|
512
|
+
streamChunkSubscription = void 0;
|
|
697
513
|
};
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const fileNameHint = typeof info.name === "string" && info.name || void 0;
|
|
706
|
-
const contentTypeHint = typeof info.type === "string" && info.type || void 0;
|
|
707
|
-
const saved = await saveBotFrameworkAttachmentView({
|
|
708
|
-
...request,
|
|
709
|
-
url: `${baseUrl}/views/${encodeURIComponent(viewId)}`,
|
|
710
|
-
maxBytes: params.maxBytes,
|
|
711
|
-
fileNameHint,
|
|
712
|
-
contentTypeHint
|
|
713
|
-
});
|
|
714
|
-
if (!saved) return;
|
|
715
|
-
return {
|
|
716
|
-
path: saved.path,
|
|
717
|
-
contentType: saved.contentType,
|
|
718
|
-
kind: require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
719
|
-
contentType: saved.contentType,
|
|
720
|
-
fileName: fileNameHint
|
|
721
|
-
})
|
|
514
|
+
const acknowledgedNativeDelivery = () => {
|
|
515
|
+
if (!acknowledgedStreamId || !acknowledgedText) return { visibleReplySent: false };
|
|
516
|
+
return {
|
|
517
|
+
visibleReplySent: true,
|
|
518
|
+
content: acknowledgedText,
|
|
519
|
+
messageId: acknowledgedStreamId
|
|
520
|
+
};
|
|
722
521
|
};
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const unique = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeUniqueTrimmedStringList)(params.attachmentIds);
|
|
732
|
-
if (unique.length === 0 || !params.serviceUrl || !params.tokenProvider) return {
|
|
733
|
-
media: [],
|
|
734
|
-
attachmentCount: unique.length
|
|
522
|
+
const fallbackPayloadAfterAcknowledgedText = (payload) => {
|
|
523
|
+
if (!acknowledgedLogicalText || typeof payload.text !== "string" || !payload.text.startsWith(acknowledgedLogicalText)) return payload;
|
|
524
|
+
const remainingText = payload.text.slice(acknowledgedLogicalText.length);
|
|
525
|
+
if (!remainingText && !hasUnstreamedContent(payload)) return;
|
|
526
|
+
return {
|
|
527
|
+
...payload,
|
|
528
|
+
text: remainingText || void 0
|
|
529
|
+
};
|
|
735
530
|
};
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
if (item) media.push({
|
|
743
|
-
...item,
|
|
744
|
-
sourceId: attachmentId
|
|
745
|
-
});
|
|
746
|
-
else media.push({
|
|
747
|
-
kind: "document",
|
|
748
|
-
sourceId: attachmentId
|
|
749
|
-
});
|
|
750
|
-
} catch (err) {
|
|
751
|
-
media.push({
|
|
752
|
-
kind: "document",
|
|
753
|
-
sourceId: attachmentId
|
|
754
|
-
});
|
|
755
|
-
params.logger?.warn?.("msteams botFramework attachment download failed", {
|
|
756
|
-
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
757
|
-
attachmentId
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
return {
|
|
761
|
-
media,
|
|
762
|
-
attachmentCount: unique.length
|
|
531
|
+
const fallbackPayloadForSuppressedFinal = (payload) => {
|
|
532
|
+
return Boolean(payload.mediaUrl || payload.mediaUrls?.length) ? {
|
|
533
|
+
...payload,
|
|
534
|
+
mediaUrl: void 0,
|
|
535
|
+
mediaUrls: void 0
|
|
536
|
+
} : payload;
|
|
763
537
|
};
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const html = require_resolve_allowlist.extractHtmlFromAttachment(att);
|
|
778
|
-
if (!html) continue;
|
|
779
|
-
for (const value of require_resolve_allowlist.htmlTagAttributeValues(html, "attachment", "id")) {
|
|
780
|
-
const id = value.trim();
|
|
781
|
-
if (id) ids.add(id);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
return Array.from(ids);
|
|
785
|
-
}
|
|
786
|
-
function summarizeMSTeamsHtmlAttachments(attachments) {
|
|
787
|
-
const list = Array.isArray(attachments) ? attachments : [];
|
|
788
|
-
if (list.length === 0) return;
|
|
789
|
-
let htmlAttachments = 0;
|
|
790
|
-
let imgTags = 0;
|
|
791
|
-
let dataImages = 0;
|
|
792
|
-
let cidImages = 0;
|
|
793
|
-
const srcHosts = /* @__PURE__ */ new Set();
|
|
794
|
-
let attachmentTags = 0;
|
|
795
|
-
const attachmentIds = /* @__PURE__ */ new Set();
|
|
796
|
-
for (const att of list) {
|
|
797
|
-
const html = require_resolve_allowlist.extractHtmlFromAttachment(att);
|
|
798
|
-
if (!html) continue;
|
|
799
|
-
htmlAttachments += 1;
|
|
800
|
-
for (const value of require_resolve_allowlist.htmlTagAttributeValues(html, "img", "src")) {
|
|
801
|
-
imgTags += 1;
|
|
802
|
-
const src = value.trim();
|
|
803
|
-
if (src) {
|
|
804
|
-
if (src.startsWith("data:")) dataImages += 1;
|
|
805
|
-
else if (src.startsWith("cid:")) cidImages += 1;
|
|
806
|
-
else srcHosts.add(require_resolve_allowlist.safeHostForUrl(src));
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
for (const value of require_resolve_allowlist.htmlTagAttributeValues(html, "attachment", "id")) {
|
|
810
|
-
attachmentTags += 1;
|
|
811
|
-
const id = value.trim();
|
|
812
|
-
if (id) attachmentIds.add(id);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
if (htmlAttachments === 0) return;
|
|
816
|
-
return {
|
|
817
|
-
htmlAttachments,
|
|
818
|
-
imgTags,
|
|
819
|
-
dataImages,
|
|
820
|
-
cidImages,
|
|
821
|
-
srcHosts: Array.from(srcHosts).slice(0, 5),
|
|
822
|
-
attachmentTags,
|
|
823
|
-
attachmentIds: Array.from(attachmentIds).slice(0, 5)
|
|
538
|
+
const finalStreamActivity = (text) => ({
|
|
539
|
+
...require_conversation_store_state.buildMSTeamsMessageActivity(text === void 0 ? void 0 : require_conversation_store_state.formatMSTeamsMarkdown(text, params.tableMode ?? "code")),
|
|
540
|
+
channelData: params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {}
|
|
541
|
+
});
|
|
542
|
+
const queueFinalReplacement = (activeStream, text) => {
|
|
543
|
+
const activity = finalStreamActivity(text);
|
|
544
|
+
replacementTextAwaitingAcknowledgement = {
|
|
545
|
+
text: activity.text,
|
|
546
|
+
logicalText: text
|
|
547
|
+
};
|
|
548
|
+
activeStream.emit(activity);
|
|
549
|
+
queuedFinalActivity = activity;
|
|
550
|
+
emittedText = text;
|
|
824
551
|
};
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
const representedIds = /* @__PURE__ */ new Set();
|
|
828
|
-
for (const attachment of attachments) {
|
|
829
|
-
if ((require_resolve_allowlist.normalizeContentType(attachment.contentType) ?? "").startsWith("text/html")) continue;
|
|
830
|
-
const id = attachment.id?.trim();
|
|
831
|
-
if (id) representedIds.add(id);
|
|
832
|
-
}
|
|
833
|
-
return extractMSTeamsHtmlAttachmentIds(attachments).filter((id) => !representedIds.has(id));
|
|
834
|
-
}
|
|
835
|
-
function createAdvertisedMediaFact(kind, sourceId) {
|
|
836
|
-
return {
|
|
837
|
-
kind,
|
|
838
|
-
...sourceId ? { sourceId } : {}
|
|
552
|
+
const deferredReplacementLogicalContent = () => {
|
|
553
|
+
return deferredReplacementEntries.map((entry) => entry.payload.text).filter((text) => Boolean(text)).join("\n") || void 0;
|
|
839
554
|
};
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
...inlineMedia,
|
|
853
|
-
...htmlAttachmentIds.map((sourceId) => createAdvertisedMediaFact("document", sourceId))
|
|
854
|
-
];
|
|
855
|
-
}
|
|
856
|
-
//#endregion
|
|
857
|
-
//#region extensions/msteams/src/attachments/remote-media.ts
|
|
858
|
-
/**
|
|
859
|
-
* Direct save path used when the caller supplies the already-guarded fetch
|
|
860
|
-
* implementation. This lets Teams-specific auth fallback own the request
|
|
861
|
-
* sequence while keeping redirect and DNS pinning inside `safeFetchWithPolicy`.
|
|
862
|
-
*/
|
|
863
|
-
async function downloadAndStoreMSTeamsRemoteMedia(params) {
|
|
864
|
-
const response = await params.fetchImpl(params.url, { redirect: "follow" });
|
|
865
|
-
try {
|
|
866
|
-
const saved = await (0, openclaw_plugin_sdk_media_runtime.saveResponseMedia)(response, {
|
|
867
|
-
sourceUrl: params.url,
|
|
868
|
-
filePathHint: params.filePathHint,
|
|
869
|
-
maxBytes: params.maxBytes,
|
|
870
|
-
fallbackContentType: params.contentTypeHint,
|
|
871
|
-
originalFilename: params.preserveFilenames ? params.filePathHint : void 0
|
|
872
|
-
});
|
|
873
|
-
return {
|
|
874
|
-
path: saved.path,
|
|
875
|
-
contentType: saved.contentType,
|
|
876
|
-
kind: params.kind ?? require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
877
|
-
contentType: saved.contentType,
|
|
878
|
-
fileName: params.filePathHint
|
|
879
|
-
})
|
|
880
|
-
};
|
|
881
|
-
} finally {
|
|
882
|
-
await response.body?.cancel().catch(() => void 0);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
//#endregion
|
|
886
|
-
//#region extensions/msteams/src/attachments/download.ts
|
|
887
|
-
function resolveDownloadCandidate(att, graphReferenceToken) {
|
|
888
|
-
const contentType = require_resolve_allowlist.normalizeContentType(att.contentType);
|
|
889
|
-
const graphReference = graphReferenceToken !== void 0 && contentType === "reference";
|
|
890
|
-
const name = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.name) ?? "";
|
|
891
|
-
if (contentType === "application/vnd.microsoft.teams.file.download.info") {
|
|
892
|
-
if (!(0, openclaw_plugin_sdk_string_coerce_runtime.isRecord)(att.content)) return null;
|
|
893
|
-
const downloadUrl = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.content.downloadUrl) ?? "";
|
|
894
|
-
if (!downloadUrl) return null;
|
|
895
|
-
const fileType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.content.fileType) ?? "";
|
|
896
|
-
const uniqueId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.content.uniqueId) ?? "";
|
|
897
|
-
const fileName = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.content.fileName) ?? "";
|
|
898
|
-
const fileHint = name || fileName || (uniqueId && fileType ? `${uniqueId}.${fileType}` : "");
|
|
555
|
+
const deferredReplacementPayloads = (replacementFallback) => deferredReplacementEntries.flatMap((entry) => {
|
|
556
|
+
if (entry.kind === "payload") return [entry.payload];
|
|
557
|
+
const text = replacementFallback?.text;
|
|
558
|
+
if (!text && !hasUnstreamedContent(entry.payload)) return [];
|
|
559
|
+
return [{
|
|
560
|
+
...entry.payload,
|
|
561
|
+
text: text || void 0
|
|
562
|
+
}];
|
|
563
|
+
});
|
|
564
|
+
const finalizeWithoutReceipt = (logicalContent, canceled = false) => {
|
|
565
|
+
const fallback = pendingFinalPayload && !canceled ? fallbackPayloadAfterAcknowledgedText(pendingFinalPayload) : void 0;
|
|
566
|
+
const postNativePayloads = replacementSettlementPending && !canceled ? deferredReplacementPayloads(fallback) : [];
|
|
899
567
|
return {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
fileType
|
|
905
|
-
}),
|
|
906
|
-
url: downloadUrl,
|
|
907
|
-
fileHint: fileHint || void 0,
|
|
908
|
-
contentTypeHint: void 0,
|
|
909
|
-
sourceId: att.id?.trim() || void 0
|
|
568
|
+
...acknowledgedNativeDelivery(),
|
|
569
|
+
...!canceled && logicalContent ? { logicalContent } : {},
|
|
570
|
+
...!replacementSettlementPending && fallback ? { fallbackPayload: fallback } : {},
|
|
571
|
+
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
910
572
|
};
|
|
911
|
-
}
|
|
912
|
-
const contentUrl = graphReference ? att.contentUrl ?? "" : (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.contentUrl) ?? "";
|
|
913
|
-
if (!contentUrl) return null;
|
|
914
|
-
const sharesUrl = graphReference ? `${require_resolve_allowlist.GRAPH_ROOT}/shares/${require_resolve_allowlist.encodeGraphShareId(contentUrl)}/driveItem/content` : require_resolve_allowlist.tryBuildGraphSharesUrlForSharedLink(contentUrl);
|
|
915
|
-
const resolvedUrl = sharesUrl ?? contentUrl;
|
|
916
|
-
const resolvedContentTypeHint = graphReference ? "application/octet-stream" : sharesUrl ? void 0 : contentType;
|
|
917
|
-
return {
|
|
918
|
-
kind: "remote",
|
|
919
|
-
mediaKind: require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
920
|
-
contentType,
|
|
921
|
-
fileName: name
|
|
922
|
-
}),
|
|
923
|
-
url: resolvedUrl,
|
|
924
|
-
fileHint: graphReference ? att.name ?? void 0 : name || void 0,
|
|
925
|
-
contentTypeHint: resolvedContentTypeHint,
|
|
926
|
-
sourceId: att.id?.trim() || void 0,
|
|
927
|
-
graphReference
|
|
928
573
|
};
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (!match) return null;
|
|
942
|
-
const contentType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(match[1] ?? "");
|
|
943
|
-
if (!Boolean(match[2])) return null;
|
|
944
|
-
const canonicalPayload = canonicalizeInlineBase64Payload(match[3] ?? "");
|
|
945
|
-
if (!canonicalPayload) return null;
|
|
946
|
-
const estimatedBytes = Buffer.byteLength(canonicalPayload, "base64");
|
|
947
|
-
if (estimatedBytes <= 0 || typeof maxBytes === "number" && (estimatedBytes > maxBytes || totalBytes + estimatedBytes > maxBytes)) return null;
|
|
948
|
-
try {
|
|
949
|
-
return {
|
|
950
|
-
data: Buffer.from(canonicalPayload, "base64"),
|
|
951
|
-
contentType,
|
|
952
|
-
estimatedBytes
|
|
953
|
-
};
|
|
954
|
-
} catch {
|
|
955
|
-
return null;
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
async function resolveInlineDataImageMime(inline) {
|
|
959
|
-
const detectedMime = await require_runtime.getMSTeamsRuntime().media.detectMime({
|
|
960
|
-
buffer: inline.data,
|
|
961
|
-
headerMime: inline.contentType
|
|
962
|
-
});
|
|
963
|
-
const mime = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(detectedMime ?? inline.contentType);
|
|
964
|
-
return mime?.startsWith("image/") ? mime : void 0;
|
|
965
|
-
}
|
|
966
|
-
async function fetchWithAuthFallback(params) {
|
|
967
|
-
const fetchAttempt = (fetchFn, requestInit) => require_resolve_allowlist.safeFetchWithPolicy({
|
|
968
|
-
url: params.url,
|
|
969
|
-
policy: params.policy,
|
|
970
|
-
fetchFn,
|
|
971
|
-
requestInit,
|
|
972
|
-
resolveFn: params.resolveFn,
|
|
973
|
-
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
574
|
+
const progressDraft = (0, openclaw_plugin_sdk_channel_outbound.createChannelProgressDraftCompositor)({
|
|
575
|
+
preparedItems: true,
|
|
576
|
+
formatPlainText: (text) => text,
|
|
577
|
+
entry: params.msteamsConfig,
|
|
578
|
+
mode: streamMode,
|
|
579
|
+
active: Boolean(stream) && streamMode === "progress",
|
|
580
|
+
seed: params.progressSeed ?? "msteams",
|
|
581
|
+
update: (text) => {
|
|
582
|
+
if (!stream || wasCanceled() || streamFinalizationPending) return false;
|
|
583
|
+
stream.update(flattenInformativeStatus(text));
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
974
586
|
});
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
587
|
+
return {
|
|
588
|
+
onPartialReply(payload) {
|
|
589
|
+
if (!stream || !payload.text || wasCanceled() || streamMode !== "partial") return;
|
|
590
|
+
if (replacementSettlementPending && replacementFinalPending) {
|
|
591
|
+
pendingFinalPayload = { text: payload.text };
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (streamFinalizationPending || nativeDeliveryClaimed) return;
|
|
595
|
+
const fullText = payload.text;
|
|
596
|
+
let prefixLength = 0;
|
|
597
|
+
while (prefixLength < emittedText.length && prefixLength < fullText.length && emittedText[prefixLength] === fullText[prefixLength]) prefixLength += 1;
|
|
598
|
+
const previousRemainder = emittedText.slice(prefixLength);
|
|
599
|
+
const delta = fullText.slice(prefixLength);
|
|
600
|
+
if (!delta) return;
|
|
601
|
+
if (previousRemainder.trim()) {
|
|
602
|
+
stream.clearText();
|
|
603
|
+
replacementFinalPending = true;
|
|
604
|
+
replacementSettlementPending = true;
|
|
605
|
+
pendingFinalPayload = { text: fullText };
|
|
606
|
+
streamFinalizationPending = true;
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
stream.emit(delta);
|
|
610
|
+
emittedText = fullText;
|
|
611
|
+
tokensEmitted = true;
|
|
612
|
+
nativeDispatchStarted = true;
|
|
613
|
+
},
|
|
614
|
+
pushItemEvent: progressDraft.pushItemEvent.bind(progressDraft),
|
|
615
|
+
pushToolEvent: progressDraft.pushToolEvent,
|
|
616
|
+
pushReasoningProgress: progressDraft.pushReasoningProgress.bind(progressDraft),
|
|
617
|
+
resetReasoningProgress: progressDraft.resetReasoningProgress,
|
|
618
|
+
pushApprovalEvent: progressDraft.pushApprovalEvent.bind(progressDraft),
|
|
619
|
+
pushPlanProgress: progressDraft.pushPlanProgress.bind(progressDraft),
|
|
620
|
+
preparePayload(payload) {
|
|
621
|
+
if (!stream) return payload;
|
|
622
|
+
if (wasCanceled()) return;
|
|
623
|
+
if (payload.text) progressDraft.markFinalReplyStarted();
|
|
624
|
+
if (replacementSettlementPending) {
|
|
625
|
+
if (!replacementFinalPending || !payload.text) {
|
|
626
|
+
deferredReplacementEntries.push({
|
|
627
|
+
kind: "payload",
|
|
628
|
+
payload
|
|
629
|
+
});
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
replacementFinalPending = false;
|
|
633
|
+
deferredReplacementEntries.push({
|
|
634
|
+
kind: "replacement",
|
|
635
|
+
payload
|
|
636
|
+
});
|
|
637
|
+
pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
|
|
638
|
+
queueFinalReplacement(stream, payload.text);
|
|
639
|
+
tokensEmitted = false;
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (streamMode === "progress" && payload.text && !nativeDispatchStarted) {
|
|
643
|
+
stream.emit(payload.text);
|
|
644
|
+
emittedText = payload.text;
|
|
645
|
+
nativeDispatchStarted = true;
|
|
646
|
+
tokensEmitted = true;
|
|
647
|
+
}
|
|
648
|
+
if (tokensEmitted) {
|
|
649
|
+
const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
|
|
650
|
+
pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
|
|
651
|
+
streamFinalizationPending = true;
|
|
652
|
+
tokensEmitted = false;
|
|
653
|
+
if (!hasMedia) return;
|
|
654
|
+
const { presentation: _presentation, presentationTextMode: _mode, ...mediaPayload } = payload;
|
|
655
|
+
return {
|
|
656
|
+
...mediaPayload,
|
|
657
|
+
text: void 0
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
return payload;
|
|
661
|
+
},
|
|
662
|
+
claimNativeDelivery() {
|
|
663
|
+
if (!nativeDispatchStarted || nativeDeliveryClaimed) return false;
|
|
664
|
+
nativeDeliveryClaimed = true;
|
|
665
|
+
return true;
|
|
666
|
+
},
|
|
667
|
+
async finalize() {
|
|
668
|
+
progressDraft.markFinalReplyStarted();
|
|
669
|
+
if (!stream || !nativeDispatchStarted) {
|
|
670
|
+
releaseStreamChunkSubscription();
|
|
671
|
+
return { visibleReplySent: false };
|
|
672
|
+
}
|
|
673
|
+
let logicalContent;
|
|
1058
674
|
try {
|
|
1059
|
-
|
|
1060
|
-
if (
|
|
1061
|
-
|
|
675
|
+
if (wasCanceled() || !streamFinalizationPending) return acknowledgedNativeDelivery();
|
|
676
|
+
if (replacementSettlementPending && replacementFinalPending && pendingFinalPayload?.text) {
|
|
677
|
+
replacementFinalPending = false;
|
|
678
|
+
deferredReplacementEntries.push({
|
|
679
|
+
kind: "replacement",
|
|
680
|
+
payload: pendingFinalPayload
|
|
681
|
+
});
|
|
682
|
+
logicalContent = deferredReplacementLogicalContent();
|
|
683
|
+
queueFinalReplacement(stream, pendingFinalPayload.text);
|
|
684
|
+
}
|
|
685
|
+
logicalContent ??= replacementSettlementPending ? deferredReplacementLogicalContent() : void 0;
|
|
686
|
+
const logicalText = pendingFinalPayload?.text ?? (emittedText || void 0);
|
|
687
|
+
const finalActivity = queuedFinalActivity ?? finalStreamActivity(logicalText);
|
|
688
|
+
const content = finalActivity.text;
|
|
689
|
+
logicalContent ??= content !== logicalText ? logicalText : void 0;
|
|
690
|
+
if (!queuedFinalActivity) {
|
|
691
|
+
if (content !== void 0 && content !== emittedText) {
|
|
692
|
+
stream.clearText();
|
|
693
|
+
replacementTextAwaitingAcknowledgement = {
|
|
694
|
+
text: content,
|
|
695
|
+
logicalText
|
|
696
|
+
};
|
|
697
|
+
stream.emit(finalActivity);
|
|
698
|
+
} else stream.emit({
|
|
699
|
+
...finalActivity,
|
|
700
|
+
text: void 0
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
const result = await stream.close();
|
|
704
|
+
if (!result) return finalizeWithoutReceipt(logicalContent, wasCanceled());
|
|
705
|
+
const messageId = require_conversation_store_state.extractMessageId(result) ?? acknowledgedStreamId;
|
|
706
|
+
const postNativePayloads = replacementSettlementPending ? deferredReplacementPayloads(void 0) : pendingFinalPayload?.presentation ? [{
|
|
707
|
+
...pendingFinalPayload,
|
|
708
|
+
text: void 0
|
|
709
|
+
}] : [];
|
|
1062
710
|
return {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
711
|
+
visibleReplySent: true,
|
|
712
|
+
...content === void 0 ? {} : { content },
|
|
713
|
+
...logicalContent ? { logicalContent } : {},
|
|
714
|
+
...messageId ? { messageId } : {},
|
|
715
|
+
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
1066
716
|
};
|
|
1067
717
|
} catch (err) {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
preserveFilenames: params.preserveFilenames,
|
|
1081
|
-
fetchImpl: (input, init) => fetchWithAuthFallback({
|
|
1082
|
-
url: (0, openclaw_plugin_sdk_request_url.resolveRequestUrl)(input),
|
|
1083
|
-
tokenProvider: params.tokenProvider,
|
|
1084
|
-
fetchFn: params.fetchFn,
|
|
1085
|
-
requestInit: init,
|
|
1086
|
-
resolveFn: params.resolveFn,
|
|
1087
|
-
policy,
|
|
1088
|
-
deadline: params.deadline,
|
|
1089
|
-
bearerToken: candidate.graphReference ? params.graphReferenceToken : void 0
|
|
1090
|
-
})
|
|
1091
|
-
});
|
|
1092
|
-
} catch (err) {
|
|
1093
|
-
if (candidate.graphReference) {
|
|
1094
|
-
params.logger?.warn?.("msteams SharePoint reference download failed", {
|
|
1095
|
-
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
1096
|
-
name: candidate.fileHint
|
|
1097
|
-
});
|
|
1098
|
-
return;
|
|
718
|
+
if (err instanceof Error && err.name === "StreamCancelledError") return acknowledgedNativeDelivery();
|
|
719
|
+
params.log?.warn?.(`msteams stream finalize failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
|
|
720
|
+
return finalizeWithoutReceipt(logicalContent);
|
|
721
|
+
} finally {
|
|
722
|
+
pendingFinalPayload = void 0;
|
|
723
|
+
streamFinalizationPending = false;
|
|
724
|
+
queuedFinalActivity = void 0;
|
|
725
|
+
replacementFinalPending = false;
|
|
726
|
+
replacementSettlementPending = false;
|
|
727
|
+
replacementTextAwaitingAcknowledgement = void 0;
|
|
728
|
+
deferredReplacementEntries = [];
|
|
729
|
+
releaseStreamChunkSubscription();
|
|
1099
730
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
731
|
+
},
|
|
732
|
+
hasStream() {
|
|
733
|
+
return Boolean(stream);
|
|
734
|
+
},
|
|
735
|
+
isStreamActive() {
|
|
736
|
+
return Boolean(stream) && tokensEmitted && !wasCanceled();
|
|
737
|
+
},
|
|
738
|
+
wasCanceled
|
|
1103
739
|
};
|
|
1104
|
-
const out = [];
|
|
1105
|
-
for (const candidate of candidates) {
|
|
1106
|
-
const media = await downloadCandidate(candidate) ?? { kind: candidate.mediaKind };
|
|
1107
|
-
out.push(candidate.sourceId ? {
|
|
1108
|
-
...media,
|
|
1109
|
-
sourceId: candidate.sourceId
|
|
1110
|
-
} : media);
|
|
1111
|
-
}
|
|
1112
|
-
return out;
|
|
1113
|
-
}
|
|
1114
|
-
function safeHostForLog(url) {
|
|
1115
|
-
return URL.parse(url)?.host ?? "invalid-url";
|
|
1116
740
|
}
|
|
1117
741
|
//#endregion
|
|
1118
|
-
//#region extensions/msteams/src/
|
|
1119
|
-
function
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
};
|
|
1126
|
-
}
|
|
1127
|
-
function buildMSTeamsGraphMessageUrl(params) {
|
|
1128
|
-
const conversationType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(params.conversationType ?? "");
|
|
1129
|
-
const messageId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.messageId);
|
|
1130
|
-
if (!messageId) return;
|
|
1131
|
-
if (conversationType === "channel") {
|
|
1132
|
-
const teamAadGroupId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.teamAadGroupId);
|
|
1133
|
-
const channelId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.channelId);
|
|
1134
|
-
if (!teamAadGroupId || !channelId) return;
|
|
1135
|
-
const messageRoot = `${require_resolve_allowlist.GRAPH_ROOT}/teams/${encodeURIComponent(teamAadGroupId)}/channels/${encodeURIComponent(channelId)}/messages`;
|
|
1136
|
-
const threadRootMessageId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.threadRootMessageId);
|
|
1137
|
-
return threadRootMessageId && threadRootMessageId !== messageId ? `${messageRoot}/${encodeURIComponent(threadRootMessageId)}/replies/${encodeURIComponent(messageId)}` : `${messageRoot}/${encodeURIComponent(messageId)}`;
|
|
1138
|
-
}
|
|
1139
|
-
const chatId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.conversationId);
|
|
1140
|
-
if (!chatId) return;
|
|
1141
|
-
return `${require_resolve_allowlist.GRAPH_ROOT}/chats/${encodeURIComponent(chatId)}/messages/${encodeURIComponent(messageId)}`;
|
|
1142
|
-
}
|
|
1143
|
-
async function withGraphMediaResponse(params, url, kind, read) {
|
|
1144
|
-
const { response, release } = await (0, openclaw_plugin_sdk_ssrf_runtime.fetchWithSsrFGuard)({
|
|
1145
|
-
url,
|
|
1146
|
-
fetchImpl: params.fetchFn ?? fetch,
|
|
1147
|
-
init: { headers: require_resolve_allowlist.ensureUserAgentHeader({ Authorization: `Bearer ${params.accessToken}` }) },
|
|
1148
|
-
policy: params.ssrfPolicy,
|
|
1149
|
-
auditContext: `msteams.graph.${kind}`,
|
|
1150
|
-
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
742
|
+
//#region extensions/msteams/src/reply-dispatcher.ts
|
|
743
|
+
function createMSTeamsReplyDispatcher(params) {
|
|
744
|
+
const core = require_runtime.getMSTeamsRuntime();
|
|
745
|
+
const msteamsCfg = require_resolve_allowlist.resolveMSTeamsAccountConfig(params.cfg, params.accountId);
|
|
746
|
+
const mediaMaxBytes = (0, openclaw_plugin_sdk_account_helpers.resolveChannelMediaMaxBytes)({
|
|
747
|
+
cfg: params.cfg,
|
|
748
|
+
resolveChannelLimitMb: () => msteamsCfg.mediaMaxMb
|
|
1151
749
|
});
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
750
|
+
const feedbackLoopEnabled = msteamsCfg.feedbackEnabled !== false;
|
|
751
|
+
const conversationType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(params.conversationRef.conversation?.conversationType);
|
|
752
|
+
const isTypingSupported = conversationType === "personal" || conversationType === "groupchat";
|
|
753
|
+
const TYPING_KEEPALIVE_INTERVAL_MS = 8e3;
|
|
754
|
+
const TYPING_KEEPALIVE_MAX_DURATION_MS = 6e5;
|
|
755
|
+
const sendTypingIndicator = async () => {
|
|
756
|
+
if (!isTypingSupported || streamController.isStreamActive() || streamController.wasCanceled()) return;
|
|
757
|
+
await params.context.sendActivity({ type: "typing" });
|
|
758
|
+
};
|
|
759
|
+
const { onModelSelected, typingCallbacks, ...replyPipeline } = (0, openclaw_plugin_sdk_channel_outbound.createChannelMessageReplyPipeline)({
|
|
760
|
+
cfg: params.cfg,
|
|
761
|
+
agentId: params.agentId,
|
|
762
|
+
channel: "msteams",
|
|
763
|
+
accountId: params.accountId,
|
|
764
|
+
typing: {
|
|
765
|
+
start: sendTypingIndicator,
|
|
766
|
+
keepaliveIntervalMs: TYPING_KEEPALIVE_INTERVAL_MS,
|
|
767
|
+
maxDurationMs: TYPING_KEEPALIVE_MAX_DURATION_MS,
|
|
768
|
+
onStartError: (err) => {
|
|
769
|
+
(0, openclaw_plugin_sdk_channel_outbound.logTypingFailure)({
|
|
770
|
+
log: (message) => params.log.debug?.(message),
|
|
771
|
+
channel: "msteams",
|
|
772
|
+
action: "start",
|
|
773
|
+
error: err
|
|
774
|
+
});
|
|
775
|
+
}
|
|
1176
776
|
}
|
|
1177
777
|
});
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
778
|
+
const chunkMode = core.channel.text.resolveChunkMode(params.cfg, "msteams", params.accountId);
|
|
779
|
+
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
|
780
|
+
cfg: params.cfg,
|
|
781
|
+
channel: "msteams",
|
|
782
|
+
accountId: params.accountId
|
|
783
|
+
});
|
|
784
|
+
const hookRunner = (0, openclaw_plugin_sdk_plugin_runtime.getGlobalHookRunner)();
|
|
785
|
+
const streamController = createTeamsReplyStreamController({
|
|
786
|
+
tableMode,
|
|
787
|
+
allowProviderPreview: !((hookRunner?.hasHooks("reply_payload_sending") ?? false) || (hookRunner?.hasHooks("message_sending") ?? false)),
|
|
788
|
+
conversationType,
|
|
789
|
+
context: params.context,
|
|
790
|
+
feedbackLoopEnabled,
|
|
791
|
+
log: params.log,
|
|
792
|
+
msteamsConfig: msteamsCfg,
|
|
793
|
+
progressSeed: `${params.accountId ?? "default"}:${params.conversationRef.conversation?.id ?? ""}`
|
|
794
|
+
});
|
|
795
|
+
const teamsStreamMode = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelPreviewStreamMode)(msteamsCfg, "partial");
|
|
796
|
+
const blockStreamingResolved = teamsStreamMode === "block" ? true : (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingBlockEnabled)(msteamsCfg);
|
|
797
|
+
const blockStreamingEnabled = blockStreamingResolved ?? false;
|
|
798
|
+
const typingIndicatorEnabled = typeof msteamsCfg?.typingIndicator === "boolean" ? msteamsCfg.typingIndicator : true;
|
|
799
|
+
const pendingDeliveries = [];
|
|
800
|
+
let pendingSettlement;
|
|
801
|
+
const findPendingNativeDelivery = () => pendingDeliveries.find((candidate) => !candidate.nativeSettled);
|
|
802
|
+
const queueDeliveryFailureSystemEvent = (failure) => {
|
|
803
|
+
const classification = require_channel_setup.classifyMSTeamsSendError(failure.error);
|
|
804
|
+
const errorText = require_channel_setup.formatUnknownError(failure.error);
|
|
805
|
+
const failedAll = failure.failed >= failure.total;
|
|
806
|
+
const ambiguous = classification.kind === "ambiguous";
|
|
807
|
+
const sentences = [
|
|
808
|
+
`Microsoft Teams delivery failed: ${ambiguous ? failedAll ? "the delivery outcome is unknown for the previous reply" : `the delivery outcome is unknown for ${failure.failed} of ${failure.total} message blocks` : failedAll ? "the previous reply was not delivered" : `${failure.failed} of ${failure.total} message blocks were not delivered`}.`,
|
|
809
|
+
ambiguous ? void 0 : `The user may not have received ${failedAll ? "that reply" : "the full reply"}.`,
|
|
810
|
+
`Error: ${errorText}.`,
|
|
811
|
+
classification.statusCode != null ? `Status: ${classification.statusCode}.` : void 0,
|
|
812
|
+
require_channel_setup.formatMSTeamsDeliveryFailureGuidance(classification)
|
|
813
|
+
].filter(Boolean);
|
|
814
|
+
core.system.enqueueSystemEvent(sentences.join(" "), {
|
|
815
|
+
sessionKey: params.sessionKey,
|
|
816
|
+
contextKey: `msteams:delivery-failure:${params.conversationRef.conversation?.id ?? "unknown"}`
|
|
817
|
+
});
|
|
1191
818
|
};
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
...params,
|
|
1198
|
-
url: `${params.messageUrl}/hostedContents`
|
|
819
|
+
const renderReplyPayload = (payload) => {
|
|
820
|
+
return require_conversation_store_state.renderReplyPayloadsToMessages([payload], {
|
|
821
|
+
textChunkLimit: params.textLimit,
|
|
822
|
+
tableMode,
|
|
823
|
+
chunkMode
|
|
1199
824
|
});
|
|
1200
|
-
} catch (err) {
|
|
1201
|
-
params.logger?.warn?.("msteams graph hostedContents fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1202
|
-
return {
|
|
1203
|
-
media: [],
|
|
1204
|
-
count: 0
|
|
1205
|
-
};
|
|
1206
|
-
}
|
|
1207
|
-
if (hosted.items.length === 0) return {
|
|
1208
|
-
media: [],
|
|
1209
|
-
status: hosted.status,
|
|
1210
|
-
count: 0
|
|
1211
825
|
};
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
826
|
+
const settlePendingDelivery = (delivery) => {
|
|
827
|
+
if (delivery.settled || !delivery.blockSettled || !delivery.nativeSettled) return;
|
|
828
|
+
delivery.settled = true;
|
|
829
|
+
const acceptedParts = [...delivery.nativeResult ? [delivery.nativeResult] : [], ...delivery.blockResults];
|
|
830
|
+
const messageIds = acceptedParts.flatMap((part) => part.messageIds);
|
|
831
|
+
const content = delivery.errors.length > 0 ? acceptedParts.map((part) => part.content).filter(Boolean).join("\n") : delivery.content;
|
|
832
|
+
const outcome = {
|
|
833
|
+
visibleReplySent: acceptedParts.length > 0,
|
|
834
|
+
...messageIds.length > 0 ? { messageIds } : {},
|
|
835
|
+
...acceptedParts.length > 0 && content !== void 0 ? { content } : {}
|
|
836
|
+
};
|
|
837
|
+
if (delivery.errors.length === 0) {
|
|
838
|
+
delivery.finalization.resolve(outcome);
|
|
839
|
+
return;
|
|
1217
840
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
841
|
+
const error = delivery.errors.find((candidate) => !(candidate instanceof openclaw_plugin_sdk_error_runtime.PlatformMessageNotDispatchedError)) ?? delivery.errors[0];
|
|
842
|
+
delivery.finalization.reject(outcome.visibleReplySent ? (0, openclaw_plugin_sdk_channel_inbound.createChannelPartialDeliveryError)(error, {
|
|
843
|
+
...outcome,
|
|
844
|
+
visibleReplySent: true
|
|
845
|
+
}) : error);
|
|
846
|
+
};
|
|
847
|
+
const flushPendingMessages = async () => {
|
|
848
|
+
for (const delivery of pendingDeliveries) {
|
|
849
|
+
if (delivery.blockSettled) continue;
|
|
850
|
+
const toSend = delivery.messages.splice(0);
|
|
851
|
+
const total = toSend.length;
|
|
852
|
+
let failed = 0;
|
|
853
|
+
let lastFailedError;
|
|
854
|
+
const sentIds = [];
|
|
855
|
+
for (const msg of toSend) try {
|
|
856
|
+
const msgIds = await require_conversation_store_state.sendMSTeamsMessages({
|
|
857
|
+
accountId: params.accountId,
|
|
858
|
+
replyStyle: params.replyStyle,
|
|
859
|
+
app: params.app,
|
|
860
|
+
conversationRef: params.conversationRef,
|
|
861
|
+
context: params.context,
|
|
862
|
+
messages: [msg],
|
|
863
|
+
onRetry: (event) => {
|
|
864
|
+
params.log.debug?.("retrying send", {
|
|
865
|
+
replyStyle: params.replyStyle,
|
|
866
|
+
...event
|
|
867
|
+
});
|
|
868
|
+
},
|
|
869
|
+
tokenProvider: params.tokenProvider,
|
|
870
|
+
sharePointSiteId: params.sharePointSiteId,
|
|
871
|
+
mediaMaxBytes,
|
|
872
|
+
feedbackLoopEnabled,
|
|
873
|
+
serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(msteamsCfg)
|
|
1231
874
|
});
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
sourceId
|
|
875
|
+
const validIds = msgIds.filter((id) => id.trim() && id !== "unknown");
|
|
876
|
+
if (msgIds.length > 0) delivery.blockResults.push({
|
|
877
|
+
messageIds: validIds,
|
|
878
|
+
...msg.text ? { content: msg.text } : {}
|
|
1237
879
|
});
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
880
|
+
sentIds.push(...validIds);
|
|
881
|
+
} catch (msgError) {
|
|
882
|
+
failed += 1;
|
|
883
|
+
lastFailedError = msgError;
|
|
884
|
+
delivery.errors.push(msgError);
|
|
885
|
+
params.log.debug?.("individual message send failed, continuing with remaining blocks");
|
|
886
|
+
}
|
|
887
|
+
if (failed > 0) {
|
|
888
|
+
const classification = require_channel_setup.classifyMSTeamsSendError(lastFailedError);
|
|
889
|
+
const hint = require_channel_setup.formatMSTeamsSendErrorHint(classification);
|
|
890
|
+
params.log.warn?.(`failed to deliver ${failed} of ${total} message blocks`, {
|
|
891
|
+
failed,
|
|
892
|
+
total,
|
|
893
|
+
error: require_channel_setup.formatUnknownError(lastFailedError),
|
|
894
|
+
classification,
|
|
895
|
+
...hint ? { hint } : {}
|
|
896
|
+
});
|
|
897
|
+
queueDeliveryFailureSystemEvent({
|
|
898
|
+
failed,
|
|
899
|
+
total,
|
|
900
|
+
error: lastFailedError
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
delivery.blockSettled = true;
|
|
904
|
+
settlePendingDelivery(delivery);
|
|
905
|
+
if (sentIds.length > 0) try {
|
|
906
|
+
params.onSentMessageIds?.(sentIds);
|
|
907
|
+
} catch (error) {
|
|
908
|
+
params.log.warn?.("failed to record sent Teams message ids", { error: require_channel_setup.formatUnknownError(error) });
|
|
909
|
+
}
|
|
1243
910
|
}
|
|
1244
|
-
}
|
|
1245
|
-
return {
|
|
1246
|
-
media: out,
|
|
1247
|
-
status: hosted.status,
|
|
1248
|
-
count: hosted.items.length
|
|
1249
911
|
};
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
});
|
|
1258
|
-
const ssrfPolicy = (0, openclaw_plugin_sdk_ssrf_policy.buildHostnameAllowlistPolicyFromSuffixAllowlist)(policy.allowHosts);
|
|
1259
|
-
const messageUrl = params.messageUrl;
|
|
1260
|
-
const logGraphFailure = (phase, err) => {
|
|
1261
|
-
params.logger?.debug?.(`graph media ${phase} failed`, {
|
|
1262
|
-
messageUrl,
|
|
1263
|
-
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)
|
|
1264
|
-
});
|
|
1265
|
-
params.logger?.warn?.(`msteams graph ${phase} failed`, {
|
|
1266
|
-
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
1267
|
-
...phase === "message parse" ? { messageUrl } : {}
|
|
1268
|
-
});
|
|
912
|
+
const dispatcherOptions = {
|
|
913
|
+
...replyPipeline,
|
|
914
|
+
humanDelay: (0, openclaw_plugin_sdk_agent_runtime.resolveHumanDelayConfig)(params.cfg, params.agentId),
|
|
915
|
+
onReplyStart: async () => {
|
|
916
|
+
if (typingIndicatorEnabled) await typingCallbacks?.onReplyStart?.();
|
|
917
|
+
},
|
|
918
|
+
typingCallbacks
|
|
1269
919
|
};
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
920
|
+
const delivery = {
|
|
921
|
+
observeMessageSent: true,
|
|
922
|
+
deliver: async (payload) => {
|
|
923
|
+
if (pendingSettlement) await pendingSettlement;
|
|
924
|
+
let resolvedPayload = payload;
|
|
925
|
+
if (payload.presentation) {
|
|
926
|
+
let nativePayload;
|
|
927
|
+
const fallbackPayload = await (0, openclaw_plugin_sdk_interactive_runtime.renderPresentationForDelivery)({
|
|
928
|
+
presentationCapabilities: require_presentation.MSTEAMS_PRESENTATION_CAPABILITIES,
|
|
929
|
+
renderPresentation: (adapted) => {
|
|
930
|
+
nativePayload = adapted;
|
|
931
|
+
return adapted;
|
|
932
|
+
}
|
|
933
|
+
}, payload);
|
|
934
|
+
resolvedPayload = nativePayload ?? fallbackPayload;
|
|
935
|
+
}
|
|
936
|
+
const preparedPayload = streamController.preparePayload(resolvedPayload);
|
|
937
|
+
const native = streamController.claimNativeDelivery();
|
|
938
|
+
if (preparedPayload && !native && findPendingNativeDelivery()) await settleDelivery();
|
|
939
|
+
const messages = preparedPayload && !streamController.wasCanceled() ? renderReplyPayload(preparedPayload) : [];
|
|
940
|
+
if (!native && messages.length === 0) return {
|
|
941
|
+
visibleReplySent: false,
|
|
942
|
+
suppression: { reason: "no_visible_result" }
|
|
943
|
+
};
|
|
944
|
+
const pending = {
|
|
945
|
+
messages,
|
|
946
|
+
finalization: (0, openclaw_plugin_sdk_extension_shared.createDeferred)(),
|
|
947
|
+
content: payload.text,
|
|
948
|
+
blockResults: [],
|
|
949
|
+
nativeSettled: !native,
|
|
950
|
+
blockSettled: messages.length === 0,
|
|
951
|
+
settled: false,
|
|
952
|
+
errors: []
|
|
953
|
+
};
|
|
954
|
+
pendingDeliveries.push(pending);
|
|
955
|
+
if (blockStreamingEnabled) await flushPendingMessages();
|
|
956
|
+
settlePendingDelivery(pending);
|
|
957
|
+
return {
|
|
958
|
+
visibleReplySent: false,
|
|
959
|
+
finalization: pending.finalization.promise
|
|
960
|
+
};
|
|
961
|
+
},
|
|
962
|
+
onError: (err, info) => {
|
|
963
|
+
const errMsg = require_channel_setup.formatUnknownError(err);
|
|
964
|
+
const classification = require_channel_setup.classifyMSTeamsSendError(err);
|
|
965
|
+
const hint = require_channel_setup.formatMSTeamsSendErrorHint(classification);
|
|
966
|
+
params.log.error("reply failed", {
|
|
967
|
+
kind: info.kind,
|
|
968
|
+
error: errMsg,
|
|
969
|
+
classification,
|
|
970
|
+
hint
|
|
1308
971
|
});
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
const settleDelivery = () => pendingSettlement ??= Promise.resolve().then(async () => {
|
|
975
|
+
await flushPendingMessages();
|
|
976
|
+
const nativeDelivery = findPendingNativeDelivery();
|
|
977
|
+
if (!nativeDelivery) {
|
|
978
|
+
await streamController.finalize();
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
let nativeResult;
|
|
982
|
+
try {
|
|
983
|
+
nativeResult = await streamController.finalize();
|
|
984
|
+
} catch (error) {
|
|
985
|
+
nativeDelivery.errors.push(error);
|
|
986
|
+
nativeDelivery.nativeSettled = true;
|
|
987
|
+
settlePendingDelivery(nativeDelivery);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
if (nativeResult.visibleReplySent) nativeDelivery.nativeResult = {
|
|
991
|
+
messageIds: nativeResult.messageId ? [nativeResult.messageId] : [],
|
|
992
|
+
...nativeResult.content !== void 0 ? { content: nativeResult.content } : {}
|
|
993
|
+
};
|
|
994
|
+
const hasPostNativePayloads = Boolean(nativeResult.postNativePayloads?.length);
|
|
995
|
+
if (nativeResult.logicalContent !== void 0) nativeDelivery.content = nativeResult.logicalContent;
|
|
996
|
+
else if (nativeResult.content !== void 0 && (!nativeResult.fallbackPayload && !hasPostNativePayloads || nativeDelivery.content === void 0)) nativeDelivery.content = nativeResult.content;
|
|
997
|
+
const afterNativePayloads = [...nativeResult.fallbackPayload ? [nativeResult.fallbackPayload] : [], ...nativeResult.postNativePayloads ?? []];
|
|
998
|
+
if (afterNativePayloads.length > 0) {
|
|
999
|
+
nativeDelivery.messages.push(...afterNativePayloads.flatMap((payload) => renderReplyPayload(payload)));
|
|
1000
|
+
nativeDelivery.blockSettled = nativeDelivery.messages.length === 0;
|
|
1001
|
+
}
|
|
1002
|
+
nativeDelivery.nativeSettled = true;
|
|
1003
|
+
if (!nativeDelivery.blockSettled) await flushPendingMessages();
|
|
1004
|
+
settlePendingDelivery(nativeDelivery);
|
|
1005
|
+
if (nativeResult.messageId) try {
|
|
1006
|
+
params.onSentMessageIds?.([nativeResult.messageId]);
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
params.log.warn?.("failed to record sent Teams message id", { error: require_channel_setup.formatUnknownError(error) });
|
|
1009
|
+
}
|
|
1010
|
+
}).finally(() => {
|
|
1011
|
+
pendingSettlement = void 0;
|
|
1328
1012
|
});
|
|
1329
|
-
const
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
}
|
|
1013
|
+
const shouldSuppressDefaultToolProgressMessages = streamController.hasStream() && teamsStreamMode === "progress";
|
|
1014
|
+
const progressCallbacks = streamController.hasStream() ? {
|
|
1015
|
+
onReasoningStream: async (payload) => {
|
|
1016
|
+
await streamController.pushReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true });
|
|
1017
|
+
return false;
|
|
1018
|
+
},
|
|
1019
|
+
onReasoningEnd: () => {
|
|
1020
|
+
streamController.resetReasoningProgress();
|
|
1021
|
+
return false;
|
|
1022
|
+
},
|
|
1023
|
+
onToolStart: streamController.pushToolEvent,
|
|
1024
|
+
onItemEvent: streamController.pushItemEvent,
|
|
1025
|
+
onPlanUpdate: async (payload) => {
|
|
1026
|
+
if (payload.phase !== "update") return false;
|
|
1027
|
+
await streamController.pushPlanProgress(payload.steps, payload);
|
|
1028
|
+
return false;
|
|
1029
|
+
},
|
|
1030
|
+
onApprovalEvent: async (payload) => {
|
|
1031
|
+
await streamController.pushApprovalEvent(payload);
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
} : {};
|
|
1352
1035
|
return {
|
|
1353
|
-
|
|
1354
|
-
...
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1036
|
+
dispatcherOptions: {
|
|
1037
|
+
...dispatcherOptions,
|
|
1038
|
+
onSettled: settleDelivery
|
|
1039
|
+
},
|
|
1040
|
+
delivery,
|
|
1041
|
+
replyOptions: {
|
|
1042
|
+
progressPreambleEnabled: shouldSuppressDefaultToolProgressMessages,
|
|
1043
|
+
commentaryProgressEnabled: shouldSuppressDefaultToolProgressMessages,
|
|
1044
|
+
...streamController.hasStream() ? { onPartialReply: (payload) => {
|
|
1045
|
+
streamController.onPartialReply(payload);
|
|
1046
|
+
return false;
|
|
1047
|
+
} } : {},
|
|
1048
|
+
...progressCallbacks,
|
|
1049
|
+
...shouldSuppressDefaultToolProgressMessages ? { suppressDefaultToolProgressMessages: true } : {},
|
|
1050
|
+
disableBlockStreaming: blockStreamingResolved == null ? void 0 : !blockStreamingResolved,
|
|
1051
|
+
onModelSelected
|
|
1052
|
+
}
|
|
1363
1053
|
};
|
|
1364
1054
|
}
|
|
1365
1055
|
//#endregion
|
|
1366
|
-
//#region extensions/msteams/src/
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
const unavailableCount = params.materializedMedia.filter((media) => !media.path).length + Math.max(0, params.nativeMedia.length - params.materializedMedia.length);
|
|
1373
|
-
if (unavailableCount === 0) return params.body;
|
|
1374
|
-
return (0, openclaw_plugin_sdk_channel_inbound.formatInboundMediaUnavailableText)({
|
|
1375
|
-
body: params.body,
|
|
1376
|
-
notice: `[msteams ${unavailableCount > 1 ? `${unavailableCount} attachments` : "attachment"} unavailable]`
|
|
1377
|
-
});
|
|
1378
|
-
}
|
|
1379
|
-
function hasDefinitiveContentType(media) {
|
|
1380
|
-
const contentType = media.contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
1381
|
-
return Boolean(contentType && contentType !== "application/octet-stream" && contentType !== "binary/octet-stream");
|
|
1056
|
+
//#region extensions/msteams/src/team-identity.ts
|
|
1057
|
+
const teamGroupIdCache = /* @__PURE__ */ new Map();
|
|
1058
|
+
const TEAM_GROUP_ID_CACHE_MAX_ENTRIES = 500;
|
|
1059
|
+
function cacheTeamGroupId(conversationTeamId, groupId) {
|
|
1060
|
+
teamGroupIdCache.set(conversationTeamId, groupId);
|
|
1061
|
+
(0, openclaw_plugin_sdk_collection_runtime.pruneMapToMaxSize)(teamGroupIdCache, TEAM_GROUP_ID_CACHE_MAX_ENTRIES);
|
|
1382
1062
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
});
|
|
1390
|
-
for (const [index, materialized] of materializedMedia.entries()) {
|
|
1391
|
-
const sourceIndex = materialized.sourceId ? nativeIndexBySourceId.get(materialized.sourceId) : void 0;
|
|
1392
|
-
const positionalIndex = options.positionallyAligned === false || index >= nativeMedia.length ? void 0 : index;
|
|
1393
|
-
const unresolvedIndexes = options.positionallyAligned === false ? merged.slice(0, nativeSlotCount).flatMap((media, mediaIndex) => !media.path && !media.sourceId ? [mediaIndex] : []) : [];
|
|
1394
|
-
const fallbackIndex = unresolvedIndexes.find((mediaIndex) => merged[mediaIndex]?.kind === materialized.kind) ?? (unresolvedIndexes.length === 1 ? unresolvedIndexes[0] : void 0);
|
|
1395
|
-
const targetIndex = sourceIndex ?? positionalIndex ?? fallbackIndex;
|
|
1396
|
-
if (targetIndex === void 0) {
|
|
1397
|
-
if (materialized.sourceId) nativeIndexBySourceId.set(materialized.sourceId, merged.length);
|
|
1398
|
-
merged.push(materialized);
|
|
1399
|
-
} else {
|
|
1400
|
-
if (materialized.sourceId) nativeIndexBySourceId.set(materialized.sourceId, targetIndex);
|
|
1401
|
-
const current = merged[targetIndex];
|
|
1402
|
-
if (materialized.path) merged[targetIndex] = materialized;
|
|
1403
|
-
else if (!current?.path) merged[targetIndex] = {
|
|
1404
|
-
...current,
|
|
1405
|
-
...materialized,
|
|
1406
|
-
kind: hasDefinitiveContentType(materialized) || !current?.kind ? materialized.kind : current.kind
|
|
1407
|
-
};
|
|
1408
|
-
}
|
|
1063
|
+
/** Resolve the Graph team GUID without ever treating a Bot Framework team ID as equivalent. */
|
|
1064
|
+
async function resolveTeamGroupId(params) {
|
|
1065
|
+
const activityGroupId = params.aadGroupId?.trim();
|
|
1066
|
+
if (activityGroupId) {
|
|
1067
|
+
cacheTeamGroupId(params.conversationTeamId, activityGroupId);
|
|
1068
|
+
return activityGroupId;
|
|
1409
1069
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
async function resolveMSTeamsInboundMedia(params) {
|
|
1416
|
-
const { attachments, htmlSummary, maxBytes, tokenProvider, allowHosts, conversationType, conversationId, conversationMessageId, teamAadGroupId, serviceUrl, activity, log } = params;
|
|
1417
|
-
let mediaList = await downloadMSTeamsAttachments({
|
|
1418
|
-
attachments,
|
|
1419
|
-
maxBytes,
|
|
1420
|
-
tokenProvider,
|
|
1421
|
-
allowHosts,
|
|
1422
|
-
authAllowHosts: params.authAllowHosts,
|
|
1070
|
+
const cached = teamGroupIdCache.get(params.conversationTeamId);
|
|
1071
|
+
if (cached) return cached;
|
|
1072
|
+
const getTeamDetails = params.getTeamDetails;
|
|
1073
|
+
if (!getTeamDetails) return;
|
|
1074
|
+
const groupId = (await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
1423
1075
|
deadline: params.deadline,
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
conversationType,
|
|
1431
|
-
htmlSummary,
|
|
1432
|
-
graphMediaFallback: params.graphMediaFallback
|
|
1433
|
-
});
|
|
1434
|
-
const shouldFetchGraphMessage = hasHtmlFileAttachment || hasChannelOrGroupHtml;
|
|
1435
|
-
const isBotFrameworkPersonalChat = isBotFrameworkPersonalChatId(conversationId);
|
|
1436
|
-
if (hasHtmlFileAttachment && isBotFrameworkPersonalChat) {
|
|
1437
|
-
if (!serviceUrl) log.debug?.("bot framework attachment skipped (missing serviceUrl)", {
|
|
1438
|
-
conversationType,
|
|
1439
|
-
conversationId
|
|
1440
|
-
});
|
|
1441
|
-
else {
|
|
1442
|
-
const bfMedia = await downloadMSTeamsBotFrameworkAttachments({
|
|
1443
|
-
serviceUrl,
|
|
1444
|
-
attachmentIds,
|
|
1445
|
-
tokenProvider,
|
|
1446
|
-
maxBytes,
|
|
1447
|
-
allowHosts,
|
|
1448
|
-
authAllowHosts: params.authAllowHosts,
|
|
1449
|
-
deadline: params.deadline
|
|
1450
|
-
});
|
|
1451
|
-
mediaList = mergeMSTeamsMediaFacts(mediaList, bfMedia.media, { positionallyAligned: false });
|
|
1452
|
-
if (!hasMaterializedMedia(bfMedia.media)) log.debug?.("bot framework attachments fetch empty", {
|
|
1453
|
-
conversationType,
|
|
1454
|
-
attachmentCount: bfMedia.attachmentCount ?? attachmentIds.length
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
if (shouldFetchGraphMessage && !isBotFrameworkPersonalChat) {
|
|
1459
|
-
const graphTeamAadGroupId = conversationType.trim().toLowerCase() === "channel" && !teamAadGroupId ? await params.resolveTeamAadGroupId?.() : teamAadGroupId;
|
|
1460
|
-
const messageUrl = buildMSTeamsGraphMessageUrl({
|
|
1461
|
-
conversationType,
|
|
1462
|
-
conversationId,
|
|
1463
|
-
messageId: activity.id ?? void 0,
|
|
1464
|
-
threadRootMessageId: conversationMessageId ?? activity.replyToId,
|
|
1465
|
-
teamAadGroupId: graphTeamAadGroupId,
|
|
1466
|
-
channelId: activity.channelData?.channel?.id
|
|
1467
|
-
});
|
|
1468
|
-
if (!messageUrl) log.debug?.("graph message url unavailable", {
|
|
1469
|
-
conversationType,
|
|
1470
|
-
hasChannelData: Boolean(activity.channelData),
|
|
1471
|
-
messageId: activity.id ?? void 0,
|
|
1472
|
-
replyToId: activity.replyToId ?? void 0
|
|
1473
|
-
});
|
|
1474
|
-
else {
|
|
1475
|
-
const graphMedia = await downloadMSTeamsGraphMedia({
|
|
1476
|
-
messageUrl,
|
|
1477
|
-
tokenProvider,
|
|
1478
|
-
maxBytes,
|
|
1479
|
-
allowHosts,
|
|
1480
|
-
authAllowHosts: params.authAllowHosts,
|
|
1481
|
-
deadline: params.deadline,
|
|
1482
|
-
logger: log
|
|
1483
|
-
});
|
|
1484
|
-
mediaList = mergeMSTeamsMediaFacts(mediaList, graphMedia.media, { positionallyAligned: false });
|
|
1485
|
-
if (!hasMaterializedMedia(mediaList)) log.debug?.("graph media fetch empty", {
|
|
1486
|
-
messageUrl,
|
|
1487
|
-
hostedStatus: graphMedia.hostedStatus,
|
|
1488
|
-
attachmentStatus: graphMedia.attachmentStatus,
|
|
1489
|
-
hostedCount: graphMedia.hostedCount,
|
|
1490
|
-
attachmentCount: graphMedia.attachmentCount,
|
|
1491
|
-
tokenError: graphMedia.tokenError,
|
|
1492
|
-
attachmentIdCount: attachmentIds.length
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
const materializedCount = mediaList.filter((media) => Boolean(media.path)).length;
|
|
1498
|
-
if (materializedCount > 0) log.debug?.("downloaded attachments", { count: materializedCount });
|
|
1499
|
-
else if (htmlSummary?.imgTags) log.debug?.("inline images detected but none downloaded", {
|
|
1500
|
-
imgTags: htmlSummary.imgTags,
|
|
1501
|
-
srcHosts: htmlSummary.srcHosts,
|
|
1502
|
-
dataImages: htmlSummary.dataImages,
|
|
1503
|
-
cidImages: htmlSummary.cidImages
|
|
1504
|
-
});
|
|
1505
|
-
return mediaList;
|
|
1076
|
+
label: "MS Teams team details",
|
|
1077
|
+
work: () => getTeamDetails(params.conversationTeamId)
|
|
1078
|
+
})).aadGroupId?.trim();
|
|
1079
|
+
if (!groupId) return;
|
|
1080
|
+
cacheTeamGroupId(params.conversationTeamId, groupId);
|
|
1081
|
+
return groupId;
|
|
1506
1082
|
}
|
|
1507
1083
|
//#endregion
|
|
1508
|
-
//#region extensions/msteams/src/monitor-handler/
|
|
1509
|
-
|
|
1510
|
-
const
|
|
1511
|
-
const
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1084
|
+
//#region extensions/msteams/src/monitor-handler/config.ts
|
|
1085
|
+
function createMSTeamsHandlerConfigReader(deps) {
|
|
1086
|
+
const source = deps.accountPolicyCfg ?? deps.cfg;
|
|
1087
|
+
const read = (0, openclaw_plugin_sdk_runtime_config_snapshot.createRuntimeConfigReader)(source);
|
|
1088
|
+
const originallyConfigured = require_resolve_allowlist.listMSTeamsAccountIds(source).includes(deps.accountId);
|
|
1089
|
+
let lastSource;
|
|
1090
|
+
let lastProjection = deps.cfg;
|
|
1091
|
+
return () => {
|
|
1092
|
+
const current = read();
|
|
1093
|
+
if (current === lastSource) return lastProjection;
|
|
1094
|
+
lastSource = current;
|
|
1095
|
+
if (current === source && deps.accountPolicyCfg) return lastProjection = deps.cfg;
|
|
1096
|
+
const retired = originallyConfigured && !require_resolve_allowlist.listMSTeamsAccountIds(current).includes(deps.accountId);
|
|
1097
|
+
if (deps.accountId === "default" && !current.channels?.msteams?.accounts && !retired) return lastProjection = current;
|
|
1098
|
+
const accountConfig = require_resolve_allowlist.resolveMSTeamsAccountConfig(current, deps.accountId);
|
|
1099
|
+
return lastProjection = require_resolve_allowlist.withAccountScopedMSTeamsConfig({
|
|
1100
|
+
cfg: current,
|
|
1101
|
+
accountId: deps.accountId,
|
|
1102
|
+
accountConfig: {
|
|
1103
|
+
...accountConfig,
|
|
1104
|
+
...current.channels?.msteams?.enabled === false || retired ? { enabled: false } : {}
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
//#endregion
|
|
1110
|
+
//#region extensions/msteams/src/attachments/bot-framework.ts
|
|
1111
|
+
const BOT_FRAMEWORK_SCOPE = "https://api.botframework.com";
|
|
1112
|
+
/**
|
|
1113
|
+
* Detect Bot Framework personal chat ("a:") and MSA orgid ("8:orgid:") conversation
|
|
1114
|
+
* IDs. These identifiers are not recognized by Graph's `/chats/{id}` endpoint, so we
|
|
1115
|
+
* must fetch media via the Bot Framework v3 attachments endpoint instead.
|
|
1116
|
+
*
|
|
1117
|
+
* Graph-compatible IDs start with `19:` and are left untouched by this detector.
|
|
1118
|
+
*/
|
|
1119
|
+
function isBotFrameworkPersonalChatId(conversationId) {
|
|
1120
|
+
if (typeof conversationId !== "string") return false;
|
|
1121
|
+
const trimmed = conversationId.trim();
|
|
1122
|
+
return trimmed.startsWith("a:") || trimmed.startsWith("8:orgid:");
|
|
1123
|
+
}
|
|
1124
|
+
async function fetchBotFrameworkAttachment(params, kind) {
|
|
1125
|
+
let response;
|
|
1126
|
+
try {
|
|
1127
|
+
const headers = require_resolve_allowlist.ensureUserAgentHeader();
|
|
1128
|
+
require_resolve_allowlist.applyAuthorizationHeaderForUrl({
|
|
1129
|
+
headers,
|
|
1130
|
+
url: params.url,
|
|
1131
|
+
authAllowHosts: params.policy.authAllowHosts,
|
|
1132
|
+
bearerToken: params.accessToken
|
|
1133
|
+
});
|
|
1134
|
+
response = await require_resolve_allowlist.safeFetchWithPolicy({
|
|
1135
|
+
url: params.url,
|
|
1136
|
+
policy: params.policy,
|
|
1137
|
+
resolveFn: node_dns_promises.lookup,
|
|
1138
|
+
requestInit: { headers },
|
|
1139
|
+
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
1140
|
+
});
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
params.logger?.warn?.(`msteams botFramework ${kind} fetch failed`, { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (!response.ok) {
|
|
1146
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1147
|
+
params.logger?.warn?.(`msteams botFramework ${kind} non-ok`, { status: response.status });
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
return response;
|
|
1151
|
+
}
|
|
1152
|
+
async function downloadMSTeamsBotFrameworkAttachment(params) {
|
|
1153
|
+
if (!params.serviceUrl || !params.attachmentId || !params.tokenProvider) return;
|
|
1154
|
+
const tokenProvider = params.tokenProvider;
|
|
1155
|
+
const policy = require_resolve_allowlist.resolveAttachmentFetchPolicy({
|
|
1156
|
+
allowHosts: params.allowHosts,
|
|
1157
|
+
authAllowHosts: params.authAllowHosts
|
|
1515
1158
|
});
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1159
|
+
const baseUrl = `${params.serviceUrl.replace(/\/+$/, "")}/v3/attachments/${encodeURIComponent(params.attachmentId)}`;
|
|
1160
|
+
if (!(0, openclaw_plugin_sdk_ssrf_policy.isHttpsUrlAllowedByHostnameSuffixAllowlist)(baseUrl, policy.allowHosts)) return;
|
|
1161
|
+
let accessToken;
|
|
1162
|
+
try {
|
|
1163
|
+
accessToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
1164
|
+
deadline: params.deadline,
|
|
1165
|
+
label: "MS Teams Bot Framework token",
|
|
1166
|
+
work: () => tokenProvider.getAccessToken(BOT_FRAMEWORK_SCOPE)
|
|
1167
|
+
});
|
|
1168
|
+
} catch (err) {
|
|
1169
|
+
params.logger?.warn?.("msteams botFramework token acquisition failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1170
|
+
return;
|
|
1519
1171
|
}
|
|
1520
|
-
|
|
1521
|
-
const
|
|
1172
|
+
if (!accessToken) return;
|
|
1173
|
+
const request = {
|
|
1174
|
+
url: baseUrl,
|
|
1175
|
+
accessToken,
|
|
1176
|
+
policy,
|
|
1177
|
+
logger: params.logger,
|
|
1178
|
+
deadline: params.deadline
|
|
1179
|
+
};
|
|
1180
|
+
const infoResponse = await fetchBotFrameworkAttachment(request, "attachmentInfo");
|
|
1181
|
+
if (!infoResponse) return;
|
|
1182
|
+
let info;
|
|
1522
1183
|
try {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1184
|
+
info = await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(infoResponse, "msteams botFramework attachmentInfo");
|
|
1185
|
+
} catch (err) {
|
|
1186
|
+
request.logger?.warn?.("msteams botFramework attachmentInfo parse failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
if (!info) return;
|
|
1190
|
+
const views = Array.isArray(info.views) ? info.views : [];
|
|
1191
|
+
const candidateView = views.find((view) => view?.viewId === "original") ?? views.find((view) => typeof view?.viewId === "string");
|
|
1192
|
+
const viewId = typeof candidateView?.viewId === "string" && candidateView.viewId ? candidateView.viewId : void 0;
|
|
1193
|
+
if (!viewId) return;
|
|
1194
|
+
if (typeof candidateView?.size === "number" && candidateView.size > 0 && candidateView.size > params.maxBytes) return;
|
|
1195
|
+
const fileNameHint = typeof info.name === "string" && info.name || void 0;
|
|
1196
|
+
const contentTypeHint = typeof info.type === "string" && info.type || void 0;
|
|
1197
|
+
const viewUrl = `${baseUrl}/views/${encodeURIComponent(viewId)}`;
|
|
1198
|
+
const maxBytes = params.maxBytes;
|
|
1199
|
+
const response = await fetchBotFrameworkAttachment({
|
|
1200
|
+
...request,
|
|
1201
|
+
url: viewUrl
|
|
1202
|
+
}, "attachmentView");
|
|
1203
|
+
if (!response) return;
|
|
1204
|
+
let saved;
|
|
1205
|
+
try {
|
|
1206
|
+
saved = await require_runtime.getMSTeamsRuntime().channel.media.saveResponseMedia(response, {
|
|
1207
|
+
sourceUrl: viewUrl,
|
|
1208
|
+
filePathHint: fileNameHint,
|
|
1209
|
+
maxBytes,
|
|
1210
|
+
fallbackContentType: contentTypeHint,
|
|
1211
|
+
subdir: "inbound"
|
|
1548
1212
|
});
|
|
1549
1213
|
} catch (err) {
|
|
1550
|
-
|
|
1214
|
+
request.logger?.warn?.("msteams botFramework attachmentView save failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1215
|
+
return;
|
|
1216
|
+
} finally {
|
|
1217
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1551
1218
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1219
|
+
if (!saved) return;
|
|
1220
|
+
return {
|
|
1221
|
+
path: saved.path,
|
|
1222
|
+
contentType: saved.contentType,
|
|
1223
|
+
kind: require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
1224
|
+
contentType: saved.contentType,
|
|
1225
|
+
fileName: fileNameHint
|
|
1226
|
+
})
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Download media for every attachment referenced by a Bot Framework personal
|
|
1231
|
+
* chat activity. Returns all successfully fetched media along with diagnostics
|
|
1232
|
+
* compatible with `downloadMSTeamsGraphMedia`'s result shape so callers can
|
|
1233
|
+
* reuse the existing logging path.
|
|
1234
|
+
*/
|
|
1235
|
+
async function downloadMSTeamsBotFrameworkAttachments(params) {
|
|
1236
|
+
const unique = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeUniqueTrimmedStringList)(params.attachmentIds);
|
|
1237
|
+
if (unique.length === 0 || !params.serviceUrl || !params.tokenProvider) return {
|
|
1238
|
+
media: [],
|
|
1239
|
+
attachmentCount: unique.length
|
|
1240
|
+
};
|
|
1241
|
+
const media = [];
|
|
1242
|
+
for (const attachmentId of unique) try {
|
|
1243
|
+
const item = await downloadMSTeamsBotFrameworkAttachment({
|
|
1244
|
+
...params,
|
|
1245
|
+
attachmentId
|
|
1246
|
+
});
|
|
1247
|
+
if (item) media.push({
|
|
1248
|
+
...item,
|
|
1249
|
+
sourceId: attachmentId
|
|
1250
|
+
});
|
|
1251
|
+
else media.push({
|
|
1252
|
+
kind: "document",
|
|
1253
|
+
sourceId: attachmentId
|
|
1254
|
+
});
|
|
1255
|
+
} catch (err) {
|
|
1256
|
+
media.push({
|
|
1257
|
+
kind: "document",
|
|
1258
|
+
sourceId: attachmentId
|
|
1259
|
+
});
|
|
1260
|
+
params.logger?.warn?.("msteams botFramework attachment download failed", {
|
|
1261
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
1262
|
+
attachmentId
|
|
1263
|
+
});
|
|
1565
1264
|
}
|
|
1566
1265
|
return {
|
|
1567
|
-
|
|
1568
|
-
|
|
1266
|
+
media,
|
|
1267
|
+
attachmentCount: unique.length
|
|
1569
1268
|
};
|
|
1570
1269
|
}
|
|
1571
1270
|
//#endregion
|
|
1572
|
-
//#region extensions/msteams/src/
|
|
1573
|
-
|
|
1574
|
-
const
|
|
1575
|
-
const
|
|
1576
|
-
|
|
1577
|
-
const utf8 = new TextEncoder();
|
|
1578
|
-
function flattenInformativeStatus(text) {
|
|
1579
|
-
const joined = text.split("\n").map((line) => line.trim().replace(/^[•-]\s+/u, "")).filter(Boolean).join(" · ");
|
|
1580
|
-
if (joined.length <= INFORMATIVE_MAX_CHARS && utf8.encode(joined).length <= INFORMATIVE_MAX_BYTES) return joined;
|
|
1581
|
-
const graphemes = Array.from(segmenter.segment(joined), (s) => s.segment);
|
|
1582
|
-
let chars = 1;
|
|
1583
|
-
let bytes = utf8.encode(ELLIPSIS).length;
|
|
1584
|
-
let start = graphemes.length;
|
|
1585
|
-
while (start > 0) {
|
|
1586
|
-
const next = graphemes[start - 1];
|
|
1587
|
-
const nextBytes = utf8.encode(next).length;
|
|
1588
|
-
if (chars + next.length > INFORMATIVE_MAX_CHARS || bytes + nextBytes > INFORMATIVE_MAX_BYTES) break;
|
|
1589
|
-
chars += next.length;
|
|
1590
|
-
bytes += nextBytes;
|
|
1591
|
-
start -= 1;
|
|
1271
|
+
//#region extensions/msteams/src/attachments/html.ts
|
|
1272
|
+
function collectHtmlAttachmentIds(html, ids) {
|
|
1273
|
+
for (const value of require_resolve_allowlist.htmlTagAttributeValues(html, "attachment", "id")) {
|
|
1274
|
+
const id = value.trim();
|
|
1275
|
+
if (id) ids.add(id);
|
|
1592
1276
|
}
|
|
1593
|
-
return ELLIPSIS + graphemes.slice(start).join("");
|
|
1594
1277
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1278
|
+
/**
|
|
1279
|
+
* Extract every `<attachment id="...">` reference from the HTML attachments in
|
|
1280
|
+
* the inbound activity. Returns the complete (non-sliced) list; callers that
|
|
1281
|
+
* need a capped diagnostic summary can truncate after calling this helper.
|
|
1282
|
+
*/
|
|
1283
|
+
function extractMSTeamsHtmlAttachmentIds(attachments) {
|
|
1284
|
+
const list = attachments ?? [];
|
|
1285
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1286
|
+
for (const att of list) {
|
|
1287
|
+
const html = require_resolve_allowlist.extractHtmlFromAttachment(att);
|
|
1288
|
+
if (!html) continue;
|
|
1289
|
+
collectHtmlAttachmentIds(html, ids);
|
|
1290
|
+
}
|
|
1291
|
+
return Array.from(ids);
|
|
1599
1292
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
let
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
let queuedFinalActivity;
|
|
1620
|
-
const streamEvents = stream?.events;
|
|
1621
|
-
let streamChunkSubscription;
|
|
1622
|
-
if (typeof streamEvents?.on === "function" && typeof streamEvents.off === "function") streamChunkSubscription = streamEvents.on("chunk", (activity) => {
|
|
1623
|
-
const replacementAcknowledgementPending = replacementTextAwaitingAcknowledgement !== void 0;
|
|
1624
|
-
const replacementAcknowledgement = typeof activity.text === "string" && replacementAcknowledgementPending && activity.text === replacementTextAwaitingAcknowledgement?.text;
|
|
1625
|
-
if (activity.type !== "typing" || activity.channelData?.streamType !== "streaming" || !activity.id || !activity.text || acknowledgedStreamId !== void 0 && activity.id !== acknowledgedStreamId || (replacementAcknowledgementPending ? !replacementAcknowledgement : !activity.text.startsWith(acknowledgedText)) || !replacementAcknowledgement && !emittedText.startsWith(activity.text)) return;
|
|
1626
|
-
acknowledgedStreamId = activity.id;
|
|
1627
|
-
acknowledgedText = activity.text;
|
|
1628
|
-
acknowledgedLogicalText = replacementAcknowledgement ? replacementTextAwaitingAcknowledgement.logicalText : activity.text;
|
|
1629
|
-
if (replacementAcknowledgement) replacementTextAwaitingAcknowledgement = void 0;
|
|
1630
|
-
});
|
|
1631
|
-
const wasCanceled = () => canceledLocally || Boolean(stream?.canceled);
|
|
1632
|
-
const releaseStreamChunkSubscription = () => {
|
|
1633
|
-
if (streamChunkSubscription === void 0) return;
|
|
1634
|
-
streamEvents?.off(streamChunkSubscription);
|
|
1635
|
-
streamChunkSubscription = void 0;
|
|
1636
|
-
};
|
|
1637
|
-
const acknowledgedNativeDelivery = () => {
|
|
1638
|
-
if (!acknowledgedStreamId || !acknowledgedText) return { visibleReplySent: false };
|
|
1639
|
-
return {
|
|
1640
|
-
visibleReplySent: true,
|
|
1641
|
-
content: acknowledgedText,
|
|
1642
|
-
messageId: acknowledgedStreamId
|
|
1643
|
-
};
|
|
1644
|
-
};
|
|
1645
|
-
const fallbackPayloadAfterAcknowledgedText = (payload) => {
|
|
1646
|
-
if (!acknowledgedLogicalText || typeof payload.text !== "string" || !payload.text.startsWith(acknowledgedLogicalText)) return payload;
|
|
1647
|
-
const remainingText = payload.text.slice(acknowledgedLogicalText.length);
|
|
1648
|
-
const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
|
|
1649
|
-
if (!remainingText && !hasMedia) return;
|
|
1650
|
-
return {
|
|
1651
|
-
...payload,
|
|
1652
|
-
text: remainingText || void 0
|
|
1653
|
-
};
|
|
1654
|
-
};
|
|
1655
|
-
const fallbackPayloadForSuppressedFinal = (payload) => {
|
|
1656
|
-
return Boolean(payload.mediaUrl || payload.mediaUrls?.length) ? {
|
|
1657
|
-
...payload,
|
|
1658
|
-
mediaUrl: void 0,
|
|
1659
|
-
mediaUrls: void 0
|
|
1660
|
-
} : payload;
|
|
1661
|
-
};
|
|
1662
|
-
const finalStreamActivity = (text) => ({
|
|
1663
|
-
...require_conversation_store_state.buildMSTeamsMessageActivity(text === void 0 ? void 0 : require_conversation_store_state.formatMSTeamsMarkdown(text, params.tableMode ?? "code")),
|
|
1664
|
-
channelData: params.feedbackLoopEnabled ? { feedbackLoopEnabled: true } : {}
|
|
1665
|
-
});
|
|
1666
|
-
const queueFinalReplacement = (activeStream, text) => {
|
|
1667
|
-
const activity = finalStreamActivity(text);
|
|
1668
|
-
replacementTextAwaitingAcknowledgement = {
|
|
1669
|
-
text: activity.text,
|
|
1670
|
-
logicalText: text
|
|
1671
|
-
};
|
|
1672
|
-
activeStream.emit(activity);
|
|
1673
|
-
queuedFinalActivity = activity;
|
|
1674
|
-
emittedText = text;
|
|
1675
|
-
};
|
|
1676
|
-
const deferredReplacementLogicalContent = () => {
|
|
1677
|
-
return deferredReplacementEntries.map((entry) => entry.payload.text).filter((text) => Boolean(text)).join("\n") || void 0;
|
|
1678
|
-
};
|
|
1679
|
-
const deferredReplacementPayloads = (replacementFallback) => deferredReplacementEntries.flatMap((entry) => {
|
|
1680
|
-
if (entry.kind === "payload") return [entry.payload];
|
|
1681
|
-
const hasMedia = Boolean(entry.payload.mediaUrl || entry.payload.mediaUrls?.length);
|
|
1682
|
-
const text = replacementFallback?.text;
|
|
1683
|
-
if (!text && !hasMedia) return [];
|
|
1684
|
-
return [{
|
|
1685
|
-
...entry.payload,
|
|
1686
|
-
text: text || void 0
|
|
1687
|
-
}];
|
|
1688
|
-
});
|
|
1689
|
-
const finalizeWithoutReceipt = (logicalContent, canceled = false) => {
|
|
1690
|
-
const fallback = pendingFinalPayload && !canceled ? fallbackPayloadAfterAcknowledgedText(pendingFinalPayload) : void 0;
|
|
1691
|
-
const postNativePayloads = replacementSettlementPending && !canceled ? deferredReplacementPayloads(fallback) : [];
|
|
1692
|
-
return {
|
|
1693
|
-
...acknowledgedNativeDelivery(),
|
|
1694
|
-
...!canceled && logicalContent ? { logicalContent } : {},
|
|
1695
|
-
...!replacementSettlementPending && fallback ? { fallbackPayload: fallback } : {},
|
|
1696
|
-
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
1697
|
-
};
|
|
1698
|
-
};
|
|
1699
|
-
const progressDraft = (0, openclaw_plugin_sdk_channel_outbound.createChannelProgressDraftCompositor)({
|
|
1700
|
-
preparedItems: true,
|
|
1701
|
-
formatPlainText: (text) => text,
|
|
1702
|
-
entry: params.msteamsConfig,
|
|
1703
|
-
mode: streamMode,
|
|
1704
|
-
active: Boolean(stream) && streamMode === "progress",
|
|
1705
|
-
seed: params.progressSeed ?? "msteams",
|
|
1706
|
-
update: (text) => {
|
|
1707
|
-
if (!stream || wasCanceled() || streamFinalizationPending) return false;
|
|
1708
|
-
try {
|
|
1709
|
-
stream.update(flattenInformativeStatus(text));
|
|
1710
|
-
return true;
|
|
1711
|
-
} catch (err) {
|
|
1712
|
-
if (isStreamCancelledError(err)) canceledLocally = true;
|
|
1713
|
-
else params.log?.debug?.(`stream informative update failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
|
|
1714
|
-
return false;
|
|
1293
|
+
function summarizeMSTeamsHtmlAttachments(attachments) {
|
|
1294
|
+
const list = attachments ?? [];
|
|
1295
|
+
let htmlAttachments = 0;
|
|
1296
|
+
let imgTags = 0;
|
|
1297
|
+
let dataImages = 0;
|
|
1298
|
+
let cidImages = 0;
|
|
1299
|
+
const srcHosts = /* @__PURE__ */ new Set();
|
|
1300
|
+
const attachmentIds = /* @__PURE__ */ new Set();
|
|
1301
|
+
for (const att of list) {
|
|
1302
|
+
const html = require_resolve_allowlist.extractHtmlFromAttachment(att);
|
|
1303
|
+
if (!html) continue;
|
|
1304
|
+
htmlAttachments += 1;
|
|
1305
|
+
for (const value of require_resolve_allowlist.htmlTagAttributeValues(html, "img", "src")) {
|
|
1306
|
+
imgTags += 1;
|
|
1307
|
+
const src = value.trim();
|
|
1308
|
+
if (src) {
|
|
1309
|
+
if (src.startsWith("data:")) dataImages += 1;
|
|
1310
|
+
else if (src.startsWith("cid:")) cidImages += 1;
|
|
1311
|
+
else srcHosts.add(require_resolve_allowlist.safeHostForUrl(src));
|
|
1715
1312
|
}
|
|
1716
1313
|
}
|
|
1717
|
-
|
|
1314
|
+
collectHtmlAttachmentIds(html, attachmentIds);
|
|
1315
|
+
}
|
|
1316
|
+
if (htmlAttachments === 0) return;
|
|
1718
1317
|
return {
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
if (streamFinalizationPending || nativeDeliveryClaimed) return;
|
|
1726
|
-
const fullText = payload.text;
|
|
1727
|
-
let prefixLength = 0;
|
|
1728
|
-
while (prefixLength < emittedText.length && prefixLength < fullText.length && emittedText[prefixLength] === fullText[prefixLength]) prefixLength += 1;
|
|
1729
|
-
const previousRemainder = emittedText.slice(prefixLength);
|
|
1730
|
-
const delta = fullText.slice(prefixLength);
|
|
1731
|
-
if (!delta) return;
|
|
1732
|
-
if (previousRemainder.trim()) {
|
|
1733
|
-
stream.clearText();
|
|
1734
|
-
replacementFinalPending = true;
|
|
1735
|
-
replacementSettlementPending = true;
|
|
1736
|
-
pendingFinalPayload = { text: fullText };
|
|
1737
|
-
streamFinalizationPending = true;
|
|
1738
|
-
return;
|
|
1739
|
-
}
|
|
1740
|
-
try {
|
|
1741
|
-
stream.emit(delta);
|
|
1742
|
-
emittedText = fullText;
|
|
1743
|
-
tokensEmitted = true;
|
|
1744
|
-
nativeDispatchStarted = true;
|
|
1745
|
-
} catch (err) {
|
|
1746
|
-
if (isStreamCancelledError(err)) {
|
|
1747
|
-
canceledLocally = true;
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
throw err;
|
|
1751
|
-
}
|
|
1752
|
-
},
|
|
1753
|
-
pushItemEvent: progressDraft.pushItemEvent.bind(progressDraft),
|
|
1754
|
-
pushToolEvent: progressDraft.pushToolEvent,
|
|
1755
|
-
pushReasoningProgress: progressDraft.pushReasoningProgress.bind(progressDraft),
|
|
1756
|
-
resetReasoningProgress: progressDraft.resetReasoningProgress,
|
|
1757
|
-
pushApprovalEvent: progressDraft.pushApprovalEvent.bind(progressDraft),
|
|
1758
|
-
pushPlanProgress: progressDraft.pushPlanProgress.bind(progressDraft),
|
|
1759
|
-
preparePayload(payload) {
|
|
1760
|
-
if (!stream) return payload;
|
|
1761
|
-
if (wasCanceled()) return;
|
|
1762
|
-
if (payload.text) progressDraft.markFinalReplyStarted();
|
|
1763
|
-
if (replacementSettlementPending) {
|
|
1764
|
-
if (!replacementFinalPending || !payload.text) {
|
|
1765
|
-
deferredReplacementEntries.push({
|
|
1766
|
-
kind: "payload",
|
|
1767
|
-
payload
|
|
1768
|
-
});
|
|
1769
|
-
return;
|
|
1770
|
-
}
|
|
1771
|
-
replacementFinalPending = false;
|
|
1772
|
-
deferredReplacementEntries.push({
|
|
1773
|
-
kind: "replacement",
|
|
1774
|
-
payload
|
|
1775
|
-
});
|
|
1776
|
-
pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
|
|
1777
|
-
try {
|
|
1778
|
-
queueFinalReplacement(stream, payload.text);
|
|
1779
|
-
tokensEmitted = false;
|
|
1780
|
-
return;
|
|
1781
|
-
} catch (err) {
|
|
1782
|
-
replacementTextAwaitingAcknowledgement = void 0;
|
|
1783
|
-
tokensEmitted = false;
|
|
1784
|
-
if (isStreamCancelledError(err)) {
|
|
1785
|
-
canceledLocally = true;
|
|
1786
|
-
pendingFinalPayload = void 0;
|
|
1787
|
-
deferredReplacementEntries = [];
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
|
-
throw err;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
if (streamMode === "progress" && payload.text && !nativeDispatchStarted) try {
|
|
1794
|
-
stream.emit(payload.text);
|
|
1795
|
-
emittedText = payload.text;
|
|
1796
|
-
nativeDispatchStarted = true;
|
|
1797
|
-
tokensEmitted = true;
|
|
1798
|
-
} catch (err) {
|
|
1799
|
-
if (isStreamCancelledError(err)) {
|
|
1800
|
-
canceledLocally = true;
|
|
1801
|
-
return;
|
|
1802
|
-
}
|
|
1803
|
-
throw err;
|
|
1804
|
-
}
|
|
1805
|
-
if (tokensEmitted) {
|
|
1806
|
-
const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
|
|
1807
|
-
pendingFinalPayload = fallbackPayloadForSuppressedFinal(payload);
|
|
1808
|
-
streamFinalizationPending = true;
|
|
1809
|
-
tokensEmitted = false;
|
|
1810
|
-
return hasMedia ? {
|
|
1811
|
-
...payload,
|
|
1812
|
-
text: void 0
|
|
1813
|
-
} : void 0;
|
|
1814
|
-
}
|
|
1815
|
-
return payload;
|
|
1816
|
-
},
|
|
1817
|
-
claimNativeDelivery() {
|
|
1818
|
-
if (!nativeDispatchStarted || nativeDeliveryClaimed) return false;
|
|
1819
|
-
nativeDeliveryClaimed = true;
|
|
1820
|
-
return true;
|
|
1821
|
-
},
|
|
1822
|
-
async finalize() {
|
|
1823
|
-
progressDraft.markFinalReplyStarted();
|
|
1824
|
-
if (!stream || !nativeDispatchStarted) {
|
|
1825
|
-
releaseStreamChunkSubscription();
|
|
1826
|
-
return { visibleReplySent: false };
|
|
1827
|
-
}
|
|
1828
|
-
let logicalContent;
|
|
1829
|
-
try {
|
|
1830
|
-
if (wasCanceled() || !streamFinalizationPending) return acknowledgedNativeDelivery();
|
|
1831
|
-
if (replacementSettlementPending && replacementFinalPending && pendingFinalPayload?.text) {
|
|
1832
|
-
replacementFinalPending = false;
|
|
1833
|
-
deferredReplacementEntries.push({
|
|
1834
|
-
kind: "replacement",
|
|
1835
|
-
payload: pendingFinalPayload
|
|
1836
|
-
});
|
|
1837
|
-
logicalContent = deferredReplacementLogicalContent();
|
|
1838
|
-
queueFinalReplacement(stream, pendingFinalPayload.text);
|
|
1839
|
-
}
|
|
1840
|
-
logicalContent ??= replacementSettlementPending ? deferredReplacementLogicalContent() : void 0;
|
|
1841
|
-
const logicalText = pendingFinalPayload?.text ?? (emittedText || void 0);
|
|
1842
|
-
const finalActivity = queuedFinalActivity ?? finalStreamActivity(logicalText);
|
|
1843
|
-
const content = finalActivity.text;
|
|
1844
|
-
logicalContent ??= content !== logicalText ? logicalText : void 0;
|
|
1845
|
-
if (!queuedFinalActivity) {
|
|
1846
|
-
if (content !== void 0 && content !== emittedText) {
|
|
1847
|
-
stream.clearText();
|
|
1848
|
-
replacementTextAwaitingAcknowledgement = {
|
|
1849
|
-
text: content,
|
|
1850
|
-
logicalText
|
|
1851
|
-
};
|
|
1852
|
-
stream.emit(finalActivity);
|
|
1853
|
-
} else stream.emit({
|
|
1854
|
-
...finalActivity,
|
|
1855
|
-
text: void 0
|
|
1856
|
-
});
|
|
1857
|
-
}
|
|
1858
|
-
const result = await stream.close();
|
|
1859
|
-
if (!result) return finalizeWithoutReceipt(logicalContent, wasCanceled());
|
|
1860
|
-
const messageId = require_conversation_store_state.extractMessageId(result) ?? acknowledgedStreamId;
|
|
1861
|
-
const postNativePayloads = replacementSettlementPending ? deferredReplacementPayloads(void 0) : [];
|
|
1862
|
-
return {
|
|
1863
|
-
visibleReplySent: true,
|
|
1864
|
-
...content === void 0 ? {} : { content },
|
|
1865
|
-
...logicalContent ? { logicalContent } : {},
|
|
1866
|
-
...messageId ? { messageId } : {},
|
|
1867
|
-
...postNativePayloads.length > 0 ? { postNativePayloads } : {}
|
|
1868
|
-
};
|
|
1869
|
-
} catch (err) {
|
|
1870
|
-
if (isStreamCancelledError(err)) {
|
|
1871
|
-
canceledLocally = true;
|
|
1872
|
-
return acknowledgedNativeDelivery();
|
|
1873
|
-
}
|
|
1874
|
-
params.log?.warn?.(`msteams stream finalize failed: ${(0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)}`);
|
|
1875
|
-
return finalizeWithoutReceipt(logicalContent);
|
|
1876
|
-
} finally {
|
|
1877
|
-
pendingFinalPayload = void 0;
|
|
1878
|
-
streamFinalizationPending = false;
|
|
1879
|
-
queuedFinalActivity = void 0;
|
|
1880
|
-
replacementFinalPending = false;
|
|
1881
|
-
replacementSettlementPending = false;
|
|
1882
|
-
replacementTextAwaitingAcknowledgement = void 0;
|
|
1883
|
-
deferredReplacementEntries = [];
|
|
1884
|
-
releaseStreamChunkSubscription();
|
|
1885
|
-
}
|
|
1886
|
-
},
|
|
1887
|
-
hasStream() {
|
|
1888
|
-
return Boolean(stream);
|
|
1889
|
-
},
|
|
1890
|
-
isStreamActive() {
|
|
1891
|
-
return Boolean(stream) && tokensEmitted && !wasCanceled();
|
|
1892
|
-
},
|
|
1893
|
-
wasCanceled
|
|
1318
|
+
htmlAttachments,
|
|
1319
|
+
imgTags,
|
|
1320
|
+
dataImages,
|
|
1321
|
+
cidImages,
|
|
1322
|
+
srcHosts: Array.from(srcHosts).slice(0, 5),
|
|
1323
|
+
attachmentIds: Array.from(attachmentIds).slice(0, 5)
|
|
1894
1324
|
};
|
|
1895
1325
|
}
|
|
1326
|
+
function resolveUnrepresentedHtmlAttachmentIds(attachments) {
|
|
1327
|
+
const representedIds = /* @__PURE__ */ new Set();
|
|
1328
|
+
for (const attachment of attachments) {
|
|
1329
|
+
if ((require_resolve_allowlist.normalizeContentType(attachment.contentType) ?? "").startsWith("text/html")) continue;
|
|
1330
|
+
const id = attachment.id?.trim();
|
|
1331
|
+
if (id) representedIds.add(id);
|
|
1332
|
+
}
|
|
1333
|
+
return extractMSTeamsHtmlAttachmentIds(attachments).filter((id) => !representedIds.has(id));
|
|
1334
|
+
}
|
|
1335
|
+
function createAdvertisedMediaFact(kind, sourceId) {
|
|
1336
|
+
return {
|
|
1337
|
+
kind,
|
|
1338
|
+
...sourceId ? { sourceId } : {}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
function resolveMSTeamsAdvertisedMedia(attachments) {
|
|
1342
|
+
const list = attachments ?? [];
|
|
1343
|
+
const fileAttachments = list.filter(require_resolve_allowlist.isAdvertisedFileAttachment);
|
|
1344
|
+
const inlineMedia = require_resolve_allowlist.extractInlineImageReferences(list).map((candidate) => createAdvertisedMediaFact("image", candidate.sourceId));
|
|
1345
|
+
const htmlAttachmentIds = resolveUnrepresentedHtmlAttachmentIds(list);
|
|
1346
|
+
return [
|
|
1347
|
+
...fileAttachments.map((attachment) => createAdvertisedMediaFact(require_resolve_allowlist.isLikelyImageAttachment(attachment) ? "image" : require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
1348
|
+
contentType: require_resolve_allowlist.normalizeContentType(attachment.contentType),
|
|
1349
|
+
fileName: attachment.name ?? void 0
|
|
1350
|
+
}), attachment.id?.trim() || void 0)),
|
|
1351
|
+
...inlineMedia,
|
|
1352
|
+
...htmlAttachmentIds.map((sourceId) => createAdvertisedMediaFact("document", sourceId))
|
|
1353
|
+
];
|
|
1354
|
+
}
|
|
1355
|
+
//#endregion
|
|
1356
|
+
//#region extensions/msteams/src/attachments/remote-media.ts
|
|
1357
|
+
/**
|
|
1358
|
+
* Direct save path used when the caller supplies the already-guarded fetch
|
|
1359
|
+
* implementation. This lets Teams-specific auth fallback own the request
|
|
1360
|
+
* sequence while keeping redirect and DNS pinning inside `safeFetchWithPolicy`.
|
|
1361
|
+
*/
|
|
1362
|
+
async function downloadAndStoreMSTeamsRemoteMedia(params) {
|
|
1363
|
+
const response = await params.fetchImpl(params.url, { redirect: "follow" });
|
|
1364
|
+
try {
|
|
1365
|
+
const saved = await (0, openclaw_plugin_sdk_media_runtime.saveResponseMedia)(response, {
|
|
1366
|
+
sourceUrl: params.url,
|
|
1367
|
+
filePathHint: params.filePathHint,
|
|
1368
|
+
maxBytes: params.maxBytes,
|
|
1369
|
+
fallbackContentType: params.contentTypeHint
|
|
1370
|
+
});
|
|
1371
|
+
return {
|
|
1372
|
+
path: saved.path,
|
|
1373
|
+
contentType: saved.contentType,
|
|
1374
|
+
kind: params.kind ?? require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
1375
|
+
contentType: saved.contentType,
|
|
1376
|
+
fileName: params.filePathHint
|
|
1377
|
+
})
|
|
1378
|
+
};
|
|
1379
|
+
} finally {
|
|
1380
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1896
1383
|
//#endregion
|
|
1897
|
-
//#region extensions/msteams/src/
|
|
1898
|
-
function
|
|
1899
|
-
const
|
|
1900
|
-
const
|
|
1901
|
-
const
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1384
|
+
//#region extensions/msteams/src/attachments/download.ts
|
|
1385
|
+
function resolveDownloadCandidate(att, graphReferenceToken) {
|
|
1386
|
+
const contentType = require_resolve_allowlist.normalizeContentType(att.contentType);
|
|
1387
|
+
const graphReference = graphReferenceToken !== void 0 && contentType === "reference";
|
|
1388
|
+
const name = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.name) ?? "";
|
|
1389
|
+
if (contentType === "application/vnd.microsoft.teams.file.download.info") {
|
|
1390
|
+
if (!(0, openclaw_plugin_sdk_string_coerce_runtime.isRecord)(att.content)) return null;
|
|
1391
|
+
const downloadUrl = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.content.downloadUrl) ?? "";
|
|
1392
|
+
if (!downloadUrl) return null;
|
|
1393
|
+
const fileType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.content.fileType) ?? "";
|
|
1394
|
+
const uniqueId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.content.uniqueId) ?? "";
|
|
1395
|
+
const fileName = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.content.fileName) ?? "";
|
|
1396
|
+
const fileHint = name || fileName || (uniqueId && fileType ? `${uniqueId}.${fileType}` : "");
|
|
1397
|
+
return {
|
|
1398
|
+
kind: "remote",
|
|
1399
|
+
mediaKind: require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
1400
|
+
contentType,
|
|
1401
|
+
fileName: fileHint,
|
|
1402
|
+
fileType
|
|
1403
|
+
}),
|
|
1404
|
+
url: downloadUrl,
|
|
1405
|
+
fileHint: fileHint || void 0,
|
|
1406
|
+
contentTypeHint: void 0,
|
|
1407
|
+
sourceId: att.id?.trim() || void 0
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
const contentUrl = graphReference ? att.contentUrl ?? "" : (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(att.contentUrl) ?? "";
|
|
1411
|
+
if (!contentUrl) return null;
|
|
1412
|
+
const sharesUrl = graphReference ? `${require_resolve_allowlist.GRAPH_ROOT}/shares/${require_resolve_allowlist.encodeGraphShareId(contentUrl)}/driveItem/content` : require_resolve_allowlist.tryBuildGraphSharesUrlForSharedLink(contentUrl);
|
|
1413
|
+
const resolvedUrl = sharesUrl ?? contentUrl;
|
|
1414
|
+
const resolvedContentTypeHint = graphReference ? "application/octet-stream" : sharesUrl ? void 0 : contentType;
|
|
1415
|
+
return {
|
|
1416
|
+
kind: "remote",
|
|
1417
|
+
mediaKind: require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
1418
|
+
contentType,
|
|
1419
|
+
fileName: name
|
|
1420
|
+
}),
|
|
1421
|
+
url: resolvedUrl,
|
|
1422
|
+
fileHint: graphReference ? att.name ?? void 0 : name || void 0,
|
|
1423
|
+
contentTypeHint: resolvedContentTypeHint,
|
|
1424
|
+
sourceId: att.id?.trim() || void 0,
|
|
1425
|
+
graphReference
|
|
1926
1426
|
};
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
error: err
|
|
1942
|
-
});
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1427
|
+
}
|
|
1428
|
+
function scopeCandidatesForUrl(url) {
|
|
1429
|
+
const host = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(URL.parse(url)?.hostname);
|
|
1430
|
+
return host.endsWith("graph.microsoft.com") || host.endsWith("sharepoint.com") || host.endsWith("1drv.ms") || host.includes("sharepoint") ? ["https://graph.microsoft.com", "https://api.botframework.com"] : ["https://api.botframework.com", "https://graph.microsoft.com"];
|
|
1431
|
+
}
|
|
1432
|
+
function canonicalizeInlineBase64Payload(value) {
|
|
1433
|
+
let cleaned = "";
|
|
1434
|
+
for (const char of value) if (char.charCodeAt(0) > 32) cleaned += char;
|
|
1435
|
+
return cleaned.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(cleaned) ? cleaned : void 0;
|
|
1436
|
+
}
|
|
1437
|
+
async function resolveInlineDataImageMime(inline) {
|
|
1438
|
+
const detectedMime = await require_runtime.getMSTeamsRuntime().media.detectMime({
|
|
1439
|
+
buffer: inline.data,
|
|
1440
|
+
headerMime: inline.contentType
|
|
1945
1441
|
});
|
|
1946
|
-
const
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1442
|
+
const mime = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(detectedMime ?? inline.contentType);
|
|
1443
|
+
return mime?.startsWith("image/") ? mime : void 0;
|
|
1444
|
+
}
|
|
1445
|
+
async function downloadMSTeamsAttachments(params) {
|
|
1446
|
+
const list = params.attachments ?? [];
|
|
1447
|
+
if (list.length === 0) return [];
|
|
1448
|
+
const policy = require_resolve_allowlist.resolveAttachmentFetchPolicy({
|
|
1449
|
+
allowHosts: params.allowHosts,
|
|
1450
|
+
authAllowHosts: params.authAllowHosts
|
|
1951
1451
|
});
|
|
1952
|
-
const
|
|
1953
|
-
const
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1452
|
+
const allowHosts = policy.allowHosts;
|
|
1453
|
+
const candidates = list.filter(require_resolve_allowlist.isAdvertisedFileAttachment).map((attachment) => {
|
|
1454
|
+
return (require_resolve_allowlist.isDownloadableAttachment(attachment) ? resolveDownloadCandidate(attachment, params.graphReferenceToken) : null) ?? {
|
|
1455
|
+
kind: "unavailable",
|
|
1456
|
+
mediaKind: require_resolve_allowlist.resolveMSTeamsMediaKind({
|
|
1457
|
+
contentType: require_resolve_allowlist.normalizeContentType(attachment.contentType),
|
|
1458
|
+
fileName: attachment.name ?? void 0
|
|
1459
|
+
}),
|
|
1460
|
+
sourceId: attachment.id?.trim() || void 0
|
|
1461
|
+
};
|
|
1962
1462
|
});
|
|
1963
|
-
const
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
const errorText = require_channel_setup.formatUnknownError(failure.error);
|
|
1973
|
-
const failedAll = failure.failed >= failure.total;
|
|
1974
|
-
const ambiguous = classification.kind === "ambiguous";
|
|
1975
|
-
const sentences = [
|
|
1976
|
-
`Microsoft Teams delivery failed: ${ambiguous ? failedAll ? "the delivery outcome is unknown for the previous reply" : `the delivery outcome is unknown for ${failure.failed} of ${failure.total} message blocks` : failedAll ? "the previous reply was not delivered" : `${failure.failed} of ${failure.total} message blocks were not delivered`}.`,
|
|
1977
|
-
ambiguous ? void 0 : `The user may not have received ${failedAll ? "that reply" : "the full reply"}.`,
|
|
1978
|
-
`Error: ${errorText}.`,
|
|
1979
|
-
classification.statusCode != null ? `Status: ${classification.statusCode}.` : void 0,
|
|
1980
|
-
require_channel_setup.formatMSTeamsDeliveryFailureGuidance(classification)
|
|
1981
|
-
].filter(Boolean);
|
|
1982
|
-
core.system.enqueueSystemEvent(sentences.join(" "), {
|
|
1983
|
-
sessionKey: params.sessionKey,
|
|
1984
|
-
contextKey: `msteams:delivery-failure:${params.conversationRef.conversation?.id ?? "unknown"}`
|
|
1985
|
-
});
|
|
1986
|
-
};
|
|
1987
|
-
const renderReplyPayload = (payload) => {
|
|
1988
|
-
return require_conversation_store_state.renderReplyPayloadsToMessages([payload], {
|
|
1989
|
-
textChunkLimit: params.textLimit,
|
|
1990
|
-
tableMode,
|
|
1991
|
-
chunkMode
|
|
1992
|
-
});
|
|
1993
|
-
};
|
|
1994
|
-
const settlePendingDelivery = (delivery) => {
|
|
1995
|
-
if (delivery.settled || !delivery.blockSettled || !delivery.nativeSettled) return;
|
|
1996
|
-
delivery.settled = true;
|
|
1997
|
-
const acceptedParts = [...delivery.nativeResult ? [delivery.nativeResult] : [], ...delivery.blockResults];
|
|
1998
|
-
const messageIds = acceptedParts.flatMap((part) => part.messageIds);
|
|
1999
|
-
const content = delivery.errors.length > 0 ? acceptedParts.map((part) => part.content).filter(Boolean).join("\n") : delivery.content;
|
|
2000
|
-
const outcome = {
|
|
2001
|
-
visibleReplySent: acceptedParts.length > 0,
|
|
2002
|
-
...messageIds.length > 0 ? { messageIds } : {},
|
|
2003
|
-
...acceptedParts.length > 0 && content !== void 0 ? { content } : {}
|
|
1463
|
+
const maxInlineBytes = params.maxBytes;
|
|
1464
|
+
candidates.push(...require_resolve_allowlist.extractInlineImageReferences(list).map((candidate) => {
|
|
1465
|
+
if (candidate.kind === "url") return {
|
|
1466
|
+
kind: "remote",
|
|
1467
|
+
mediaKind: "image",
|
|
1468
|
+
url: candidate.url,
|
|
1469
|
+
fileHint: candidate.fileHint,
|
|
1470
|
+
contentTypeHint: candidate.contentType,
|
|
1471
|
+
sourceId: candidate.sourceId
|
|
2004
1472
|
};
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
});
|
|
2043
|
-
const validIds = msgIds.filter((id) => id.trim() && id !== "unknown");
|
|
2044
|
-
if (msgIds.length > 0) delivery.blockResults.push({
|
|
2045
|
-
messageIds: validIds,
|
|
2046
|
-
...msg.text ? { content: msg.text } : {}
|
|
2047
|
-
});
|
|
2048
|
-
sentIds.push(...validIds);
|
|
2049
|
-
} catch (msgError) {
|
|
2050
|
-
failed += 1;
|
|
2051
|
-
lastFailedError = msgError;
|
|
2052
|
-
delivery.errors.push(msgError);
|
|
2053
|
-
params.log.debug?.("individual message send failed, continuing with remaining blocks");
|
|
1473
|
+
return Object.assign(candidate, { mediaKind: "image" });
|
|
1474
|
+
}));
|
|
1475
|
+
for (const sourceId of resolveUnrepresentedHtmlAttachmentIds(list)) candidates.push({
|
|
1476
|
+
kind: "unavailable",
|
|
1477
|
+
mediaKind: "document",
|
|
1478
|
+
sourceId
|
|
1479
|
+
});
|
|
1480
|
+
if (candidates.length === 0) return [];
|
|
1481
|
+
let totalInlineBytes = 0;
|
|
1482
|
+
const downloadCandidate = async (candidate) => {
|
|
1483
|
+
if (candidate.kind === "unavailable") return;
|
|
1484
|
+
if (candidate.kind === "data") {
|
|
1485
|
+
const match = /^data:(image\/[a-z0-9.+-]+)?(;base64)?,(.*)$/i.exec(candidate.src);
|
|
1486
|
+
if (!match) return;
|
|
1487
|
+
const declaredContentType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(match[1] ?? "");
|
|
1488
|
+
if (!Boolean(match[2])) return;
|
|
1489
|
+
const canonicalPayload = canonicalizeInlineBase64Payload(match[3] ?? "");
|
|
1490
|
+
if (!canonicalPayload) return;
|
|
1491
|
+
const estimatedBytes = Buffer.byteLength(canonicalPayload, "base64");
|
|
1492
|
+
if (estimatedBytes <= 0 || estimatedBytes > maxInlineBytes || totalInlineBytes + estimatedBytes > maxInlineBytes) return;
|
|
1493
|
+
const decoded = {
|
|
1494
|
+
data: Buffer.from(canonicalPayload, "base64"),
|
|
1495
|
+
contentType: declaredContentType,
|
|
1496
|
+
estimatedBytes
|
|
1497
|
+
};
|
|
1498
|
+
totalInlineBytes += decoded.estimatedBytes;
|
|
1499
|
+
try {
|
|
1500
|
+
const contentType = await resolveInlineDataImageMime(decoded);
|
|
1501
|
+
if (!contentType) return;
|
|
1502
|
+
const saved = await require_runtime.getMSTeamsRuntime().channel.media.saveMediaBuffer(decoded.data, contentType, "inbound", params.maxBytes);
|
|
1503
|
+
return {
|
|
1504
|
+
path: saved.path,
|
|
1505
|
+
contentType: saved.contentType,
|
|
1506
|
+
kind: "image"
|
|
1507
|
+
};
|
|
1508
|
+
} catch (err) {
|
|
1509
|
+
params.logger?.warn?.("msteams inline attachment decode failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
2054
1510
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
if (!(0, openclaw_plugin_sdk_ssrf_policy.isHttpsUrlAllowedByHostnameSuffixAllowlist)(candidate.url, allowHosts)) return;
|
|
1514
|
+
try {
|
|
1515
|
+
return await downloadAndStoreMSTeamsRemoteMedia({
|
|
1516
|
+
url: candidate.url,
|
|
1517
|
+
filePathHint: candidate.fileHint ?? candidate.url,
|
|
1518
|
+
maxBytes: params.maxBytes,
|
|
1519
|
+
contentTypeHint: candidate.contentTypeHint,
|
|
1520
|
+
kind: candidate.graphReference ? void 0 : candidate.mediaKind,
|
|
1521
|
+
fetchImpl: async (input, init) => {
|
|
1522
|
+
const url = (0, openclaw_plugin_sdk_request_url.resolveRequestUrl)(input);
|
|
1523
|
+
const providedTokenProvider = params.tokenProvider;
|
|
1524
|
+
const deadline = params.deadline;
|
|
1525
|
+
const bearerToken = candidate.graphReference ? params.graphReferenceToken : void 0;
|
|
1526
|
+
const fetchAttempt = (requestInit) => require_resolve_allowlist.safeFetchWithPolicy({
|
|
1527
|
+
url,
|
|
1528
|
+
policy,
|
|
1529
|
+
resolveFn: node_dns_promises.lookup,
|
|
1530
|
+
requestInit,
|
|
1531
|
+
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(deadline)
|
|
1532
|
+
});
|
|
1533
|
+
if (bearerToken !== void 0) {
|
|
1534
|
+
const headers = require_resolve_allowlist.ensureUserAgentHeader(init?.headers);
|
|
1535
|
+
require_resolve_allowlist.applyAuthorizationHeaderForUrl({
|
|
1536
|
+
headers,
|
|
1537
|
+
url,
|
|
1538
|
+
authAllowHosts: policy.authAllowHosts,
|
|
1539
|
+
bearerToken
|
|
1540
|
+
});
|
|
1541
|
+
return fetchAttempt({
|
|
1542
|
+
...init,
|
|
1543
|
+
headers
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
const firstAttempt = await fetchAttempt(init);
|
|
1547
|
+
if (firstAttempt.ok) return firstAttempt;
|
|
1548
|
+
if (!providedTokenProvider) return firstAttempt;
|
|
1549
|
+
const tokenProvider = providedTokenProvider;
|
|
1550
|
+
if (firstAttempt.status !== 401 && firstAttempt.status !== 403) return firstAttempt;
|
|
1551
|
+
if (!(0, openclaw_plugin_sdk_ssrf_policy.isHttpsUrlAllowedByHostnameSuffixAllowlist)(url, policy.authAllowHosts)) return firstAttempt;
|
|
1552
|
+
let fallbackAttempt = firstAttempt;
|
|
1553
|
+
const scopes = scopeCandidatesForUrl(url);
|
|
1554
|
+
for (const scope of scopes) try {
|
|
1555
|
+
const token = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
1556
|
+
deadline,
|
|
1557
|
+
label: "MS Teams attachment token",
|
|
1558
|
+
work: () => tokenProvider.getAccessToken(scope)
|
|
1559
|
+
});
|
|
1560
|
+
const authHeaders = new Headers(init?.headers);
|
|
1561
|
+
authHeaders.set("Authorization", `Bearer ${token}`);
|
|
1562
|
+
const authAttempt = await fetchAttempt({
|
|
1563
|
+
...init,
|
|
1564
|
+
headers: authHeaders
|
|
1565
|
+
});
|
|
1566
|
+
await fallbackAttempt.body?.cancel().catch(() => void 0);
|
|
1567
|
+
if (authAttempt.ok || require_resolve_allowlist.isRedirectStatus(authAttempt.status)) return authAttempt;
|
|
1568
|
+
fallbackAttempt = authAttempt;
|
|
1569
|
+
} catch {}
|
|
1570
|
+
return fallbackAttempt;
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
} catch (err) {
|
|
1574
|
+
if (candidate.graphReference) {
|
|
1575
|
+
params.logger?.warn?.("msteams SharePoint reference download failed", {
|
|
1576
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
1577
|
+
name: candidate.fileHint
|
|
2069
1578
|
});
|
|
1579
|
+
return;
|
|
2070
1580
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
if (sentIds.length > 0) try {
|
|
2074
|
-
params.onSentMessageIds?.(sentIds);
|
|
2075
|
-
} catch (error) {
|
|
2076
|
-
params.log.warn?.("failed to record sent Teams message ids", { error: require_channel_setup.formatUnknownError(error) });
|
|
2077
|
-
}
|
|
1581
|
+
const msg = (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err);
|
|
1582
|
+
params.logger?.warn?.(`msteams attachment download failed host=${safeHostForLog(candidate.url)} error=${msg}`);
|
|
2078
1583
|
}
|
|
2079
1584
|
};
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
1585
|
+
const out = [];
|
|
1586
|
+
for (const candidate of candidates) {
|
|
1587
|
+
const media = await downloadCandidate(candidate) ?? { kind: candidate.mediaKind };
|
|
1588
|
+
out.push(candidate.sourceId ? {
|
|
1589
|
+
...media,
|
|
1590
|
+
sourceId: candidate.sourceId
|
|
1591
|
+
} : media);
|
|
1592
|
+
}
|
|
1593
|
+
return out;
|
|
1594
|
+
}
|
|
1595
|
+
function safeHostForLog(url) {
|
|
1596
|
+
return URL.parse(url)?.host ?? "invalid-url";
|
|
1597
|
+
}
|
|
1598
|
+
//#endregion
|
|
1599
|
+
//#region extensions/msteams/src/attachments/graph.ts
|
|
1600
|
+
function createGraphHostedContentFact(item) {
|
|
1601
|
+
const contentType = require_resolve_allowlist.normalizeContentType(item.contentType);
|
|
1602
|
+
return {
|
|
1603
|
+
kind: require_resolve_allowlist.resolveMSTeamsMediaKind({ contentType }),
|
|
1604
|
+
...contentType ? { contentType } : {},
|
|
1605
|
+
...item.id ? { sourceId: item.id } : {}
|
|
2087
1606
|
};
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
1607
|
+
}
|
|
1608
|
+
function buildMSTeamsGraphMessageUrl(params) {
|
|
1609
|
+
const conversationType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeLowercaseStringOrEmpty)(params.conversationType ?? "");
|
|
1610
|
+
const messageId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.messageId);
|
|
1611
|
+
if (!messageId) return;
|
|
1612
|
+
if (conversationType === "channel") {
|
|
1613
|
+
const teamAadGroupId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.teamAadGroupId);
|
|
1614
|
+
const channelId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.channelId);
|
|
1615
|
+
if (!teamAadGroupId || !channelId) return;
|
|
1616
|
+
const messageRoot = `${require_resolve_allowlist.GRAPH_ROOT}/teams/${encodeURIComponent(teamAadGroupId)}/channels/${encodeURIComponent(channelId)}/messages`;
|
|
1617
|
+
const threadRootMessageId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.threadRootMessageId);
|
|
1618
|
+
return threadRootMessageId && threadRootMessageId !== messageId ? `${messageRoot}/${encodeURIComponent(threadRootMessageId)}/replies/${encodeURIComponent(messageId)}` : `${messageRoot}/${encodeURIComponent(messageId)}`;
|
|
1619
|
+
}
|
|
1620
|
+
const chatId = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalString)(params.conversationId);
|
|
1621
|
+
if (!chatId) return;
|
|
1622
|
+
return `${require_resolve_allowlist.GRAPH_ROOT}/chats/${encodeURIComponent(chatId)}/messages/${encodeURIComponent(messageId)}`;
|
|
1623
|
+
}
|
|
1624
|
+
async function withGraphMediaResponse(params, url, kind, read) {
|
|
1625
|
+
const { response, release } = await (0, openclaw_plugin_sdk_ssrf_runtime.fetchWithSsrFGuard)({
|
|
1626
|
+
url,
|
|
1627
|
+
init: { headers: require_resolve_allowlist.ensureUserAgentHeader({ Authorization: `Bearer ${params.accessToken}` }) },
|
|
1628
|
+
policy: params.ssrfPolicy,
|
|
1629
|
+
auditContext: `msteams.graph.${kind}`,
|
|
1630
|
+
timeoutMs: require_resolve_allowlist.resolveMSTeamsRequestTimeoutMs(params.deadline)
|
|
1631
|
+
});
|
|
1632
|
+
try {
|
|
1633
|
+
return await read(response);
|
|
1634
|
+
} finally {
|
|
1635
|
+
if (!response.bodyUsed) response.body?.cancel().catch(() => void 0);
|
|
1636
|
+
await release();
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
function normalizeGraphAttachment(att) {
|
|
1640
|
+
let content = att.content;
|
|
1641
|
+
if (typeof content === "string") try {
|
|
1642
|
+
content = JSON.parse(content);
|
|
1643
|
+
} catch {}
|
|
1644
|
+
return {
|
|
1645
|
+
id: att.id ?? void 0,
|
|
1646
|
+
contentType: require_resolve_allowlist.normalizeContentType(att.contentType),
|
|
1647
|
+
contentUrl: att.contentUrl ?? void 0,
|
|
1648
|
+
name: att.name ?? void 0,
|
|
1649
|
+
content
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
async function downloadMSTeamsGraphMedia(params) {
|
|
1653
|
+
if (!params.messageUrl || !params.tokenProvider) return { media: [] };
|
|
1654
|
+
const tokenProvider = params.tokenProvider;
|
|
1655
|
+
const policy = require_resolve_allowlist.resolveAttachmentFetchPolicy({
|
|
1656
|
+
allowHosts: params.allowHosts,
|
|
1657
|
+
authAllowHosts: params.authAllowHosts
|
|
1658
|
+
});
|
|
1659
|
+
const ssrfPolicy = (0, openclaw_plugin_sdk_ssrf_policy.buildHostnameAllowlistPolicyFromSuffixAllowlist)(policy.allowHosts);
|
|
1660
|
+
const messageUrl = params.messageUrl;
|
|
1661
|
+
const logGraphFailure = (phase, err) => {
|
|
1662
|
+
params.logger?.debug?.(`graph media ${phase} failed`, {
|
|
1663
|
+
messageUrl,
|
|
1664
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err)
|
|
1665
|
+
});
|
|
1666
|
+
params.logger?.warn?.(`msteams graph ${phase} failed`, {
|
|
1667
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
1668
|
+
...phase === "message parse" ? { messageUrl } : {}
|
|
1669
|
+
});
|
|
1670
|
+
};
|
|
1671
|
+
let accessToken;
|
|
1672
|
+
try {
|
|
1673
|
+
accessToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
1674
|
+
deadline: params.deadline,
|
|
1675
|
+
label: "MS Teams Graph media token",
|
|
1676
|
+
work: () => tokenProvider.getAccessToken("https://graph.microsoft.com")
|
|
1677
|
+
});
|
|
1678
|
+
} catch (err) {
|
|
1679
|
+
logGraphFailure("token acquisition", err);
|
|
1680
|
+
return {
|
|
1681
|
+
media: [],
|
|
1682
|
+
tokenError: true
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
let messageAttachments = [];
|
|
1686
|
+
let referenceAttachments = [];
|
|
1687
|
+
let messageStatus;
|
|
1688
|
+
try {
|
|
1689
|
+
await withGraphMediaResponse({
|
|
1690
|
+
...params,
|
|
1691
|
+
accessToken,
|
|
1692
|
+
ssrfPolicy
|
|
1693
|
+
}, messageUrl, "message", async (msgRes) => {
|
|
1694
|
+
messageStatus = msgRes.status;
|
|
1695
|
+
if (msgRes.ok) {
|
|
1696
|
+
let msgData;
|
|
1697
|
+
try {
|
|
1698
|
+
msgData = await (0, openclaw_plugin_sdk_provider_http.readProviderJsonResponse)(msgRes, "MS Teams Graph message");
|
|
1699
|
+
} catch (err) {
|
|
1700
|
+
logGraphFailure("message parse", err);
|
|
1701
|
+
msgData = {};
|
|
1702
|
+
}
|
|
1703
|
+
messageAttachments = Array.isArray(msgData.attachments) ? msgData.attachments : [];
|
|
1704
|
+
referenceAttachments = messageAttachments.filter((a) => a.contentType === "reference" && a.contentUrl && a.name);
|
|
1705
|
+
} else params.logger?.debug?.("graph media message fetch not ok", {
|
|
1706
|
+
messageUrl,
|
|
1707
|
+
status: messageStatus
|
|
2128
1708
|
});
|
|
2129
|
-
}
|
|
1709
|
+
});
|
|
1710
|
+
} catch (err) {
|
|
1711
|
+
logGraphFailure("message fetch", err);
|
|
1712
|
+
}
|
|
1713
|
+
const sharePointMedia = await downloadMSTeamsAttachments({
|
|
1714
|
+
...params,
|
|
1715
|
+
attachments: referenceAttachments,
|
|
1716
|
+
graphReferenceToken: accessToken,
|
|
1717
|
+
allowHosts: policy.allowHosts,
|
|
1718
|
+
authAllowHosts: policy.authAllowHosts
|
|
1719
|
+
});
|
|
1720
|
+
const { maxBytes, logger, deadline } = params;
|
|
1721
|
+
const hostedRequest = {
|
|
1722
|
+
accessToken,
|
|
1723
|
+
ssrfPolicy,
|
|
1724
|
+
deadline
|
|
2130
1725
|
};
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
1726
|
+
let hosted = { items: [] };
|
|
1727
|
+
try {
|
|
1728
|
+
hosted = await withGraphMediaResponse(hostedRequest, `${messageUrl}/hostedContents`, "collection", async (response) => {
|
|
1729
|
+
const status = response.status;
|
|
1730
|
+
if (!response.ok) return {
|
|
1731
|
+
status,
|
|
1732
|
+
items: []
|
|
1733
|
+
};
|
|
1734
|
+
try {
|
|
1735
|
+
return {
|
|
1736
|
+
status,
|
|
1737
|
+
items: await (0, openclaw_plugin_sdk_provider_http.readProviderJsonArrayFieldResponse)(response, "MS Teams Graph collection", "value")
|
|
1738
|
+
};
|
|
1739
|
+
} catch {
|
|
1740
|
+
return {
|
|
1741
|
+
status,
|
|
1742
|
+
items: []
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
} catch (err) {
|
|
1747
|
+
logger?.warn?.("msteams graph hostedContents fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1748
|
+
}
|
|
1749
|
+
const hostedMedia = [];
|
|
1750
|
+
for (const item of hosted.items) {
|
|
1751
|
+
if (!item.id) {
|
|
1752
|
+
hostedMedia.push(createGraphHostedContentFact(item));
|
|
1753
|
+
continue;
|
|
2137
1754
|
}
|
|
2138
|
-
let nativeResult;
|
|
2139
1755
|
try {
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
} catch (error) {
|
|
2165
|
-
params.log.warn?.("failed to record sent Teams message id", { error: require_channel_setup.formatUnknownError(error) });
|
|
2166
|
-
}
|
|
2167
|
-
}).finally(() => {
|
|
2168
|
-
pendingSettlement = void 0;
|
|
2169
|
-
});
|
|
2170
|
-
const shouldSuppressDefaultToolProgressMessages = streamController.hasStream() && teamsStreamMode === "progress";
|
|
2171
|
-
const progressCallbacks = streamController.hasStream() ? {
|
|
2172
|
-
onReasoningStream: async (payload) => {
|
|
2173
|
-
await streamController.pushReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true });
|
|
2174
|
-
return false;
|
|
2175
|
-
},
|
|
2176
|
-
onReasoningEnd: () => {
|
|
2177
|
-
streamController.resetReasoningProgress();
|
|
2178
|
-
return false;
|
|
2179
|
-
},
|
|
2180
|
-
onToolStart: streamController.pushToolEvent,
|
|
2181
|
-
onItemEvent: streamController.pushItemEvent,
|
|
2182
|
-
onPlanUpdate: async (payload) => {
|
|
2183
|
-
if (payload.phase !== "update") return false;
|
|
2184
|
-
await streamController.pushPlanProgress(payload.steps, payload);
|
|
2185
|
-
return false;
|
|
2186
|
-
},
|
|
2187
|
-
onApprovalEvent: async (payload) => {
|
|
2188
|
-
await streamController.pushApprovalEvent(payload);
|
|
2189
|
-
return false;
|
|
1756
|
+
const sourceId = item.id;
|
|
1757
|
+
const valueUrl = `${messageUrl}/hostedContents/${encodeURIComponent(sourceId)}/$value`;
|
|
1758
|
+
await withGraphMediaResponse(hostedRequest, valueUrl, "hostedContent.value", async (valRes) => {
|
|
1759
|
+
if (!valRes.ok) {
|
|
1760
|
+
hostedMedia.push(createGraphHostedContentFact(item));
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
const saved = await require_runtime.getMSTeamsRuntime().channel.media.saveResponseMedia(valRes, {
|
|
1764
|
+
sourceUrl: valueUrl,
|
|
1765
|
+
maxBytes,
|
|
1766
|
+
fallbackContentType: item.contentType ?? void 0,
|
|
1767
|
+
subdir: "inbound"
|
|
1768
|
+
});
|
|
1769
|
+
hostedMedia.push({
|
|
1770
|
+
path: saved.path,
|
|
1771
|
+
contentType: saved.contentType,
|
|
1772
|
+
kind: require_resolve_allowlist.resolveMSTeamsMediaKind({ contentType: saved.contentType }),
|
|
1773
|
+
sourceId
|
|
1774
|
+
});
|
|
1775
|
+
});
|
|
1776
|
+
} catch (err) {
|
|
1777
|
+
hostedMedia.push(createGraphHostedContentFact(item));
|
|
1778
|
+
logger?.warn?.("msteams graph hostedContent value fetch failed", { error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err) });
|
|
1779
|
+
continue;
|
|
2190
1780
|
}
|
|
2191
|
-
}
|
|
1781
|
+
}
|
|
1782
|
+
const normalizedAttachments = messageAttachments.map(normalizeGraphAttachment);
|
|
1783
|
+
const referenceUrls = new Set(referenceAttachments.map((att) => att.contentUrl));
|
|
1784
|
+
const filteredAttachments = normalizedAttachments.filter((att) => att.contentType !== "reference" || !referenceUrls.has(att.contentUrl));
|
|
1785
|
+
let attachmentMedia = [];
|
|
1786
|
+
try {
|
|
1787
|
+
attachmentMedia = await downloadMSTeamsAttachments({
|
|
1788
|
+
attachments: filteredAttachments,
|
|
1789
|
+
maxBytes: params.maxBytes,
|
|
1790
|
+
tokenProvider: params.tokenProvider,
|
|
1791
|
+
allowHosts: policy.allowHosts,
|
|
1792
|
+
authAllowHosts: policy.authAllowHosts,
|
|
1793
|
+
deadline: params.deadline,
|
|
1794
|
+
logger: params.logger
|
|
1795
|
+
});
|
|
1796
|
+
} catch (err) {
|
|
1797
|
+
params.logger?.warn?.("msteams graph attachment download failed", {
|
|
1798
|
+
error: (0, openclaw_plugin_sdk_error_runtime.coerceErrorMessage)(err),
|
|
1799
|
+
messageUrl
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
2192
1802
|
return {
|
|
2193
|
-
|
|
2194
|
-
...
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
streamController.onPartialReply(payload);
|
|
2203
|
-
return false;
|
|
2204
|
-
} } : {},
|
|
2205
|
-
...progressCallbacks,
|
|
2206
|
-
...shouldSuppressDefaultToolProgressMessages ? { suppressDefaultToolProgressMessages: true } : {},
|
|
2207
|
-
disableBlockStreaming: blockStreamingResolved == null ? void 0 : !blockStreamingResolved,
|
|
2208
|
-
onModelSelected
|
|
2209
|
-
}
|
|
1803
|
+
media: [
|
|
1804
|
+
...sharePointMedia,
|
|
1805
|
+
...hostedMedia,
|
|
1806
|
+
...attachmentMedia
|
|
1807
|
+
],
|
|
1808
|
+
hostedCount: hosted.items.length,
|
|
1809
|
+
attachmentCount: filteredAttachments.length + sharePointMedia.length,
|
|
1810
|
+
hostedStatus: hosted.status,
|
|
1811
|
+
attachmentStatus: messageStatus
|
|
2210
1812
|
};
|
|
2211
1813
|
}
|
|
2212
1814
|
//#endregion
|
|
2213
|
-
//#region extensions/msteams/src/monitor-handler/inbound-
|
|
2214
|
-
async function
|
|
2215
|
-
const
|
|
2216
|
-
const
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
const { agentBody, inboundMedia } = content;
|
|
2221
|
-
const { teamAadGroupId, quoteBodyFull, quoteSenderId, quoteSenderName, threadContext } = thread;
|
|
2222
|
-
const { conversationId, conversationType, isChannel, teamId, graphChannelId } = facts;
|
|
2223
|
-
const teamsFrom = isDirectMessage ? `msteams:${senderId}` : isChannel ? `msteams:channel:${conversationId}` : `msteams:group:${conversationId}`;
|
|
2224
|
-
const teamsTo = isDirectMessage ? `user:${senderId}` : `conversation:${conversationId}`;
|
|
2225
|
-
const envelopeFrom = isDirectMessage ? senderName : conversationType;
|
|
2226
|
-
const buildEnvelope = await (0, openclaw_plugin_sdk_channel_inbound.createChannelInboundEnvelopeBuilderAsync)({
|
|
2227
|
-
cfg,
|
|
2228
|
-
route
|
|
2229
|
-
});
|
|
2230
|
-
let combinedBody = buildEnvelope({
|
|
2231
|
-
channel: "Teams",
|
|
2232
|
-
from: envelopeFrom,
|
|
2233
|
-
timestamp,
|
|
2234
|
-
body: agentBody
|
|
2235
|
-
});
|
|
2236
|
-
const isRoomish = !isDirectMessage;
|
|
2237
|
-
const historyKey = isRoomish ? facts.historyKey : void 0;
|
|
2238
|
-
if (isRoomish && historyKey) combinedBody = (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildPendingContext({
|
|
2239
|
-
historyKey,
|
|
2240
|
-
limit: historyLimit,
|
|
2241
|
-
currentMessage: combinedBody,
|
|
2242
|
-
formatEntry: (entry) => buildEnvelope({
|
|
2243
|
-
channel: "Teams",
|
|
2244
|
-
from: conversationType,
|
|
2245
|
-
timestamp: entry.timestamp,
|
|
2246
|
-
previousTimestamp: null,
|
|
2247
|
-
body: `${entry.sender}: ${entry.body}${entry.messageId ? ` [id:${entry.messageId}]` : ""}`
|
|
2248
|
-
})
|
|
2249
|
-
});
|
|
2250
|
-
const inboundHistory = isRoomish && historyKey && historyLimit > 0 ? (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildInboundHistory({
|
|
2251
|
-
historyKey,
|
|
2252
|
-
limit: historyLimit
|
|
2253
|
-
}) : void 0;
|
|
2254
|
-
const commandBody = text.trim();
|
|
2255
|
-
const quoteSenderAllowed = quoteInfo && quoteInfo.sender ? (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
|
|
2256
|
-
isGroup: !isDirectMessage,
|
|
2257
|
-
groupPolicy,
|
|
2258
|
-
allowFrom: effectiveGroupAllowFrom,
|
|
2259
|
-
isSenderAllowed: (allowFrom) => (0, openclaw_plugin_sdk_allow_from.resolveAllowlistMatchSimple)({
|
|
2260
|
-
allowFrom,
|
|
2261
|
-
senderId: quoteSenderId ?? "",
|
|
2262
|
-
senderName: quoteSenderName,
|
|
2263
|
-
allowNameMatching
|
|
2264
|
-
}).allowed
|
|
2265
|
-
}) : true;
|
|
2266
|
-
const nativeChannelId = isChannel && teamAadGroupId ? `${teamAadGroupId}/${graphChannelId}` : void 0;
|
|
2267
|
-
const boundIngress = await resolveMSTeamsSenderAccess({
|
|
2268
|
-
cfg: route.accountId === params.listenerAccountId ? params.readConfig() : params.accountPolicyCfg,
|
|
2269
|
-
accountId: route.accountId,
|
|
2270
|
-
activity,
|
|
2271
|
-
hasControlCommand: admission.isControlCommand,
|
|
2272
|
-
conversationThreadId: facts.threadId,
|
|
2273
|
-
contextBinding: {
|
|
2274
|
-
agentId: route.agentId,
|
|
2275
|
-
sessionKey: route.sessionKey,
|
|
2276
|
-
...activity.id ? { messageId: activity.id } : {},
|
|
2277
|
-
...nativeChannelId ? { nativeChannelId } : {},
|
|
2278
|
-
inboundEventKind: "user_request"
|
|
2279
|
-
}
|
|
1815
|
+
//#region extensions/msteams/src/monitor-handler/inbound-content.ts
|
|
1816
|
+
async function prepareMSTeamsInboundContent(params) {
|
|
1817
|
+
const activity = params.entry.context.activity;
|
|
1818
|
+
const mayRecoverGraphMedia = Boolean(params.htmlSummary?.attachmentIds.length) || shouldAttemptMSTeamsGraphMediaFallback({
|
|
1819
|
+
conversationType: params.conversationType,
|
|
1820
|
+
htmlSummary: params.htmlSummary,
|
|
1821
|
+
graphMediaFallback: params.graphMediaFallback
|
|
2280
1822
|
});
|
|
2281
|
-
if (!
|
|
2282
|
-
log.
|
|
2283
|
-
return
|
|
2284
|
-
kind: "completed",
|
|
2285
|
-
finalResponses: 0
|
|
2286
|
-
};
|
|
1823
|
+
if (!params.rawBody && params.advertisedMedia.length === 0 && !mayRecoverGraphMedia) {
|
|
1824
|
+
params.log.debug?.("skipping empty message after stripping mentions");
|
|
1825
|
+
return null;
|
|
2287
1826
|
}
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
channel: "msteams",
|
|
2291
|
-
contextVisibility: contextVisibilityMode,
|
|
2292
|
-
supplemental: {
|
|
2293
|
-
channelStructuredContext: threadContext.length ? [{
|
|
2294
|
-
label: "Thread history",
|
|
2295
|
-
source: "msteams",
|
|
2296
|
-
type: "chat_window",
|
|
2297
|
-
sessionTranscriptMode: "preserve",
|
|
2298
|
-
payload: {
|
|
2299
|
-
order: "chronological",
|
|
2300
|
-
messages: threadContext
|
|
2301
|
-
}
|
|
2302
|
-
}] : void 0,
|
|
2303
|
-
quote: quoteInfo ? {
|
|
2304
|
-
id: quoteInfo.id ?? activity.replyToId ?? void 0,
|
|
2305
|
-
body: quoteBodyFull ?? quoteInfo.body,
|
|
2306
|
-
sender: quoteInfo.sender,
|
|
2307
|
-
senderAllowed: quoteSenderAllowed,
|
|
2308
|
-
isQuote: true
|
|
2309
|
-
} : void 0
|
|
2310
|
-
},
|
|
2311
|
-
media: await (0, openclaw_plugin_sdk_channel_inbound.toInboundMediaFactsWithMetadata)(inboundMedia),
|
|
2312
|
-
messageId: activity.id,
|
|
2313
|
-
timestamp: timestamp?.getTime() ?? Date.now(),
|
|
2314
|
-
from: teamsFrom,
|
|
2315
|
-
sender: {
|
|
2316
|
-
id: senderId,
|
|
2317
|
-
name: senderName
|
|
2318
|
-
},
|
|
2319
|
-
conversation: {
|
|
2320
|
-
kind: isDirectMessage ? "direct" : isChannel ? "channel" : "group",
|
|
2321
|
-
id: conversationId,
|
|
2322
|
-
label: envelopeFrom,
|
|
2323
|
-
spaceId: teamId,
|
|
2324
|
-
nativeChannelId
|
|
2325
|
-
},
|
|
2326
|
-
route: {
|
|
2327
|
-
agentId: route.agentId,
|
|
2328
|
-
dmScope: route.dmScope,
|
|
2329
|
-
accountId: route.accountId,
|
|
2330
|
-
routeSessionKey: route.sessionKey
|
|
2331
|
-
},
|
|
2332
|
-
reply: {
|
|
2333
|
-
to: teamsTo,
|
|
2334
|
-
originatingTo: `conversation:${conversationId}`,
|
|
2335
|
-
messageThreadId: facts.threadId ?? void 0,
|
|
2336
|
-
replyToId: activity.replyToId ?? void 0,
|
|
2337
|
-
nativeChannelId
|
|
2338
|
-
},
|
|
2339
|
-
message: {
|
|
2340
|
-
body: combinedBody,
|
|
2341
|
-
bodyForAgent: agentBody,
|
|
2342
|
-
inboundHistory,
|
|
2343
|
-
rawBody,
|
|
2344
|
-
commandBody
|
|
2345
|
-
},
|
|
2346
|
-
sessionTranscript: { historyLimit: isRoomish ? historyLimit : 0 },
|
|
2347
|
-
access: {
|
|
2348
|
-
mentions: {
|
|
2349
|
-
canDetectMention: !isDirectMessage,
|
|
2350
|
-
wasMentioned: isDirectMessage || params.mentionWasEffective
|
|
2351
|
-
},
|
|
2352
|
-
commands: { authorized: commandAuthorized === true }
|
|
2353
|
-
},
|
|
2354
|
-
extra: {
|
|
2355
|
-
GroupSubject: !isDirectMessage ? conversationType : void 0,
|
|
2356
|
-
ReplyToIsQuote: quoteInfo ? true : void 0
|
|
2357
|
-
}
|
|
2358
|
-
});
|
|
2359
|
-
const preview = (0, openclaw_plugin_sdk_text_utility_runtime.sliceUtf16Safe)(rawBody.replace(/\s+/g, " "), 0, 160);
|
|
2360
|
-
logVerboseMessage(`msteams inbound: from=${ctxPayload.From} preview="${preview}"`);
|
|
2361
|
-
const { dispatcherOptions, delivery, replyOptions } = createMSTeamsReplyDispatcher({
|
|
2362
|
-
cfg,
|
|
2363
|
-
agentId: route.agentId,
|
|
2364
|
-
sessionKey: route.sessionKey,
|
|
2365
|
-
accountId: route.accountId,
|
|
2366
|
-
runtime,
|
|
2367
|
-
log,
|
|
2368
|
-
app,
|
|
2369
|
-
conversationRef,
|
|
2370
|
-
context,
|
|
2371
|
-
replyStyle,
|
|
2372
|
-
textLimit,
|
|
2373
|
-
onSentMessageIds: (ids) => {
|
|
2374
|
-
for (const id of ids) require_conversation_store_state.recordMSTeamsSentMessage(conversationId, id, { accountId: route.accountId });
|
|
2375
|
-
},
|
|
2376
|
-
tokenProvider,
|
|
2377
|
-
sharePointSiteId: msteamsCfg?.sharePointSiteId
|
|
2378
|
-
});
|
|
2379
|
-
const senderTimezone = (activity.entities?.find((entity) => entity.type === "clientInfo"))?.timezone || conversationRef.timezone;
|
|
2380
|
-
const turnConfig = senderTimezone && !cfg.agents?.defaults?.userTimezone ? {
|
|
2381
|
-
...cfg,
|
|
2382
|
-
agents: {
|
|
2383
|
-
...cfg.agents,
|
|
2384
|
-
defaults: {
|
|
2385
|
-
...cfg.agents?.defaults,
|
|
2386
|
-
userTimezone: senderTimezone
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
} : cfg;
|
|
2390
|
-
log.info("dispatching to agent", { sessionKey: route.sessionKey });
|
|
1827
|
+
let resolvedMedia = [];
|
|
1828
|
+
const deadline = require_resolve_allowlist.createMSTeamsInboundMediaDeadline();
|
|
2391
1829
|
try {
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
})
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
1830
|
+
resolvedMedia = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
1831
|
+
deadline,
|
|
1832
|
+
label: "MS Teams inbound media",
|
|
1833
|
+
work: async () => {
|
|
1834
|
+
let mediaList = await downloadMSTeamsAttachments({
|
|
1835
|
+
attachments: params.entry.attachments,
|
|
1836
|
+
maxBytes: params.mediaMaxBytes,
|
|
1837
|
+
tokenProvider: params.tokenProvider,
|
|
1838
|
+
allowHosts: params.mediaAllowHosts,
|
|
1839
|
+
authAllowHosts: params.mediaAuthAllowHosts,
|
|
1840
|
+
deadline,
|
|
1841
|
+
logger: params.log
|
|
1842
|
+
});
|
|
1843
|
+
if (!hasMaterializedMedia(mediaList)) {
|
|
1844
|
+
const attachmentIds = extractMSTeamsHtmlAttachmentIds(params.entry.attachments);
|
|
1845
|
+
const hasHtmlFileAttachment = attachmentIds.length > 0;
|
|
1846
|
+
const shouldFetchGraphMessage = hasHtmlFileAttachment || shouldAttemptMSTeamsGraphMediaFallback(params);
|
|
1847
|
+
const isBotFrameworkPersonalChat = isBotFrameworkPersonalChatId(params.conversationId);
|
|
1848
|
+
if (hasHtmlFileAttachment && isBotFrameworkPersonalChat) {
|
|
1849
|
+
if (!activity.serviceUrl) params.log.debug?.("bot framework attachment skipped (missing serviceUrl)", {
|
|
1850
|
+
conversationType: params.conversationType,
|
|
1851
|
+
conversationId: params.conversationId
|
|
1852
|
+
});
|
|
1853
|
+
else {
|
|
1854
|
+
const bfMedia = await downloadMSTeamsBotFrameworkAttachments({
|
|
1855
|
+
serviceUrl: activity.serviceUrl,
|
|
1856
|
+
attachmentIds,
|
|
1857
|
+
tokenProvider: params.tokenProvider,
|
|
1858
|
+
maxBytes: params.mediaMaxBytes,
|
|
1859
|
+
allowHosts: params.mediaAllowHosts,
|
|
1860
|
+
authAllowHosts: params.mediaAuthAllowHosts,
|
|
1861
|
+
deadline
|
|
1862
|
+
});
|
|
1863
|
+
mediaList = mergeMSTeamsMediaFacts(mediaList, bfMedia.media, { positionallyAligned: false });
|
|
1864
|
+
if (!hasMaterializedMedia(bfMedia.media)) params.log.debug?.("bot framework attachments fetch empty", {
|
|
1865
|
+
conversationType: params.conversationType,
|
|
1866
|
+
attachmentCount: bfMedia.attachmentCount ?? attachmentIds.length
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
2428
1869
|
}
|
|
2429
|
-
|
|
1870
|
+
if (shouldFetchGraphMessage && !isBotFrameworkPersonalChat) {
|
|
1871
|
+
const graphTeamAadGroupId = params.conversationType.trim().toLowerCase() === "channel" && !params.teamAadGroupId ? await params.resolveTeamAadGroupId(deadline) : params.teamAadGroupId;
|
|
1872
|
+
const messageUrl = buildMSTeamsGraphMessageUrl({
|
|
1873
|
+
conversationType: params.conversationType,
|
|
1874
|
+
conversationId: params.conversationId,
|
|
1875
|
+
messageId: activity.id,
|
|
1876
|
+
threadRootMessageId: params.conversationMessageId ?? activity.replyToId,
|
|
1877
|
+
teamAadGroupId: graphTeamAadGroupId,
|
|
1878
|
+
channelId: activity.channelData?.channel?.id
|
|
1879
|
+
});
|
|
1880
|
+
if (!messageUrl) params.log.debug?.("graph message url unavailable", {
|
|
1881
|
+
conversationType: params.conversationType,
|
|
1882
|
+
hasChannelData: Boolean(activity.channelData),
|
|
1883
|
+
messageId: activity.id,
|
|
1884
|
+
replyToId: activity.replyToId
|
|
1885
|
+
});
|
|
1886
|
+
else {
|
|
1887
|
+
const graphMedia = await downloadMSTeamsGraphMedia({
|
|
1888
|
+
messageUrl,
|
|
1889
|
+
tokenProvider: params.tokenProvider,
|
|
1890
|
+
maxBytes: params.mediaMaxBytes,
|
|
1891
|
+
allowHosts: params.mediaAllowHosts,
|
|
1892
|
+
authAllowHosts: params.mediaAuthAllowHosts,
|
|
1893
|
+
deadline,
|
|
1894
|
+
logger: params.log
|
|
1895
|
+
});
|
|
1896
|
+
mediaList = mergeMSTeamsMediaFacts(mediaList, graphMedia.media, { positionallyAligned: false });
|
|
1897
|
+
if (!hasMaterializedMedia(mediaList)) params.log.debug?.("graph media fetch empty", {
|
|
1898
|
+
messageUrl,
|
|
1899
|
+
hostedStatus: graphMedia.hostedStatus,
|
|
1900
|
+
attachmentStatus: graphMedia.attachmentStatus,
|
|
1901
|
+
hostedCount: graphMedia.hostedCount,
|
|
1902
|
+
attachmentCount: graphMedia.attachmentCount,
|
|
1903
|
+
tokenError: graphMedia.tokenError,
|
|
1904
|
+
attachmentIdCount: attachmentIds.length
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
const materializedCount = mediaList.filter((media) => Boolean(media.path)).length;
|
|
1910
|
+
if (materializedCount > 0) params.log.debug?.("downloaded attachments", { count: materializedCount });
|
|
1911
|
+
else if (params.htmlSummary?.imgTags) params.log.debug?.("inline images detected but none downloaded", {
|
|
1912
|
+
imgTags: params.htmlSummary.imgTags,
|
|
1913
|
+
srcHosts: params.htmlSummary.srcHosts,
|
|
1914
|
+
dataImages: params.htmlSummary.dataImages,
|
|
1915
|
+
cidImages: params.htmlSummary.cidImages
|
|
1916
|
+
});
|
|
1917
|
+
return mediaList;
|
|
2430
1918
|
}
|
|
2431
1919
|
});
|
|
2432
|
-
const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
|
|
2433
|
-
const counts = (0, openclaw_plugin_sdk_channel_inbound.resolveInboundReplyDispatchCounts)(dispatchResult);
|
|
2434
|
-
log.info("dispatch complete", { counts });
|
|
2435
|
-
if ((0, openclaw_plugin_sdk_channel_inbound.hasFinalInboundReplyDispatch)(dispatchResult)) logVerboseMessage(`msteams: delivered ${counts.final} repl${counts.final === 1 ? "y" : "ies"} to ${teamsTo}`);
|
|
2436
|
-
return {
|
|
2437
|
-
kind: "completed",
|
|
2438
|
-
finalResponses: counts.final
|
|
2439
|
-
};
|
|
2440
1920
|
} catch (err) {
|
|
2441
|
-
log.
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
1921
|
+
params.log.debug?.("failed to resolve inbound Teams media", { error: require_channel_setup.formatUnknownError(err) });
|
|
1922
|
+
}
|
|
1923
|
+
const inboundMedia = mergeMSTeamsMediaFacts(params.advertisedMedia, resolvedMedia);
|
|
1924
|
+
const body = params.rawBody;
|
|
1925
|
+
const expectedMediaCount = Math.max(params.advertisedMedia.length, resolvedMedia.length);
|
|
1926
|
+
const unavailableCount = inboundMedia.filter((media) => !media.path).length + Math.max(0, expectedMediaCount - inboundMedia.length);
|
|
1927
|
+
const agentBody = unavailableCount === 0 ? body : (0, openclaw_plugin_sdk_channel_inbound.formatInboundMediaUnavailableText)({
|
|
1928
|
+
body,
|
|
1929
|
+
notice: `[msteams ${unavailableCount > 1 ? `${unavailableCount} attachments` : "attachment"} unavailable]`
|
|
1930
|
+
});
|
|
1931
|
+
if (!agentBody && inboundMedia.length === 0) {
|
|
1932
|
+
params.log.debug?.("skipping empty message after Graph media recovery");
|
|
1933
|
+
return null;
|
|
1934
|
+
}
|
|
1935
|
+
return {
|
|
1936
|
+
agentBody,
|
|
1937
|
+
inboundMedia
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
function shouldAttemptMSTeamsGraphMediaFallback(params) {
|
|
1941
|
+
const conversationType = params.conversationType.trim().toLowerCase();
|
|
1942
|
+
return params.graphMediaFallback === true && (conversationType === "channel" || conversationType === "groupchat") && (params.htmlSummary?.htmlAttachments ?? 0) > 0;
|
|
1943
|
+
}
|
|
1944
|
+
function hasDefinitiveContentType(media) {
|
|
1945
|
+
const contentType = media.contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
1946
|
+
return Boolean(contentType && contentType !== "application/octet-stream" && contentType !== "binary/octet-stream");
|
|
1947
|
+
}
|
|
1948
|
+
function mergeMSTeamsMediaFacts(nativeMedia, materializedMedia, options = {}) {
|
|
1949
|
+
const merged = [...nativeMedia];
|
|
1950
|
+
const nativeSlotCount = nativeMedia.length;
|
|
1951
|
+
const nativeIndexBySourceId = /* @__PURE__ */ new Map();
|
|
1952
|
+
nativeMedia.forEach((media, index) => {
|
|
1953
|
+
if (media.sourceId && !nativeIndexBySourceId.has(media.sourceId)) nativeIndexBySourceId.set(media.sourceId, index);
|
|
1954
|
+
});
|
|
1955
|
+
for (const [index, materialized] of materializedMedia.entries()) {
|
|
1956
|
+
const sourceIndex = materialized.sourceId ? nativeIndexBySourceId.get(materialized.sourceId) : void 0;
|
|
1957
|
+
const positionalIndex = options.positionallyAligned === false || index >= nativeMedia.length ? void 0 : index;
|
|
1958
|
+
const unresolvedIndexes = options.positionallyAligned === false ? merged.slice(0, nativeSlotCount).flatMap((media, mediaIndex) => !media.path && !media.sourceId ? [mediaIndex] : []) : [];
|
|
1959
|
+
const fallbackIndex = unresolvedIndexes.find((mediaIndex) => merged[mediaIndex]?.kind === materialized.kind) ?? (unresolvedIndexes.length === 1 ? unresolvedIndexes[0] : void 0);
|
|
1960
|
+
const targetIndex = sourceIndex ?? positionalIndex ?? fallbackIndex;
|
|
1961
|
+
if (targetIndex === void 0) {
|
|
1962
|
+
if (materialized.sourceId) nativeIndexBySourceId.set(materialized.sourceId, merged.length);
|
|
1963
|
+
merged.push(materialized);
|
|
1964
|
+
} else {
|
|
1965
|
+
if (materialized.sourceId) nativeIndexBySourceId.set(materialized.sourceId, targetIndex);
|
|
1966
|
+
const current = merged[targetIndex];
|
|
1967
|
+
if (materialized.path) merged[targetIndex] = materialized;
|
|
1968
|
+
else if (!current?.path) merged[targetIndex] = {
|
|
1969
|
+
...current,
|
|
1970
|
+
...materialized,
|
|
1971
|
+
kind: hasDefinitiveContentType(materialized) || !current?.kind ? materialized.kind : current.kind
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
2448
1974
|
}
|
|
1975
|
+
return merged;
|
|
1976
|
+
}
|
|
1977
|
+
function hasMaterializedMedia(media) {
|
|
1978
|
+
return media.some((entry) => Boolean(entry.path));
|
|
2449
1979
|
}
|
|
2450
1980
|
//#endregion
|
|
2451
1981
|
//#region extensions/msteams/src/monitor-handler/inbound-facts.ts
|
|
2452
|
-
function extractTextFromHtmlAttachments(attachments) {
|
|
1982
|
+
function extractTextFromHtmlAttachments(attachments, activity) {
|
|
2453
1983
|
for (const attachment of attachments) {
|
|
2454
1984
|
const raw = require_resolve_allowlist.extractHtmlFromAttachment(attachment);
|
|
2455
1985
|
if (!raw) continue;
|
|
2456
|
-
const text = require_resolve_allowlist.htmlToPlainText(
|
|
1986
|
+
const text = require_resolve_allowlist.htmlToPlainText(require_resolve_allowlist.normalizeMSTeamsMentionTags(require_resolve_allowlist.replaceTeamsEmojiSpriteImagesWithAltText(raw), activity, { preserveHtml: true }).replace(/<a\b[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gis, "$2 $1"));
|
|
2457
1987
|
if (text) return text;
|
|
2458
1988
|
}
|
|
2459
1989
|
return "";
|
|
@@ -2462,9 +1992,9 @@ async function prepareMSTeamsDebounceEntry(params) {
|
|
|
2462
1992
|
const activity = params.context.activity;
|
|
2463
1993
|
const attachments = Array.isArray(activity.attachments) ? activity.attachments : [];
|
|
2464
1994
|
const rawText = activity.text?.trim() ?? "";
|
|
2465
|
-
const text = rawText ? require_resolve_allowlist.
|
|
1995
|
+
const text = rawText ? require_resolve_allowlist.normalizeMSTeamsMentionTags(rawText, activity) : extractTextFromHtmlAttachments(attachments, activity) || require_resolve_allowlist.stripMSTeamsMentionTags(serializeMSTeamsAdaptiveCardActionValue(activity.value) || "");
|
|
2466
1996
|
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
2467
|
-
const replyToId = activity.replyToId
|
|
1997
|
+
const replyToId = activity.replyToId;
|
|
2468
1998
|
const implicitMentionKinds = conversationId && replyToId && await require_conversation_store_state.wasMSTeamsMessageSentWithPersistence({
|
|
2469
1999
|
conversationId,
|
|
2470
2000
|
messageId: replyToId,
|
|
@@ -2480,15 +2010,23 @@ async function prepareMSTeamsDebounceEntry(params) {
|
|
|
2480
2010
|
turnAdoptionLifecycle: params.turnAdoptionLifecycle
|
|
2481
2011
|
};
|
|
2482
2012
|
}
|
|
2483
|
-
function
|
|
2484
|
-
const
|
|
2485
|
-
const from = activity.from;
|
|
2013
|
+
function assembleMSTeamsInboundFacts(entry) {
|
|
2014
|
+
const activity = entry.context.activity;
|
|
2486
2015
|
const conversation = activity.conversation;
|
|
2016
|
+
const rawConversationId = conversation?.id ?? "";
|
|
2017
|
+
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(rawConversationId);
|
|
2018
|
+
const conversationMessageId = require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId);
|
|
2019
|
+
const conversationType = conversation?.conversationType ?? "personal";
|
|
2020
|
+
const isChannel = conversationType === "channel";
|
|
2021
|
+
const teamId = activity.channelData?.team?.id;
|
|
2022
|
+
const threadId = isChannel ? conversationMessageId ?? activity.replyToId : void 0;
|
|
2023
|
+
const advertisedMedia = resolveMSTeamsAdvertisedMedia(entry.attachments);
|
|
2024
|
+
const from = activity.from;
|
|
2487
2025
|
const clientInfo = activity.entities?.find((entity) => entity.type === "clientInfo");
|
|
2488
2026
|
const tenantId = activity.channelData?.tenant?.id ?? conversation?.tenantId;
|
|
2489
2027
|
const aadObjectId = from?.aadObjectId;
|
|
2490
2028
|
const serviceUrl = require_bot_framework_service_url.tryNormalizeBotFrameworkServiceUrl(activity.serviceUrl);
|
|
2491
|
-
|
|
2029
|
+
const conversationRef = {
|
|
2492
2030
|
activityId: activity.id,
|
|
2493
2031
|
user: from ? {
|
|
2494
2032
|
id: from.id,
|
|
@@ -2510,18 +2048,6 @@ function buildStoredConversationReference(params) {
|
|
|
2510
2048
|
...clientInfo?.timezone ? { timezone: clientInfo.timezone } : {},
|
|
2511
2049
|
...threadId ? { threadId } : {}
|
|
2512
2050
|
};
|
|
2513
|
-
}
|
|
2514
|
-
function assembleMSTeamsInboundFacts(entry) {
|
|
2515
|
-
const activity = entry.context.activity;
|
|
2516
|
-
const conversation = activity.conversation;
|
|
2517
|
-
const rawConversationId = conversation?.id ?? "";
|
|
2518
|
-
const conversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(rawConversationId);
|
|
2519
|
-
const conversationMessageId = require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId);
|
|
2520
|
-
const conversationType = conversation?.conversationType ?? "personal";
|
|
2521
|
-
const isChannel = conversationType === "channel";
|
|
2522
|
-
const teamId = activity.channelData?.team?.id;
|
|
2523
|
-
const threadId = isChannel ? conversationMessageId ?? activity.replyToId ?? void 0 : void 0;
|
|
2524
|
-
const advertisedMedia = resolveMSTeamsAdvertisedMedia(entry.attachments);
|
|
2525
2051
|
return {
|
|
2526
2052
|
...entry,
|
|
2527
2053
|
activity,
|
|
@@ -2530,9 +2056,7 @@ function assembleMSTeamsInboundFacts(entry) {
|
|
|
2530
2056
|
rawBody: entry.text,
|
|
2531
2057
|
advertisedMedia,
|
|
2532
2058
|
quoteInfo: require_resolve_allowlist.extractMSTeamsQuoteInfo(entry.attachments),
|
|
2533
|
-
attachmentTypes: entry.attachments.map((attachment) => typeof attachment.contentType === "string" ? attachment.contentType : void 0).filter(Boolean).slice(0, 3),
|
|
2534
2059
|
htmlSummary: summarizeMSTeamsHtmlAttachments(entry.attachments),
|
|
2535
|
-
rawConversationId,
|
|
2536
2060
|
conversationId,
|
|
2537
2061
|
conversationMessageId,
|
|
2538
2062
|
conversationType,
|
|
@@ -2541,44 +2065,10 @@ function assembleMSTeamsInboundFacts(entry) {
|
|
|
2541
2065
|
graphChannelId: activity.channelData?.channel?.id?.trim() || conversationId,
|
|
2542
2066
|
threadId,
|
|
2543
2067
|
historyKey: threadId ? `${conversationId}:thread:${threadId}` : conversationId,
|
|
2544
|
-
conversationRef
|
|
2545
|
-
activity,
|
|
2546
|
-
conversationId,
|
|
2547
|
-
conversationType,
|
|
2548
|
-
teamId,
|
|
2549
|
-
threadId
|
|
2550
|
-
})
|
|
2068
|
+
conversationRef
|
|
2551
2069
|
};
|
|
2552
2070
|
}
|
|
2553
2071
|
//#endregion
|
|
2554
|
-
//#region extensions/msteams/src/team-identity.ts
|
|
2555
|
-
const teamGroupIdCache = /* @__PURE__ */ new Map();
|
|
2556
|
-
const TEAM_GROUP_ID_CACHE_MAX_ENTRIES = 500;
|
|
2557
|
-
function cacheTeamGroupId(conversationTeamId, groupId) {
|
|
2558
|
-
teamGroupIdCache.set(conversationTeamId, groupId);
|
|
2559
|
-
(0, openclaw_plugin_sdk_collection_runtime.pruneMapToMaxSize)(teamGroupIdCache, TEAM_GROUP_ID_CACHE_MAX_ENTRIES);
|
|
2560
|
-
}
|
|
2561
|
-
/** Resolve the Graph team GUID without ever treating a Bot Framework team ID as equivalent. */
|
|
2562
|
-
async function resolveTeamGroupId(params) {
|
|
2563
|
-
const activityGroupId = params.aadGroupId?.trim();
|
|
2564
|
-
if (activityGroupId) {
|
|
2565
|
-
cacheTeamGroupId(params.conversationTeamId, activityGroupId);
|
|
2566
|
-
return activityGroupId;
|
|
2567
|
-
}
|
|
2568
|
-
const cached = teamGroupIdCache.get(params.conversationTeamId);
|
|
2569
|
-
if (cached) return cached;
|
|
2570
|
-
const getTeamDetails = params.getTeamDetails;
|
|
2571
|
-
if (!getTeamDetails) return;
|
|
2572
|
-
const groupId = (await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2573
|
-
deadline: params.deadline,
|
|
2574
|
-
label: "MS Teams team details",
|
|
2575
|
-
work: () => getTeamDetails(params.conversationTeamId)
|
|
2576
|
-
})).aadGroupId?.trim();
|
|
2577
|
-
if (!groupId) return;
|
|
2578
|
-
cacheTeamGroupId(params.conversationTeamId, groupId);
|
|
2579
|
-
return groupId;
|
|
2580
|
-
}
|
|
2581
|
-
//#endregion
|
|
2582
2072
|
//#region extensions/msteams/src/thread-parent-context.ts
|
|
2583
2073
|
const PARENT_CACHE_TTL_MS = 3e5;
|
|
2584
2074
|
const PARENT_CACHE_MAX = 100;
|
|
@@ -2592,7 +2082,7 @@ function touchLru(map, key, value, max) {
|
|
|
2592
2082
|
}
|
|
2593
2083
|
async function fetchParentMessageCached(token, groupId, channelId, parentId, deadline) {
|
|
2594
2084
|
const key = JSON.stringify([
|
|
2595
|
-
(0,
|
|
2085
|
+
(0, openclaw_plugin_sdk_node_crypto_runtime.sha256Hex)(token),
|
|
2596
2086
|
groupId,
|
|
2597
2087
|
channelId,
|
|
2598
2088
|
parentId
|
|
@@ -2603,7 +2093,6 @@ async function fetchParentMessageCached(token, groupId, channelId, parentId, dea
|
|
|
2603
2093
|
parentCache.set(key, cached);
|
|
2604
2094
|
return cached.message;
|
|
2605
2095
|
}
|
|
2606
|
-
if (cached) parentCache.delete(key);
|
|
2607
2096
|
const message = await require_reaction_types.fetchChannelMessage(token, groupId, channelId, parentId, deadline);
|
|
2608
2097
|
touchLru(parentCache, key, {
|
|
2609
2098
|
message,
|
|
@@ -2612,20 +2101,14 @@ async function fetchParentMessageCached(token, groupId, channelId, parentId, dea
|
|
|
2612
2101
|
return message;
|
|
2613
2102
|
}
|
|
2614
2103
|
const PARENT_TEXT_MAX_CHARS = 400;
|
|
2615
|
-
function
|
|
2104
|
+
function formatParentContextEvent(message) {
|
|
2616
2105
|
if (!message) return;
|
|
2617
2106
|
const sender = message.from?.user?.displayName ?? message.from?.application?.displayName ?? "unknown";
|
|
2618
2107
|
const contentType = message.body?.contentType ?? "text";
|
|
2619
2108
|
const raw = message.body?.content ?? "";
|
|
2620
2109
|
const text = contentType === "html" ? require_reaction_types.stripHtmlFromTeamsMessage(raw) : raw.replace(/\s+/g, " ").trim();
|
|
2621
2110
|
if (!text) return;
|
|
2622
|
-
return {
|
|
2623
|
-
sender,
|
|
2624
|
-
text: text.length > PARENT_TEXT_MAX_CHARS ? `${(0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(text, 399)}…` : text
|
|
2625
|
-
};
|
|
2626
|
-
}
|
|
2627
|
-
function formatParentContextEvent(summary) {
|
|
2628
|
-
return `Replying to @${summary.sender}: ${summary.text}`;
|
|
2111
|
+
return `Replying to @${sender}: ${text.length > PARENT_TEXT_MAX_CHARS ? `${(0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(text, 399)}…` : text}`;
|
|
2629
2112
|
}
|
|
2630
2113
|
function shouldInjectParentContext(sessionKey, parentId) {
|
|
2631
2114
|
return injectedParents.get(sessionKey) !== parentId;
|
|
@@ -2635,55 +2118,12 @@ function markParentContextInjected(sessionKey, parentId) {
|
|
|
2635
2118
|
}
|
|
2636
2119
|
//#endregion
|
|
2637
2120
|
//#region extensions/msteams/src/monitor-handler/thread-context.ts
|
|
2638
|
-
function prepareMSTeamsThreadRouting(params) {
|
|
2639
|
-
const route = require_runtime.getMSTeamsRuntime().channel.routing.resolveAgentRoute({
|
|
2640
|
-
cfg: params.cfg,
|
|
2641
|
-
channel: "msteams",
|
|
2642
|
-
accountId: params.accountId,
|
|
2643
|
-
teamId: params.teamId,
|
|
2644
|
-
peer: {
|
|
2645
|
-
kind: params.isDirectMessage ? "direct" : params.isChannel ? "channel" : "group",
|
|
2646
|
-
id: params.isDirectMessage ? params.senderId : params.conversationId
|
|
2647
|
-
}
|
|
2648
|
-
});
|
|
2649
|
-
route.sessionKey = require_thread_session.resolveMSTeamsRouteSessionKey({
|
|
2650
|
-
baseSessionKey: route.sessionKey,
|
|
2651
|
-
isChannel: params.isChannel,
|
|
2652
|
-
conversationMessageId: params.conversationMessageId,
|
|
2653
|
-
replyToId: params.context.activity.replyToId
|
|
2654
|
-
});
|
|
2655
|
-
let teamAadGroupId = params.context.activity.channelData?.team?.aadGroupId?.trim() || void 0;
|
|
2656
|
-
const conversationTeamId = params.isChannel ? params.teamId : void 0;
|
|
2657
|
-
let teamGroupIdPromise;
|
|
2658
|
-
const resolveTeamAadGroupId = async (deadline) => {
|
|
2659
|
-
if (!conversationTeamId) return;
|
|
2660
|
-
teamGroupIdPromise ??= resolveTeamGroupId({
|
|
2661
|
-
conversationTeamId,
|
|
2662
|
-
aadGroupId: teamAadGroupId,
|
|
2663
|
-
getTeamDetails: params.context.getTeamDetails,
|
|
2664
|
-
deadline
|
|
2665
|
-
}).catch((err) => {
|
|
2666
|
-
params.log.debug?.("failed to resolve Teams AAD group ID", {
|
|
2667
|
-
teamId: conversationTeamId,
|
|
2668
|
-
error: require_channel_setup.formatUnknownError(err)
|
|
2669
|
-
});
|
|
2670
|
-
});
|
|
2671
|
-
teamAadGroupId = await teamGroupIdPromise;
|
|
2672
|
-
return teamAadGroupId;
|
|
2673
|
-
};
|
|
2674
|
-
return {
|
|
2675
|
-
route,
|
|
2676
|
-
threadRootId: params.conversationMessageId ?? params.context.activity.replyToId,
|
|
2677
|
-
resolveTeamAadGroupId,
|
|
2678
|
-
getTeamAadGroupId: () => teamAadGroupId
|
|
2679
|
-
};
|
|
2680
|
-
}
|
|
2681
2121
|
async function resolveMSTeamsThreadContext(params) {
|
|
2682
2122
|
const core = require_runtime.getMSTeamsRuntime();
|
|
2683
2123
|
const activity = params.context.activity;
|
|
2684
|
-
const { route } = params
|
|
2124
|
+
const { route } = params;
|
|
2685
2125
|
const deadline = require_resolve_allowlist.createMSTeamsInboundEnrichmentDeadline();
|
|
2686
|
-
const teamAadGroupId = await params.
|
|
2126
|
+
const teamAadGroupId = await params.resolveTeamAadGroupId(deadline);
|
|
2687
2127
|
let quoteBodyFull;
|
|
2688
2128
|
let quoteSenderId;
|
|
2689
2129
|
let quoteSenderName;
|
|
@@ -2703,7 +2143,7 @@ async function resolveMSTeamsThreadContext(params) {
|
|
|
2703
2143
|
params.log.debug?.("failed to fetch full quoted message text", { error: require_channel_setup.formatUnknownError(err) });
|
|
2704
2144
|
}
|
|
2705
2145
|
let threadContext = [];
|
|
2706
|
-
const threadParentId = params.
|
|
2146
|
+
const threadParentId = params.threadRootId;
|
|
2707
2147
|
if (threadParentId && threadParentId !== activity.id && params.isChannel && teamAadGroupId) try {
|
|
2708
2148
|
const graphToken = await require_resolve_allowlist.withMSTeamsRequestDeadline({
|
|
2709
2149
|
deadline,
|
|
@@ -2730,29 +2170,22 @@ async function resolveMSTeamsThreadContext(params) {
|
|
|
2730
2170
|
allowNameMatching: params.allowNameMatching
|
|
2731
2171
|
}).allowed
|
|
2732
2172
|
});
|
|
2733
|
-
const
|
|
2734
|
-
const
|
|
2735
|
-
items: [parentMsg],
|
|
2173
|
+
const parentContextEvent = formatParentContextEvent(parentMsg);
|
|
2174
|
+
const { items: threadMessages } = (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
|
|
2175
|
+
items: parentMsg ? [parentMsg, ...replies] : replies,
|
|
2736
2176
|
mode: params.contextVisibilityMode,
|
|
2737
2177
|
kind: "thread",
|
|
2738
2178
|
isSenderAllowed: isThreadSenderAllowed
|
|
2739
|
-
})
|
|
2740
|
-
if (
|
|
2741
|
-
core.system.enqueueSystemEvent(
|
|
2179
|
+
});
|
|
2180
|
+
if (parentContextEvent && parentMsg && threadMessages.includes(parentMsg) && shouldInjectParentContext(route.sessionKey, threadParentId)) {
|
|
2181
|
+
core.system.enqueueSystemEvent(parentContextEvent, {
|
|
2742
2182
|
sessionKey: route.sessionKey,
|
|
2743
2183
|
contextKey: `msteams:thread-parent:${params.conversationId}:${threadParentId}`
|
|
2744
2184
|
});
|
|
2745
2185
|
markParentContextInjected(route.sessionKey, threadParentId);
|
|
2746
2186
|
}
|
|
2747
|
-
|
|
2748
|
-
quoteSenderId = parentMsg?.from?.user?.id ?? parentMsg?.from?.application?.id ?? void 0;
|
|
2187
|
+
quoteSenderId = parentMsg?.from?.user?.id ?? parentMsg?.from?.application?.id;
|
|
2749
2188
|
quoteSenderName = parentMsg?.from?.user?.displayName ?? parentMsg?.from?.application?.displayName ?? params.quoteInfo?.sender;
|
|
2750
|
-
const { items: threadMessages } = (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
|
|
2751
|
-
items: allMessages,
|
|
2752
|
-
mode: params.contextVisibilityMode,
|
|
2753
|
-
kind: "thread",
|
|
2754
|
-
isSenderAllowed: isThreadSenderAllowed
|
|
2755
|
-
});
|
|
2756
2189
|
threadContext = require_reaction_types.buildThreadContext(threadMessages, activity.id);
|
|
2757
2190
|
} catch (err) {
|
|
2758
2191
|
params.log.debug?.("failed to fetch thread history", { error: require_channel_setup.formatUnknownError(err) });
|
|
@@ -2790,17 +2223,8 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2790
2223
|
});
|
|
2791
2224
|
const handleTeamsMessageNow = async (params) => {
|
|
2792
2225
|
const facts = assembleMSTeamsInboundFacts(params);
|
|
2793
|
-
const { context, activity,
|
|
2226
|
+
const { context, activity, text, advertisedMedia, rawBody, quoteInfo, from, htmlSummary, conversationId, conversationMessageId, conversationType, isChannel, teamId, graphChannelId, conversationRef } = facts;
|
|
2794
2227
|
const historyBody = [text, (0, openclaw_plugin_sdk_channel_inbound.formatMediaPlaceholderText)(advertisedMedia)].filter(Boolean).join("\n");
|
|
2795
|
-
log.info("received message", {
|
|
2796
|
-
rawText: (0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(rawText, 50),
|
|
2797
|
-
text: (0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(text, 50),
|
|
2798
|
-
attachments: attachments.length,
|
|
2799
|
-
attachmentTypes,
|
|
2800
|
-
from: from?.id,
|
|
2801
|
-
conversation: conversation?.id
|
|
2802
|
-
});
|
|
2803
|
-
if (htmlSummary) log.debug?.("html attachment summary", htmlSummary);
|
|
2804
2228
|
if (!from?.id) {
|
|
2805
2229
|
log.debug?.("skipping message without from.id");
|
|
2806
2230
|
return;
|
|
@@ -2809,7 +2233,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2809
2233
|
let isBotOwnedThread = false;
|
|
2810
2234
|
if (isChannel && facts.threadId && activity.recipient?.id) {
|
|
2811
2235
|
const channelConfig = currentCfg.channels?.msteams;
|
|
2812
|
-
const routeConfig =
|
|
2236
|
+
const routeConfig = require_presentation.resolveMSTeamsRouteConfig({
|
|
2813
2237
|
cfg: channelConfig,
|
|
2814
2238
|
teamId,
|
|
2815
2239
|
teamName: activity.channelData?.team?.name,
|
|
@@ -2817,7 +2241,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2817
2241
|
channelName: activity.channelData?.channel?.name,
|
|
2818
2242
|
allowNameMatching: channelConfig?.dangerouslyAllowNameMatching === true
|
|
2819
2243
|
});
|
|
2820
|
-
if (
|
|
2244
|
+
if (require_presentation.resolveMSTeamsReplyPolicy({
|
|
2821
2245
|
isDirectMessage: false,
|
|
2822
2246
|
globalConfig: channelConfig,
|
|
2823
2247
|
...routeConfig
|
|
@@ -2846,7 +2270,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2846
2270
|
logVerboseMessage
|
|
2847
2271
|
});
|
|
2848
2272
|
if (!admission) return;
|
|
2849
|
-
const { senderId, senderName, isDirectMessage, channelGate, allowNameMatching, groupPolicy, commandAuthorized, effectiveGroupAllowFrom, allowTextCommands, isControlCommand } = admission;
|
|
2273
|
+
const { senderId, senderName, isDirectMessage, channelGate, allowNameMatching, groupPolicy, commandAuthorized, effectiveGroupAllowFrom, allowTextCommands, isControlCommand, msteamsCfg: admittedMSTeamsCfg } = admission;
|
|
2850
2274
|
const pollVote = require_conversation_store_state.extractMSTeamsPollVote(activity);
|
|
2851
2275
|
if (pollVote) {
|
|
2852
2276
|
try {
|
|
@@ -2855,11 +2279,6 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2855
2279
|
voterId: senderId,
|
|
2856
2280
|
selections: pollVote.selections
|
|
2857
2281
|
})) log.debug?.("poll vote ignored (poll not found)", { pollId: pollVote.pollId });
|
|
2858
|
-
else log.info("recorded poll vote", {
|
|
2859
|
-
pollId: pollVote.pollId,
|
|
2860
|
-
voter: senderId,
|
|
2861
|
-
selections: pollVote.selections
|
|
2862
|
-
});
|
|
2863
2282
|
} catch (err) {
|
|
2864
2283
|
log.error("failed to record poll vote", {
|
|
2865
2284
|
pollId: pollVote.pollId,
|
|
@@ -2868,19 +2287,42 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2868
2287
|
}
|
|
2869
2288
|
return;
|
|
2870
2289
|
}
|
|
2871
|
-
const
|
|
2290
|
+
const route = require_runtime.getMSTeamsRuntime().channel.routing.resolveAgentRoute({
|
|
2872
2291
|
cfg: currentCfg,
|
|
2292
|
+
channel: "msteams",
|
|
2873
2293
|
accountId,
|
|
2874
|
-
context,
|
|
2875
|
-
isDirectMessage,
|
|
2876
|
-
isChannel,
|
|
2877
|
-
senderId,
|
|
2878
|
-
conversationId,
|
|
2879
|
-
conversationMessageId: conversationMessageId ?? void 0,
|
|
2880
2294
|
teamId,
|
|
2881
|
-
|
|
2295
|
+
peer: {
|
|
2296
|
+
kind: isDirectMessage ? "direct" : isChannel ? "channel" : "group",
|
|
2297
|
+
id: isDirectMessage ? senderId : conversationId
|
|
2298
|
+
}
|
|
2882
2299
|
});
|
|
2883
|
-
|
|
2300
|
+
route.sessionKey = require_thread_session.resolveMSTeamsRouteSessionKey({
|
|
2301
|
+
baseSessionKey: route.sessionKey,
|
|
2302
|
+
isChannel,
|
|
2303
|
+
conversationMessageId,
|
|
2304
|
+
replyToId: context.activity.replyToId
|
|
2305
|
+
});
|
|
2306
|
+
let resolvedTeamAadGroupId = context.activity.channelData?.team?.aadGroupId?.trim() || void 0;
|
|
2307
|
+
const conversationTeamId = isChannel ? teamId : void 0;
|
|
2308
|
+
let teamGroupIdPromise;
|
|
2309
|
+
const resolveTeamAadGroupId = async (deadline) => {
|
|
2310
|
+
if (!conversationTeamId) return;
|
|
2311
|
+
teamGroupIdPromise ??= resolveTeamGroupId({
|
|
2312
|
+
conversationTeamId,
|
|
2313
|
+
aadGroupId: resolvedTeamAadGroupId,
|
|
2314
|
+
getTeamDetails: context.getTeamDetails,
|
|
2315
|
+
deadline
|
|
2316
|
+
}).catch((err) => {
|
|
2317
|
+
log.debug?.("failed to resolve Teams AAD group ID", {
|
|
2318
|
+
teamId: conversationTeamId,
|
|
2319
|
+
error: require_channel_setup.formatUnknownError(err)
|
|
2320
|
+
});
|
|
2321
|
+
});
|
|
2322
|
+
resolvedTeamAadGroupId = await teamGroupIdPromise;
|
|
2323
|
+
return resolvedTeamAadGroupId;
|
|
2324
|
+
};
|
|
2325
|
+
const threadRootId = conversationMessageId ?? context.activity.replyToId;
|
|
2884
2326
|
const inboundLabel = isDirectMessage ? `Teams DM from ${senderName}` : `Teams message in ${conversationType} from ${senderName}`;
|
|
2885
2327
|
const enqueuePrimaryMessageSystemEvent = () => core.system.enqueueSystemEvent(inboundLabel, {
|
|
2886
2328
|
sessionKey: route.sessionKey,
|
|
@@ -2888,7 +2330,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2888
2330
|
});
|
|
2889
2331
|
const channelId = conversationId;
|
|
2890
2332
|
const { teamConfig, channelConfig } = channelGate;
|
|
2891
|
-
const replyPolicy =
|
|
2333
|
+
const replyPolicy = require_presentation.resolveMSTeamsReplyPolicy({
|
|
2892
2334
|
isDirectMessage,
|
|
2893
2335
|
globalConfig: currentMSTeamsCfg,
|
|
2894
2336
|
teamConfig,
|
|
@@ -2934,7 +2376,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2934
2376
|
sender: senderName,
|
|
2935
2377
|
body: historyBody,
|
|
2936
2378
|
timestamp: timestamp?.getTime(),
|
|
2937
|
-
messageId: activity.id
|
|
2379
|
+
messageId: activity.id
|
|
2938
2380
|
}
|
|
2939
2381
|
});
|
|
2940
2382
|
}
|
|
@@ -2945,12 +2387,12 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2945
2387
|
entry: params,
|
|
2946
2388
|
rawBody,
|
|
2947
2389
|
advertisedMedia,
|
|
2948
|
-
htmlSummary
|
|
2390
|
+
htmlSummary,
|
|
2949
2391
|
conversationType,
|
|
2950
2392
|
conversationId,
|
|
2951
|
-
conversationMessageId
|
|
2952
|
-
teamAadGroupId:
|
|
2953
|
-
resolveTeamAadGroupId
|
|
2393
|
+
conversationMessageId,
|
|
2394
|
+
teamAadGroupId: resolvedTeamAadGroupId,
|
|
2395
|
+
resolveTeamAadGroupId,
|
|
2954
2396
|
mediaMaxBytes,
|
|
2955
2397
|
tokenProvider,
|
|
2956
2398
|
mediaAllowHosts: currentMSTeamsCfg?.mediaAllowHosts,
|
|
@@ -2961,7 +2403,9 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2961
2403
|
if (!content) return;
|
|
2962
2404
|
enqueuePrimaryMessageSystemEvent();
|
|
2963
2405
|
const thread = await resolveMSTeamsThreadContext({
|
|
2964
|
-
|
|
2406
|
+
route,
|
|
2407
|
+
threadRootId,
|
|
2408
|
+
resolveTeamAadGroupId,
|
|
2965
2409
|
context,
|
|
2966
2410
|
tokenProvider,
|
|
2967
2411
|
quoteInfo,
|
|
@@ -2974,29 +2418,219 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2974
2418
|
allowNameMatching,
|
|
2975
2419
|
log
|
|
2976
2420
|
});
|
|
2977
|
-
|
|
2421
|
+
const dispatchAccountPolicyCfg = readFullConfig();
|
|
2422
|
+
const dispatchCore = require_runtime.getMSTeamsRuntime();
|
|
2423
|
+
const { agentBody, inboundMedia } = content;
|
|
2424
|
+
const { teamAadGroupId, quoteBodyFull, quoteSenderId, quoteSenderName, threadContext } = thread;
|
|
2425
|
+
const teamsFrom = isDirectMessage ? `msteams:${senderId}` : isChannel ? `msteams:channel:${conversationId}` : `msteams:group:${conversationId}`;
|
|
2426
|
+
const teamsTo = isDirectMessage ? `user:${senderId}` : `conversation:${conversationId}`;
|
|
2427
|
+
const envelopeFrom = isDirectMessage ? senderName : conversationType;
|
|
2428
|
+
const buildEnvelope = await (0, openclaw_plugin_sdk_channel_inbound.createChannelInboundEnvelopeBuilderAsync)({
|
|
2978
2429
|
cfg: currentCfg,
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
tokenProvider,
|
|
2985
|
-
textLimit,
|
|
2986
|
-
log,
|
|
2987
|
-
logVerboseMessage,
|
|
2988
|
-
facts,
|
|
2989
|
-
admission,
|
|
2990
|
-
content,
|
|
2991
|
-
routing: threadRouting,
|
|
2992
|
-
thread,
|
|
2993
|
-
replyStyle,
|
|
2430
|
+
route
|
|
2431
|
+
});
|
|
2432
|
+
let combinedBody = buildEnvelope({
|
|
2433
|
+
channel: "Teams",
|
|
2434
|
+
from: envelopeFrom,
|
|
2994
2435
|
timestamp,
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2436
|
+
body: agentBody
|
|
2437
|
+
});
|
|
2438
|
+
const isRoomish = !isDirectMessage;
|
|
2439
|
+
const historyKey = isRoomish ? facts.historyKey : void 0;
|
|
2440
|
+
if (isRoomish && historyKey) combinedBody = (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildPendingContext({
|
|
2441
|
+
historyKey,
|
|
2442
|
+
limit: historyLimit,
|
|
2443
|
+
currentMessage: combinedBody,
|
|
2444
|
+
formatEntry: (entry) => buildEnvelope({
|
|
2445
|
+
channel: "Teams",
|
|
2446
|
+
from: conversationType,
|
|
2447
|
+
timestamp: entry.timestamp,
|
|
2448
|
+
previousTimestamp: null,
|
|
2449
|
+
body: `${entry.sender}: ${entry.body}${entry.messageId ? ` [id:${entry.messageId}]` : ""}`
|
|
2450
|
+
})
|
|
2451
|
+
});
|
|
2452
|
+
const inboundHistory = isRoomish && historyKey && historyLimit > 0 ? (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildInboundHistory({
|
|
2453
|
+
historyKey,
|
|
2454
|
+
limit: historyLimit
|
|
2455
|
+
}) : void 0;
|
|
2456
|
+
const commandBody = text.trim();
|
|
2457
|
+
const quoteSenderAllowed = quoteInfo && quoteInfo.sender ? (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
|
|
2458
|
+
isGroup: !isDirectMessage,
|
|
2459
|
+
groupPolicy,
|
|
2460
|
+
allowFrom: effectiveGroupAllowFrom,
|
|
2461
|
+
isSenderAllowed: (allowFrom) => (0, openclaw_plugin_sdk_allow_from.resolveAllowlistMatchSimple)({
|
|
2462
|
+
allowFrom,
|
|
2463
|
+
senderId: quoteSenderId ?? "",
|
|
2464
|
+
senderName: quoteSenderName,
|
|
2465
|
+
allowNameMatching
|
|
2466
|
+
}).allowed
|
|
2467
|
+
}) : true;
|
|
2468
|
+
const nativeChannelId = isChannel && teamAadGroupId ? `${teamAadGroupId}/${graphChannelId}` : void 0;
|
|
2469
|
+
const boundIngress = await resolveMSTeamsSenderAccess({
|
|
2470
|
+
cfg: route.accountId === accountId ? readConfig() : dispatchAccountPolicyCfg,
|
|
2471
|
+
accountId: route.accountId,
|
|
2472
|
+
activity,
|
|
2473
|
+
hasControlCommand: admission.isControlCommand,
|
|
2474
|
+
conversationThreadId: facts.threadId,
|
|
2475
|
+
contextBinding: {
|
|
2476
|
+
agentId: route.agentId,
|
|
2477
|
+
sessionKey: route.sessionKey,
|
|
2478
|
+
...activity.id ? { messageId: activity.id } : {},
|
|
2479
|
+
...nativeChannelId ? { nativeChannelId } : {},
|
|
2480
|
+
inboundEventKind: "user_request"
|
|
2481
|
+
}
|
|
2482
|
+
});
|
|
2483
|
+
if (!boundIngress.senderAccess.allowed || boundIngress.hasConflictingConversationScope || boundIngress.msteamsCfg?.enabled === false || boundIngress.commandAccess.shouldBlockControlCommand) {
|
|
2484
|
+
log.info("dropping message (dispatch-time account authorization changed)");
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
const ctxPayload = dispatchCore.channel.inbound.buildContext({
|
|
2488
|
+
channelIngress: boundIngress.channelIngress,
|
|
2489
|
+
channel: "msteams",
|
|
2490
|
+
contextVisibility: contextVisibilityMode,
|
|
2491
|
+
supplemental: {
|
|
2492
|
+
channelStructuredContext: threadContext.length ? [{
|
|
2493
|
+
label: "Thread history",
|
|
2494
|
+
source: "msteams",
|
|
2495
|
+
type: "chat_window",
|
|
2496
|
+
sessionTranscriptMode: "preserve",
|
|
2497
|
+
payload: {
|
|
2498
|
+
order: "chronological",
|
|
2499
|
+
messages: threadContext
|
|
2500
|
+
}
|
|
2501
|
+
}] : void 0,
|
|
2502
|
+
quote: quoteInfo ? {
|
|
2503
|
+
id: quoteInfo.id ?? activity.replyToId,
|
|
2504
|
+
body: quoteBodyFull ?? quoteInfo.body,
|
|
2505
|
+
sender: quoteInfo.sender,
|
|
2506
|
+
senderAllowed: quoteSenderAllowed,
|
|
2507
|
+
isQuote: true
|
|
2508
|
+
} : void 0
|
|
2509
|
+
},
|
|
2510
|
+
media: await (0, openclaw_plugin_sdk_channel_inbound.toInboundMediaFactsWithMetadata)(inboundMedia),
|
|
2511
|
+
messageId: activity.id,
|
|
2512
|
+
timestamp: timestamp?.getTime() ?? Date.now(),
|
|
2513
|
+
from: teamsFrom,
|
|
2514
|
+
sender: {
|
|
2515
|
+
id: senderId,
|
|
2516
|
+
name: senderName
|
|
2517
|
+
},
|
|
2518
|
+
conversation: {
|
|
2519
|
+
kind: isDirectMessage ? "direct" : isChannel ? "channel" : "group",
|
|
2520
|
+
id: conversationId,
|
|
2521
|
+
label: envelopeFrom,
|
|
2522
|
+
spaceId: teamId,
|
|
2523
|
+
nativeChannelId
|
|
2524
|
+
},
|
|
2525
|
+
route: {
|
|
2526
|
+
agentId: route.agentId,
|
|
2527
|
+
dmScope: route.dmScope,
|
|
2528
|
+
accountId: route.accountId,
|
|
2529
|
+
routeSessionKey: route.sessionKey
|
|
2530
|
+
},
|
|
2531
|
+
reply: {
|
|
2532
|
+
to: teamsTo,
|
|
2533
|
+
originatingTo: `conversation:${conversationId}`,
|
|
2534
|
+
messageThreadId: facts.threadId,
|
|
2535
|
+
replyToId: activity.replyToId,
|
|
2536
|
+
nativeChannelId
|
|
2537
|
+
},
|
|
2538
|
+
message: {
|
|
2539
|
+
body: combinedBody,
|
|
2540
|
+
bodyForAgent: agentBody,
|
|
2541
|
+
inboundHistory,
|
|
2542
|
+
rawBody,
|
|
2543
|
+
commandBody
|
|
2544
|
+
},
|
|
2545
|
+
sessionTranscript: { historyLimit: isRoomish ? historyLimit : 0 },
|
|
2546
|
+
access: {
|
|
2547
|
+
mentions: {
|
|
2548
|
+
canDetectMention: !isDirectMessage,
|
|
2549
|
+
wasMentioned: isDirectMessage || mentionDecision.effectiveWasMentioned
|
|
2550
|
+
},
|
|
2551
|
+
commands: { authorized: commandAuthorized === true }
|
|
2552
|
+
},
|
|
2553
|
+
extra: {
|
|
2554
|
+
GroupSubject: !isDirectMessage ? conversationType : void 0,
|
|
2555
|
+
ReplyToIsQuote: quoteInfo ? true : void 0
|
|
2556
|
+
}
|
|
2999
2557
|
});
|
|
2558
|
+
const { dispatcherOptions, delivery, replyOptions } = createMSTeamsReplyDispatcher({
|
|
2559
|
+
cfg: currentCfg,
|
|
2560
|
+
agentId: route.agentId,
|
|
2561
|
+
sessionKey: route.sessionKey,
|
|
2562
|
+
accountId: route.accountId,
|
|
2563
|
+
log,
|
|
2564
|
+
app,
|
|
2565
|
+
conversationRef,
|
|
2566
|
+
context,
|
|
2567
|
+
replyStyle,
|
|
2568
|
+
textLimit,
|
|
2569
|
+
onSentMessageIds: (ids) => {
|
|
2570
|
+
for (const id of ids) require_conversation_store_state.recordMSTeamsSentMessage(conversationId, id, { accountId: route.accountId });
|
|
2571
|
+
},
|
|
2572
|
+
tokenProvider,
|
|
2573
|
+
sharePointSiteId: admittedMSTeamsCfg?.sharePointSiteId
|
|
2574
|
+
});
|
|
2575
|
+
const senderTimezone = conversationRef.timezone;
|
|
2576
|
+
const turnConfig = senderTimezone && !currentCfg.agents?.defaults?.userTimezone ? {
|
|
2577
|
+
...currentCfg,
|
|
2578
|
+
agents: {
|
|
2579
|
+
...currentCfg.agents,
|
|
2580
|
+
defaults: {
|
|
2581
|
+
...currentCfg.agents?.defaults,
|
|
2582
|
+
userTimezone: senderTimezone
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
} : currentCfg;
|
|
2586
|
+
try {
|
|
2587
|
+
await dispatchCore.channel.inbound.run({
|
|
2588
|
+
channel: "msteams",
|
|
2589
|
+
accountId: route.accountId,
|
|
2590
|
+
raw: context,
|
|
2591
|
+
adapter: {
|
|
2592
|
+
ingest: () => ({
|
|
2593
|
+
id: activity.id ?? `${teamsFrom}:${Date.now()}`,
|
|
2594
|
+
timestamp: timestamp?.getTime(),
|
|
2595
|
+
rawText: rawBody,
|
|
2596
|
+
textForAgent: agentBody,
|
|
2597
|
+
textForCommands: commandBody,
|
|
2598
|
+
raw: activity
|
|
2599
|
+
}),
|
|
2600
|
+
resolveTurn: () => ({
|
|
2601
|
+
cfg: turnConfig,
|
|
2602
|
+
channel: "msteams",
|
|
2603
|
+
accountId: route.accountId,
|
|
2604
|
+
route: {
|
|
2605
|
+
agentId: route.agentId,
|
|
2606
|
+
sessionKey: route.sessionKey
|
|
2607
|
+
},
|
|
2608
|
+
ctxPayload,
|
|
2609
|
+
record: { onRecordError: (err) => {
|
|
2610
|
+
logVerboseMessage(`msteams: failed updating session meta: ${require_channel_setup.formatUnknownError(err)}`);
|
|
2611
|
+
} },
|
|
2612
|
+
history: {
|
|
2613
|
+
isGroup: isRoomish,
|
|
2614
|
+
historyKey,
|
|
2615
|
+
historyMap: conversationHistories,
|
|
2616
|
+
limit: historyLimit
|
|
2617
|
+
},
|
|
2618
|
+
dispatcherOptions,
|
|
2619
|
+
delivery,
|
|
2620
|
+
replyOptions: {
|
|
2621
|
+
...replyOptions,
|
|
2622
|
+
...facts.turnAdoptionLifecycle ? (0, openclaw_plugin_sdk_channel_outbound.bindIngressLifecycleToReplyOptions)(facts.turnAdoptionLifecycle) : {}
|
|
2623
|
+
}
|
|
2624
|
+
})
|
|
2625
|
+
}
|
|
2626
|
+
});
|
|
2627
|
+
} catch (err) {
|
|
2628
|
+
runtime.error(`msteams dispatch failed: ${require_channel_setup.formatUnknownError(err)}`);
|
|
2629
|
+
if (facts.turnAdoptionLifecycle) throw err;
|
|
2630
|
+
try {
|
|
2631
|
+
await context.sendActivity("⚠️ Something went wrong. Please try again.");
|
|
2632
|
+
} catch {}
|
|
2633
|
+
}
|
|
3000
2634
|
};
|
|
3001
2635
|
const inboundDebouncer = core.channel.debounce.createInboundDebouncer({
|
|
3002
2636
|
debounceMs: resolveDebounceMs(),
|
|
@@ -3148,13 +2782,6 @@ function createMSTeamsReactionHandler(deps) {
|
|
|
3148
2782
|
const reactionType = reaction.type ?? "unknown";
|
|
3149
2783
|
const emoji = require_reaction_types.resolveMSTeamsReactionEmoji(reactionType);
|
|
3150
2784
|
const label = direction === "added" ? `Teams reaction ${emoji} added by ${senderName} on message ${targetMessageId}` : `Teams reaction ${emoji} removed by ${senderName} from message ${targetMessageId}`;
|
|
3151
|
-
log.info(`reaction ${direction}`, {
|
|
3152
|
-
sender: senderId,
|
|
3153
|
-
reactionType,
|
|
3154
|
-
emoji,
|
|
3155
|
-
targetMessageId,
|
|
3156
|
-
conversationId
|
|
3157
|
-
});
|
|
3158
2785
|
core.system.enqueueSystemEvent(label, {
|
|
3159
2786
|
sessionKey,
|
|
3160
2787
|
contextKey: `msteams:reaction:${conversationId}:${targetMessageId}:${senderId}:${reactionType}:${direction}`
|
|
@@ -3163,44 +2790,12 @@ function createMSTeamsReactionHandler(deps) {
|
|
|
3163
2790
|
};
|
|
3164
2791
|
}
|
|
3165
2792
|
//#endregion
|
|
3166
|
-
//#region extensions/msteams/src/
|
|
2793
|
+
//#region extensions/msteams/src/monitor-handler.ts
|
|
3167
2794
|
const DEFAULT_PROMPT_STARTERS = [
|
|
3168
2795
|
"What can you do?",
|
|
3169
2796
|
"Summarize my last meeting",
|
|
3170
2797
|
"Help me draft an email"
|
|
3171
2798
|
];
|
|
3172
|
-
function buildWelcomeCard(options) {
|
|
3173
|
-
const botName = options?.botName || "OpenClaw";
|
|
3174
|
-
const starters = options?.promptStarters?.length ? options.promptStarters : DEFAULT_PROMPT_STARTERS;
|
|
3175
|
-
return {
|
|
3176
|
-
type: "AdaptiveCard",
|
|
3177
|
-
version: "1.5",
|
|
3178
|
-
body: [{
|
|
3179
|
-
type: "TextBlock",
|
|
3180
|
-
text: `Hi! I'm ${botName}.`,
|
|
3181
|
-
weight: "Bolder",
|
|
3182
|
-
size: "Medium"
|
|
3183
|
-
}, {
|
|
3184
|
-
type: "TextBlock",
|
|
3185
|
-
text: "I can help you with questions, tasks, and more. Here are some things to try:",
|
|
3186
|
-
wrap: true
|
|
3187
|
-
}],
|
|
3188
|
-
actions: starters.map((label) => ({
|
|
3189
|
-
type: "Action.Submit",
|
|
3190
|
-
title: label,
|
|
3191
|
-
data: { msteams: {
|
|
3192
|
-
type: "imBack",
|
|
3193
|
-
value: label
|
|
3194
|
-
} }
|
|
3195
|
-
}))
|
|
3196
|
-
};
|
|
3197
|
-
}
|
|
3198
|
-
function buildGroupWelcomeText(botName) {
|
|
3199
|
-
const name = botName || "OpenClaw";
|
|
3200
|
-
return `Hi! I'm ${name}. Mention me with @${name} to get started.`;
|
|
3201
|
-
}
|
|
3202
|
-
//#endregion
|
|
3203
|
-
//#region extensions/msteams/src/monitor-handler.ts
|
|
3204
2799
|
async function isMSTeamsInvokeAuthorized(params) {
|
|
3205
2800
|
const { context, deps, invokeKind } = params;
|
|
3206
2801
|
const cfg = deps.readConfig?.() ?? deps.cfg;
|
|
@@ -3258,10 +2853,30 @@ function createMSTeamsActivityHandler(deps) {
|
|
|
3258
2853
|
for (const member of membersAdded) if (member.id === botId) {
|
|
3259
2854
|
const isPersonal = ((0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(ctx.activity?.conversation?.conversationType) ?? "personal") === "personal";
|
|
3260
2855
|
if (isPersonal && msteamsCfg?.welcomeCard !== false) {
|
|
3261
|
-
const
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
2856
|
+
const botName = ctx.activity?.recipient?.name || "OpenClaw";
|
|
2857
|
+
const starters = msteamsCfg?.promptStarters?.length ? msteamsCfg.promptStarters : DEFAULT_PROMPT_STARTERS;
|
|
2858
|
+
const card = {
|
|
2859
|
+
type: "AdaptiveCard",
|
|
2860
|
+
version: "1.5",
|
|
2861
|
+
body: [{
|
|
2862
|
+
type: "TextBlock",
|
|
2863
|
+
text: `Hi! I'm ${botName}.`,
|
|
2864
|
+
weight: "Bolder",
|
|
2865
|
+
size: "Medium"
|
|
2866
|
+
}, {
|
|
2867
|
+
type: "TextBlock",
|
|
2868
|
+
text: "I can help you with questions, tasks, and more. Here are some things to try:",
|
|
2869
|
+
wrap: true
|
|
2870
|
+
}],
|
|
2871
|
+
actions: starters.map((label) => ({
|
|
2872
|
+
type: "Action.Submit",
|
|
2873
|
+
title: label,
|
|
2874
|
+
data: { msteams: {
|
|
2875
|
+
type: "imBack",
|
|
2876
|
+
value: label
|
|
2877
|
+
} }
|
|
2878
|
+
}))
|
|
2879
|
+
};
|
|
3265
2880
|
try {
|
|
3266
2881
|
await ctx.sendActivity(require_conversation_store_state.buildMSTeamsAdaptiveCardActivity(card));
|
|
3267
2882
|
deps.log.info("sent welcome card");
|
|
@@ -3269,9 +2884,9 @@ function createMSTeamsActivityHandler(deps) {
|
|
|
3269
2884
|
deps.log.debug?.("failed to send welcome card", { error: require_channel_setup.formatUnknownError(err) });
|
|
3270
2885
|
}
|
|
3271
2886
|
} else if (!isPersonal && msteamsCfg?.groupWelcomeCard === true) {
|
|
3272
|
-
const botName = ctx.activity?.recipient?.name
|
|
2887
|
+
const botName = ctx.activity?.recipient?.name || "OpenClaw";
|
|
3273
2888
|
try {
|
|
3274
|
-
await ctx.sendActivity(
|
|
2889
|
+
await ctx.sendActivity(`Hi! I'm ${botName}. Mention me with @${botName} to get started.`);
|
|
3275
2890
|
deps.log.info("sent group welcome message");
|
|
3276
2891
|
} catch (err) {
|
|
3277
2892
|
deps.log.debug?.("failed to send group welcome", { error: require_channel_setup.formatUnknownError(err) });
|
|
@@ -3363,12 +2978,12 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
|
|
|
3363
2978
|
id: isDirectMessage ? senderId : conversationId
|
|
3364
2979
|
}
|
|
3365
2980
|
});
|
|
3366
|
-
|
|
3367
|
-
if (feedbackThreadId) route.sessionKey = (0, openclaw_plugin_sdk_routing.resolveThreadSessionKeys)({
|
|
2981
|
+
route.sessionKey = require_thread_session.resolveMSTeamsRouteSessionKey({
|
|
3368
2982
|
baseSessionKey: route.sessionKey,
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
2983
|
+
isChannel,
|
|
2984
|
+
conversationMessageId: require_resolve_allowlist.extractMSTeamsConversationMessageId(rawConversationId),
|
|
2985
|
+
replyToId: activity.replyToId
|
|
2986
|
+
});
|
|
3372
2987
|
const feedbackEvent = {
|
|
3373
2988
|
type: "custom",
|
|
3374
2989
|
event: "feedback",
|
|
@@ -3414,58 +3029,95 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
|
|
|
3414
3029
|
serviceUrl: activity.serviceUrl,
|
|
3415
3030
|
locale: activity.locale
|
|
3416
3031
|
};
|
|
3417
|
-
if (isNegative && msteamsCfg?.feedbackReflection !== false)
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3032
|
+
if (isNegative && msteamsCfg?.feedbackReflection !== false) {
|
|
3033
|
+
const { app, accountId, log } = deps;
|
|
3034
|
+
const { sessionKey, agentId } = route;
|
|
3035
|
+
const conversationKind = isDirectMessage ? "direct" : isChannel ? "channel" : "group";
|
|
3036
|
+
(async () => {
|
|
3037
|
+
const cooldownMs = msteamsCfg.feedbackReflectionCooldownMs ?? openclaw_plugin_sdk_channel_inbound.DEFAULT_CHANNEL_FEEDBACK_REFLECTION_COOLDOWN_MS;
|
|
3038
|
+
let reflection;
|
|
3039
|
+
try {
|
|
3040
|
+
reflection = await (0, openclaw_plugin_sdk_channel_inbound.runChannelFeedbackReflection)({
|
|
3041
|
+
cfg,
|
|
3042
|
+
channel: "msteams",
|
|
3043
|
+
channelLabel: "Teams",
|
|
3044
|
+
agentId,
|
|
3045
|
+
sessionKey,
|
|
3046
|
+
conversationId,
|
|
3047
|
+
conversationKind,
|
|
3048
|
+
userComment,
|
|
3049
|
+
cooldownMs,
|
|
3050
|
+
onRecordError: (err) => log.debug?.("reflection session record failed", { error: require_channel_setup.formatUnknownError(err) }),
|
|
3051
|
+
onDispatchError: (err) => log.debug?.("reflection reply error", { error: require_channel_setup.formatUnknownError(err) })
|
|
3052
|
+
});
|
|
3053
|
+
} catch (err) {
|
|
3054
|
+
log.error("reflection dispatch failed", { error: require_channel_setup.formatUnknownError(err) });
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
if (reflection.status === "cooldown") {
|
|
3058
|
+
log.debug?.("skipping reflection (cooldown active)", { sessionKey });
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
if (reflection.status === "empty") {
|
|
3062
|
+
log.debug?.("reflection produced no output");
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
if (!(convType === "personal" && reflection.followUp && Boolean(reflection.userMessage))) {
|
|
3066
|
+
if (reflection.followUp && convType !== "personal") log.debug?.("skipping reflection follow-up outside direct message", {
|
|
3067
|
+
sessionKey,
|
|
3068
|
+
conversationType: convType
|
|
3069
|
+
});
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
try {
|
|
3073
|
+
await require_conversation_store_state.sendMSTeamsActivityWithReference(app, require_conversation_store_state.buildConversationReference(conversationRef), {
|
|
3074
|
+
type: "message",
|
|
3075
|
+
text: reflection.userMessage
|
|
3076
|
+
}, {
|
|
3077
|
+
accountId,
|
|
3078
|
+
serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(msteamsCfg)
|
|
3079
|
+
});
|
|
3080
|
+
} catch (err) {
|
|
3081
|
+
log.debug?.("failed to send reflection follow-up", { error: require_channel_setup.formatUnknownError(err) });
|
|
3082
|
+
}
|
|
3083
|
+
})().catch((err) => {
|
|
3084
|
+
deps.log.error("feedback reflection failed", { error: require_channel_setup.formatUnknownError(err) });
|
|
3085
|
+
});
|
|
3086
|
+
}
|
|
3431
3087
|
return true;
|
|
3432
3088
|
}
|
|
3433
3089
|
//#endregion
|
|
3434
3090
|
//#region extensions/msteams/src/file-consent-invoke.ts
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
const inMemoryFile = require_conversation_store_state.getPendingUpload(uploadId);
|
|
3446
|
-
const fsFile = inMemoryFile ? void 0 : await require_conversation_store_state.getPendingUploadFs(uploadId);
|
|
3447
|
-
const pendingFile = inMemoryFile ?? fsFile;
|
|
3448
|
-
if (pendingFile) {
|
|
3449
|
-
const pendingConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(pendingFile.conversationId);
|
|
3450
|
-
const invokeConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
3451
|
-
if (!invokeConversationId || pendingConversationId !== invokeConversationId) {
|
|
3452
|
-
log.info("file consent conversation mismatch", {
|
|
3453
|
-
uploadId,
|
|
3454
|
-
expectedConversationId: pendingConversationId,
|
|
3455
|
-
receivedConversationId: invokeConversationId || void 0
|
|
3456
|
-
});
|
|
3457
|
-
if (consentResponse.action === "accept") await context.sendActivity(expiredUploadMessage);
|
|
3458
|
-
return true;
|
|
3091
|
+
/** Run delayed upload/update work after the SDK route acknowledges the invoke. */
|
|
3092
|
+
async function runMSTeamsFileConsentInvokeHandler(context, log) {
|
|
3093
|
+
try {
|
|
3094
|
+
const expiredUploadMessage = "The file upload request has expired. Please try sending the file again.";
|
|
3095
|
+
const activity = context.activity;
|
|
3096
|
+
if (activity.type !== "invoke" || activity.name !== "fileConsent/invoke") return;
|
|
3097
|
+
const consentResponse = require_conversation_store_state.parseFileConsentInvoke(activity);
|
|
3098
|
+
if (!consentResponse) {
|
|
3099
|
+
log.debug?.("invalid file consent invoke");
|
|
3100
|
+
return;
|
|
3459
3101
|
}
|
|
3460
|
-
|
|
3461
|
-
|
|
3102
|
+
const uploadId = typeof consentResponse.context?.uploadId === "string" ? consentResponse.context.uploadId : void 0;
|
|
3103
|
+
const inMemoryFile = require_conversation_store_state.getPendingUpload(uploadId);
|
|
3104
|
+
const fsFile = inMemoryFile ? void 0 : await require_conversation_store_state.getPendingUploadFs(uploadId);
|
|
3105
|
+
const pendingFile = inMemoryFile ?? fsFile;
|
|
3462
3106
|
if (pendingFile) {
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3107
|
+
const pendingConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(pendingFile.conversationId);
|
|
3108
|
+
const invokeConversationId = require_resolve_allowlist.normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
3109
|
+
if (!invokeConversationId || pendingConversationId !== invokeConversationId) {
|
|
3110
|
+
log.info("file consent conversation mismatch", {
|
|
3111
|
+
uploadId,
|
|
3112
|
+
expectedConversationId: pendingConversationId,
|
|
3113
|
+
receivedConversationId: invokeConversationId || void 0
|
|
3114
|
+
});
|
|
3115
|
+
if (consentResponse.action === "accept") await context.sendActivity(expiredUploadMessage);
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
if (consentResponse.action === "accept" && consentResponse.uploadInfo) {
|
|
3120
|
+
if (pendingFile) try {
|
|
3469
3121
|
await require_conversation_store_state.uploadToConsentUrl({
|
|
3470
3122
|
url: consentResponse.uploadInfo.uploadUrl,
|
|
3471
3123
|
buffer: pendingFile.buffer,
|
|
@@ -3489,11 +3141,6 @@ async function handleMSTeamsFileConsentInvoke(context, log) {
|
|
|
3489
3141
|
await context.sendActivity(fileActivity);
|
|
3490
3142
|
}
|
|
3491
3143
|
else await context.sendActivity(fileActivity);
|
|
3492
|
-
log.info("file upload complete", {
|
|
3493
|
-
uploadId,
|
|
3494
|
-
filename: consentResponse.uploadInfo.name,
|
|
3495
|
-
uniqueId: consentResponse.uploadInfo.uniqueId
|
|
3496
|
-
});
|
|
3497
3144
|
} catch (err) {
|
|
3498
3145
|
log.error("file upload failed", {
|
|
3499
3146
|
uploadId,
|
|
@@ -3504,55 +3151,20 @@ async function handleMSTeamsFileConsentInvoke(context, log) {
|
|
|
3504
3151
|
require_conversation_store_state.removePendingUpload(uploadId);
|
|
3505
3152
|
await require_conversation_store_state.removePendingUploadFs(uploadId);
|
|
3506
3153
|
}
|
|
3154
|
+
else {
|
|
3155
|
+
log.debug?.("pending file not found for consent", { uploadId });
|
|
3156
|
+
await context.sendActivity(expiredUploadMessage);
|
|
3157
|
+
}
|
|
3507
3158
|
} else {
|
|
3508
|
-
log.debug?.("
|
|
3509
|
-
|
|
3159
|
+
log.debug?.("user declined file consent", { uploadId });
|
|
3160
|
+
require_conversation_store_state.removePendingUpload(uploadId);
|
|
3161
|
+
await require_conversation_store_state.removePendingUploadFs(uploadId);
|
|
3510
3162
|
}
|
|
3511
|
-
} else {
|
|
3512
|
-
log.debug?.("user declined file consent", { uploadId });
|
|
3513
|
-
require_conversation_store_state.removePendingUpload(uploadId);
|
|
3514
|
-
await require_conversation_store_state.removePendingUploadFs(uploadId);
|
|
3515
|
-
}
|
|
3516
|
-
return true;
|
|
3517
|
-
}
|
|
3518
|
-
/**
|
|
3519
|
-
* Run the file-consent invoke handler after the SDK route has acknowledged the
|
|
3520
|
-
* invoke. This intentionally does not send its own invokeResponse; it only does
|
|
3521
|
-
* the delayed upload/update work.
|
|
3522
|
-
*/
|
|
3523
|
-
async function runMSTeamsFileConsentInvokeHandler(context, log) {
|
|
3524
|
-
try {
|
|
3525
|
-
await require_conversation_store_state.withRevokedProxyFallback({
|
|
3526
|
-
run: () => handleMSTeamsFileConsentInvoke(context, log),
|
|
3527
|
-
onRevoked: async () => true,
|
|
3528
|
-
onRevokedLog: () => {
|
|
3529
|
-
log.debug?.("turn context revoked during file consent invoke; skipping delayed response");
|
|
3530
|
-
}
|
|
3531
|
-
});
|
|
3532
3163
|
} catch (err) {
|
|
3533
3164
|
log.debug?.("file consent handler error", { error: require_channel_setup.formatUnknownError(err) });
|
|
3534
3165
|
}
|
|
3535
3166
|
}
|
|
3536
3167
|
//#endregion
|
|
3537
|
-
//#region extensions/msteams/src/monitor-account-stores.ts
|
|
3538
|
-
function createMSTeamsMonitorStores(accountId, overrides) {
|
|
3539
|
-
return {
|
|
3540
|
-
conversationStore: overrides.conversationStore ?? require_conversation_store_state.createMSTeamsConversationStoreState({ accountId }),
|
|
3541
|
-
pollStore: overrides.pollStore ?? require_conversation_store_state.createMSTeamsPollStoreState({ accountId })
|
|
3542
|
-
};
|
|
3543
|
-
}
|
|
3544
|
-
//#endregion
|
|
3545
|
-
//#region extensions/msteams/src/monitor-status.ts
|
|
3546
|
-
function publishMSTeamsBlocked(statusSink, lastError) {
|
|
3547
|
-
statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelBlockedPatch)(lastError, { running: true }));
|
|
3548
|
-
}
|
|
3549
|
-
function publishMSTeamsReady(statusSink, now = Date.now()) {
|
|
3550
|
-
statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelReadyPatch)({ lastConnectedAt: now }));
|
|
3551
|
-
}
|
|
3552
|
-
function publishMSTeamsStopped(statusSink) {
|
|
3553
|
-
statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelStoppedPatch)());
|
|
3554
|
-
}
|
|
3555
|
-
//#endregion
|
|
3556
3168
|
//#region extensions/msteams/src/msteams-ingress.ts
|
|
3557
3169
|
const MSTEAMS_INGRESS_VERSION = 1;
|
|
3558
3170
|
const MSTEAMS_INGRESS_DRAIN_INTERVAL_MS = 500;
|
|
@@ -3717,17 +3329,9 @@ function createMSTeamsReplayContext(activity, app, serviceUrlBoundary, accountId
|
|
|
3717
3329
|
return {
|
|
3718
3330
|
activity,
|
|
3719
3331
|
sendActivity,
|
|
3720
|
-
sendActivities: async (activities) => {
|
|
3721
|
-
const results = [];
|
|
3722
|
-
for (const outbound of activities) results.push(await sendActivity(outbound));
|
|
3723
|
-
return results;
|
|
3724
|
-
},
|
|
3725
3332
|
updateActivity: async (outbound) => {
|
|
3726
3333
|
return await require_conversation_store_state.updateMSTeamsActivityWithReference(app, reference, typeof outbound.id === "string" ? outbound.id : "", outbound, proactiveOptions);
|
|
3727
3334
|
},
|
|
3728
|
-
deleteActivity: async (activityId) => {
|
|
3729
|
-
await require_conversation_store_state.deleteMSTeamsActivityWithReference(app, reference, activityId, proactiveOptions);
|
|
3730
|
-
},
|
|
3731
3335
|
getTeamDetails: (teamId) => app.api.teams.getById(teamId)
|
|
3732
3336
|
};
|
|
3733
3337
|
}
|
|
@@ -3744,7 +3348,7 @@ const MSTEAMS_SSO_TOKENS_NAMESPACE = "sso-tokens";
|
|
|
3744
3348
|
const MSTEAMS_MAX_SSO_TOKENS = 5e3;
|
|
3745
3349
|
const STORE_KEY_VERSION_PREFIX = "v2:";
|
|
3746
3350
|
function makeMSTeamsSsoTokenStoreKey(connectionName, userId) {
|
|
3747
|
-
return `${STORE_KEY_VERSION_PREFIX}${(0,
|
|
3351
|
+
return `${STORE_KEY_VERSION_PREFIX}${(0, openclaw_plugin_sdk_node_crypto_runtime.sha256Hex)(JSON.stringify([connectionName, userId]))}`;
|
|
3748
3352
|
}
|
|
3749
3353
|
function createMSTeamsSsoTokenStoreFs(params) {
|
|
3750
3354
|
const namespace = require_delegated_state.resolveMSTeamsAccountStateNamespace(MSTEAMS_SSO_TOKENS_NAMESPACE, params?.accountId);
|
|
@@ -3757,12 +3361,10 @@ function createMSTeamsSsoTokenStoreFs(params) {
|
|
|
3757
3361
|
return await tokenStore.lookup(makeMSTeamsSsoTokenStoreKey(connectionName, userId)) ?? null;
|
|
3758
3362
|
},
|
|
3759
3363
|
async save(token) {
|
|
3760
|
-
await
|
|
3761
|
-
await tokenStore.register(makeMSTeamsSsoTokenStoreKey(token.connectionName, token.userId), require_delegated_state.toPluginJsonValue({ ...token }));
|
|
3762
|
-
});
|
|
3364
|
+
await tokenStore.register(makeMSTeamsSsoTokenStoreKey(token.connectionName, token.userId), require_delegated_state.toPluginJsonValue(token));
|
|
3763
3365
|
},
|
|
3764
3366
|
async remove({ connectionName, userId }) {
|
|
3765
|
-
return
|
|
3367
|
+
return tokenStore.delete(makeMSTeamsSsoTokenStoreKey(connectionName, userId));
|
|
3766
3368
|
}
|
|
3767
3369
|
};
|
|
3768
3370
|
}
|
|
@@ -3772,7 +3374,6 @@ function createMSTeamsSsoTokenStoreFs(params) {
|
|
|
3772
3374
|
function createMSTeamsWebhookHandler(expressApp, warn) {
|
|
3773
3375
|
const responses = /* @__PURE__ */ new Map();
|
|
3774
3376
|
let closing = false;
|
|
3775
|
-
let closeTask;
|
|
3776
3377
|
return {
|
|
3777
3378
|
handler: ((req, res) => {
|
|
3778
3379
|
if (closing) {
|
|
@@ -3798,18 +3399,15 @@ function createMSTeamsWebhookHandler(expressApp, warn) {
|
|
|
3798
3399
|
}
|
|
3799
3400
|
return response.promise;
|
|
3800
3401
|
}),
|
|
3801
|
-
close: () => {
|
|
3402
|
+
close: async () => {
|
|
3802
3403
|
closing = true;
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
}
|
|
3811
|
-
})();
|
|
3812
|
-
return closeTask;
|
|
3404
|
+
if (responses.size === 0) return;
|
|
3405
|
+
try {
|
|
3406
|
+
await (0, openclaw_plugin_sdk_time_runtime.withTimeout)(Promise.all(responses.values()), require_resolve_allowlist.MSTEAMS_REQUEST_TIMEOUT_MS, "Microsoft Teams webhook shutdown");
|
|
3407
|
+
} catch {
|
|
3408
|
+
warn("Microsoft Teams webhook shutdown exceeded 30 seconds; closing active responses");
|
|
3409
|
+
for (const response of responses.keys()) response.destroy();
|
|
3410
|
+
}
|
|
3813
3411
|
}
|
|
3814
3412
|
};
|
|
3815
3413
|
}
|
|
@@ -3825,19 +3423,13 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3825
3423
|
let msteamsCfg = account.config;
|
|
3826
3424
|
if (opts.cfg.channels?.msteams?.enabled !== true || msteamsCfg.enabled === false) {
|
|
3827
3425
|
log.debug?.("msteams provider disabled");
|
|
3828
|
-
|
|
3829
|
-
return
|
|
3830
|
-
app: null,
|
|
3831
|
-
shutdown: async () => {}
|
|
3832
|
-
};
|
|
3426
|
+
opts.statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelBlockedPatch)("Microsoft Teams provider is disabled", { running: true }));
|
|
3427
|
+
return;
|
|
3833
3428
|
}
|
|
3834
3429
|
if (!creds) {
|
|
3835
3430
|
log.error("msteams credentials not configured");
|
|
3836
|
-
|
|
3837
|
-
return
|
|
3838
|
-
app: null,
|
|
3839
|
-
shutdown: async () => {}
|
|
3840
|
-
};
|
|
3431
|
+
opts.statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelBlockedPatch)("Microsoft Teams credentials are not configured", { running: true }));
|
|
3432
|
+
return;
|
|
3841
3433
|
}
|
|
3842
3434
|
const collisionIssue = require_channel.resolveMSTeamsWebhookCollisionIssue(accountPolicyCfg);
|
|
3843
3435
|
if (collisionIssue) throw new Error(collisionIssue);
|
|
@@ -3946,7 +3538,8 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3946
3538
|
cfg,
|
|
3947
3539
|
resolveChannelLimitMb: ({ cfg: channelCfg }) => channelCfg.channels?.msteams?.mediaMaxMb
|
|
3948
3540
|
}) ?? 8388608;
|
|
3949
|
-
const
|
|
3541
|
+
const conversationStore = require_conversation_store_state.createMSTeamsConversationStoreState({ accountId });
|
|
3542
|
+
const pollStore = require_conversation_store_state.createMSTeamsPollStoreState({ accountId });
|
|
3950
3543
|
log.info("starting provider on Gateway HTTP routes");
|
|
3951
3544
|
const express = await import("express");
|
|
3952
3545
|
const expressApp = express.default();
|
|
@@ -4183,12 +3776,11 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4183
3776
|
throwOnFailure: true,
|
|
4184
3777
|
log: (message) => log.warn?.(message)
|
|
4185
3778
|
}));
|
|
4186
|
-
|
|
4187
|
-
if (accountId === openclaw_plugin_sdk_account_id.DEFAULT_ACCOUNT_ID && !legacyMessagesOwner) unregisterRoutes.push((0, openclaw_plugin_sdk_webhook_targets.registerPluginHttpRoute)({
|
|
3779
|
+
if (accountId === openclaw_plugin_sdk_account_id.DEFAULT_ACCOUNT_ID && configuredPath !== "/api/messages") unregisterRoutes.push((0, openclaw_plugin_sdk_webhook_targets.registerPluginHttpRoute)({
|
|
4188
3780
|
path: "/api/messages",
|
|
4189
3781
|
auth: "plugin",
|
|
4190
3782
|
pluginId: "msteams",
|
|
4191
|
-
source: "msteams-webhook",
|
|
3783
|
+
source: "msteams-webhook-alias",
|
|
4192
3784
|
accountId,
|
|
4193
3785
|
handler: webhook.handler,
|
|
4194
3786
|
log: (message) => log.warn?.(message)
|
|
@@ -4199,26 +3791,15 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4199
3791
|
throw error;
|
|
4200
3792
|
}
|
|
4201
3793
|
log.info(`msteams provider started on Gateway route ${configuredPath}`);
|
|
4202
|
-
|
|
4203
|
-
let shutdownTask;
|
|
4204
|
-
const shutdown = () => {
|
|
4205
|
-
shutdownTask ??= (async () => {
|
|
4206
|
-
await webhook.close();
|
|
4207
|
-
for (const unregister of unregisterRoutes.splice(0)) unregister();
|
|
4208
|
-
await ingress.stop();
|
|
4209
|
-
publishMSTeamsStopped(opts.statusSink);
|
|
4210
|
-
})();
|
|
4211
|
-
return shutdownTask;
|
|
4212
|
-
};
|
|
3794
|
+
opts.statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelReadyPatch)());
|
|
4213
3795
|
try {
|
|
4214
|
-
await (0, openclaw_plugin_sdk_channel_outbound.waitUntilAbort)(opts.abortSignal
|
|
3796
|
+
await (0, openclaw_plugin_sdk_channel_outbound.waitUntilAbort)(opts.abortSignal);
|
|
4215
3797
|
} finally {
|
|
4216
|
-
await
|
|
3798
|
+
await webhook.close();
|
|
3799
|
+
for (const unregister of unregisterRoutes) unregister();
|
|
3800
|
+
await ingress.stop();
|
|
3801
|
+
opts.statusSink?.((0, openclaw_plugin_sdk_gateway_runtime.channelStoppedPatch)());
|
|
4217
3802
|
}
|
|
4218
|
-
return {
|
|
4219
|
-
app: expressApp,
|
|
4220
|
-
shutdown
|
|
4221
|
-
};
|
|
4222
3803
|
}
|
|
4223
3804
|
function cardActionMessage(value) {
|
|
4224
3805
|
return {
|
|
@@ -4257,18 +3838,10 @@ function adaptSdkContext(ctx, app) {
|
|
|
4257
3838
|
const sendActivity = (activity) => isThreadable ? sdkCtx.reply(activity) : sdkCtx.send(activity);
|
|
4258
3839
|
return Object.assign(Object.create(Object.getPrototypeOf(ctx)), ctx, {
|
|
4259
3840
|
sendActivity,
|
|
4260
|
-
sendActivities: async (activities) => {
|
|
4261
|
-
const results = [];
|
|
4262
|
-
for (const a of activities) results.push(await sendActivity(a));
|
|
4263
|
-
return results;
|
|
4264
|
-
},
|
|
4265
3841
|
updateActivity: async (activity) => {
|
|
4266
3842
|
const activityId = activity.id ?? "";
|
|
4267
3843
|
return activityApi.conversations.activities(conversationId).update(activityId, activity);
|
|
4268
3844
|
},
|
|
4269
|
-
deleteActivity: async (activityId) => {
|
|
4270
|
-
return activityApi.conversations.activities(conversationId).delete(activityId);
|
|
4271
|
-
},
|
|
4272
3845
|
getTeamDetails,
|
|
4273
3846
|
stream: sdkCtx.stream
|
|
4274
3847
|
});
|