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