@openclaw/msteams 2026.7.2-beta.2 → 2026.7.2-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.cjs +2 -2
- package/dist/{channel-BKc8zroK.cjs → channel-oNBYNcJT.cjs} +4 -4
- package/dist/channel-plugin-api.cjs +1 -1
- package/dist/{channel.runtime-hd1Zsoo2.cjs → channel.runtime-Sn3hY2Lu.cjs} +13 -7
- package/dist/delegated-state-DOSCHaAB.cjs +98 -0
- package/dist/directory-contract-api.cjs +1 -1
- package/dist/doctor-contract-api.cjs +86 -7
- package/dist/{messenger-BVcKl6dv.cjs → messenger-Do4oje4J.cjs} +10 -9
- package/dist/{oauth-yNoKtU_3.cjs → oauth-BUFvYhIw.cjs} +1 -1
- package/dist/{polls-BhoI4VaN.cjs → polls-CWExq2xZ.cjs} +24 -40
- package/dist/{resolve-allowlist-DXd097EP.cjs → resolve-allowlist-Dk-1CqBm.cjs} +9 -27
- package/dist/runtime-api.cjs +0 -7
- package/dist/secret-contract-api.cjs +6 -0
- package/dist/setup-plugin-api.cjs +2 -2
- package/dist/{setup-surface-D4f6x5cx.cjs → setup-surface-BcRVoUY8.cjs} +2 -2
- package/dist/{src-20YasZEp.cjs → src-BQM9QUTF.cjs} +441 -303
- package/dist/{sso-token-store-CktCz88k.cjs → sso-token-store-OSg229re.cjs} +4 -4
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
- package/dist/rolldown-runtime-D6vf50IK.cjs +0 -28
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_delegated_state = require("./delegated-state-DOSCHaAB.cjs");
|
|
2
2
|
const require_runtime = require("./runtime-gZTAuFux.cjs");
|
|
3
3
|
require("./runtime-api.cjs");
|
|
4
|
-
const require_resolve_allowlist = require("./resolve-allowlist-
|
|
5
|
-
const require_channel = require("./channel-
|
|
6
|
-
const require_setup_surface = require("./setup-surface-
|
|
7
|
-
const require_polls = require("./polls-
|
|
8
|
-
const require_sso_token_store = require("./sso-token-store-
|
|
9
|
-
const require_messenger = require("./messenger-
|
|
4
|
+
const require_resolve_allowlist = require("./resolve-allowlist-Dk-1CqBm.cjs");
|
|
5
|
+
const require_channel = require("./channel-oNBYNcJT.cjs");
|
|
6
|
+
const require_setup_surface = require("./setup-surface-BcRVoUY8.cjs");
|
|
7
|
+
const require_polls = require("./polls-CWExq2xZ.cjs");
|
|
8
|
+
const require_sso_token_store = require("./sso-token-store-OSg229re.cjs");
|
|
9
|
+
const require_messenger = require("./messenger-Do4oje4J.cjs");
|
|
10
10
|
let openclaw_plugin_sdk_allow_from = require("openclaw/plugin-sdk/allow-from");
|
|
11
11
|
let openclaw_plugin_sdk_channel_outbound = require("openclaw/plugin-sdk/channel-outbound");
|
|
12
12
|
let openclaw_plugin_sdk_string_coerce_runtime = require("openclaw/plugin-sdk/string-coerce-runtime");
|
|
13
13
|
let openclaw_plugin_sdk_media_runtime = require("openclaw/plugin-sdk/media-runtime");
|
|
14
|
-
let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
|
|
15
14
|
let openclaw_plugin_sdk_ssrf_runtime = require("openclaw/plugin-sdk/ssrf-runtime");
|
|
16
15
|
let openclaw_plugin_sdk_provider_http = require("openclaw/plugin-sdk/provider-http");
|
|
17
16
|
let openclaw_plugin_sdk_number_runtime = require("openclaw/plugin-sdk/number-runtime");
|
|
18
17
|
let openclaw_plugin_sdk_text_utility_runtime = require("openclaw/plugin-sdk/text-utility-runtime");
|
|
19
|
-
let node_path = require("node:path");
|
|
20
|
-
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
21
|
-
let openclaw_plugin_sdk_security_runtime = require("openclaw/plugin-sdk/security-runtime");
|
|
22
18
|
let openclaw_plugin_sdk_routing = require("openclaw/plugin-sdk/routing");
|
|
23
19
|
let node_crypto = require("node:crypto");
|
|
24
|
-
node_crypto =
|
|
20
|
+
node_crypto = require_delegated_state.__toESM(node_crypto, 1);
|
|
21
|
+
let openclaw_plugin_sdk_channel_inbound = require("openclaw/plugin-sdk/channel-inbound");
|
|
25
22
|
let openclaw_plugin_sdk_channel_ingress_runtime = require("openclaw/plugin-sdk/channel-ingress-runtime");
|
|
26
23
|
let openclaw_plugin_sdk_context_visibility_runtime = require("openclaw/plugin-sdk/context-visibility-runtime");
|
|
27
24
|
let openclaw_plugin_sdk_reply_history = require("openclaw/plugin-sdk/reply-history");
|
|
25
|
+
let openclaw_plugin_sdk_agent_runtime = require("openclaw/plugin-sdk/agent-runtime");
|
|
28
26
|
let openclaw_plugin_sdk_dedupe_runtime = require("openclaw/plugin-sdk/dedupe-runtime");
|
|
29
27
|
let openclaw_plugin_sdk_collection_runtime = require("openclaw/plugin-sdk/collection-runtime");
|
|
30
28
|
let openclaw_plugin_sdk_channel_pairing = require("openclaw/plugin-sdk/channel-pairing");
|
|
@@ -33,100 +31,24 @@ let openclaw_plugin_sdk_runtime_group_policy = require("openclaw/plugin-sdk/runt
|
|
|
33
31
|
let openclaw_plugin_sdk_dangerous_name_runtime = require("openclaw/plugin-sdk/dangerous-name-runtime");
|
|
34
32
|
let openclaw_plugin_sdk_reply_payload = require("openclaw/plugin-sdk/reply-payload");
|
|
35
33
|
let openclaw_plugin_sdk_webhook_ingress = require("openclaw/plugin-sdk/webhook-ingress");
|
|
36
|
-
//#region extensions/msteams/src/feedback-reflection-
|
|
37
|
-
/** Max chars of the thumbed-down response to include in the reflection prompt. */
|
|
38
|
-
const MAX_RESPONSE_CHARS = 500;
|
|
39
|
-
function buildReflectionPrompt(params) {
|
|
40
|
-
const parts = ["A user indicated your previous response wasn't helpful."];
|
|
41
|
-
if (params.thumbedDownResponse) {
|
|
42
|
-
const truncated = params.thumbedDownResponse.length > MAX_RESPONSE_CHARS ? `${(0, openclaw_plugin_sdk_text_utility_runtime.truncateUtf16Safe)(params.thumbedDownResponse, MAX_RESPONSE_CHARS)}...` : params.thumbedDownResponse;
|
|
43
|
-
parts.push(`\nYour response was:\n> ${truncated}`);
|
|
44
|
-
}
|
|
45
|
-
if (params.userComment) parts.push(`\nUser's comment: "${params.userComment}"`);
|
|
46
|
-
parts.push("\nBriefly reflect: what could you improve? Consider tone, length, accuracy, relevance, and specificity. Reply with a single JSON object only, no markdown or prose, using this exact shape:\n{\"learning\":\"...\",\"followUp\":false,\"userMessage\":\"\"}\n- learning: a short internal adjustment note (1-2 sentences) for your future behavior in this conversation.\n- followUp: true only if the user needs a direct follow-up message.\n- userMessage: only the exact user-facing message to send; empty string when followUp is false.");
|
|
47
|
-
return parts.join("\n");
|
|
48
|
-
}
|
|
49
|
-
function parseBooleanLike(value) {
|
|
50
|
-
if (typeof value === "boolean") return value;
|
|
51
|
-
if (typeof value === "string") {
|
|
52
|
-
const normalized = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(value);
|
|
53
|
-
if (normalized === "true" || normalized === "yes") return true;
|
|
54
|
-
if (normalized === "false" || normalized === "no") return false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function parseStructuredReflectionValue(value) {
|
|
58
|
-
if (value == null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
59
|
-
const candidate = value;
|
|
60
|
-
const learning = typeof candidate.learning === "string" ? candidate.learning.trim() : void 0;
|
|
61
|
-
if (!learning) return null;
|
|
62
|
-
return {
|
|
63
|
-
learning,
|
|
64
|
-
followUp: parseBooleanLike(candidate.followUp) ?? false,
|
|
65
|
-
userMessage: typeof candidate.userMessage === "string" && candidate.userMessage.trim() ? candidate.userMessage.trim() : void 0
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function parseReflectionResponse(text) {
|
|
69
|
-
const trimmed = text.trim();
|
|
70
|
-
if (!trimmed) return null;
|
|
71
|
-
const candidates = [trimmed, ...trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i)?.slice(1, 2) ?? []];
|
|
72
|
-
for (const candidateText of candidates) {
|
|
73
|
-
const candidate = candidateText.trim();
|
|
74
|
-
if (!candidate) continue;
|
|
75
|
-
try {
|
|
76
|
-
const parsed = parseStructuredReflectionValue(JSON.parse(candidate));
|
|
77
|
-
if (parsed) return parsed;
|
|
78
|
-
} catch {}
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
learning: trimmed,
|
|
82
|
-
followUp: false
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
/** Tracks last reflection time per session to enforce cooldown. */
|
|
86
|
-
const lastReflectionBySession = /* @__PURE__ */ new Map();
|
|
87
|
-
/** Maximum cooldown entries before pruning expired ones. */
|
|
88
|
-
const MAX_COOLDOWN_ENTRIES = 500;
|
|
34
|
+
//#region extensions/msteams/src/feedback-reflection-store.ts
|
|
89
35
|
const LEARNINGS_NAMESPACE = "feedback-learnings";
|
|
90
36
|
const MAX_LEARNING_ENTRIES = 1e4;
|
|
91
37
|
function learningStoreKey(storePath, sessionKey) {
|
|
92
38
|
return node_crypto.default.createHash("sha256").update(`${storePath}\0${sessionKey}`, "utf8").digest("hex");
|
|
93
39
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
40
|
+
async function storeSessionLearning(params) {
|
|
41
|
+
const store = require_runtime.getMSTeamsRuntime().state.openKeyedStore({
|
|
96
42
|
namespace: LEARNINGS_NAMESPACE,
|
|
97
43
|
maxEntries: MAX_LEARNING_ENTRIES
|
|
98
44
|
});
|
|
99
|
-
}
|
|
100
|
-
/** Prune expired cooldown entries to prevent unbounded memory growth. */
|
|
101
|
-
function pruneExpiredCooldowns(cooldownMs) {
|
|
102
|
-
if (lastReflectionBySession.size <= MAX_COOLDOWN_ENTRIES) return;
|
|
103
|
-
const now = Date.now();
|
|
104
|
-
for (const [key, time] of lastReflectionBySession) if (now - time >= cooldownMs) lastReflectionBySession.delete(key);
|
|
105
|
-
}
|
|
106
|
-
/** Check if a reflection is allowed (cooldown not active). */
|
|
107
|
-
function isReflectionAllowed(sessionKey, cooldownMs) {
|
|
108
|
-
const cooldown = cooldownMs ?? 3e5;
|
|
109
|
-
const lastTime = lastReflectionBySession.get(sessionKey);
|
|
110
|
-
if (lastTime == null) return true;
|
|
111
|
-
return Date.now() - lastTime >= cooldown;
|
|
112
|
-
}
|
|
113
|
-
/** Record that a reflection was run for a session. */
|
|
114
|
-
function recordReflectionTime(sessionKey, cooldownMs) {
|
|
115
|
-
lastReflectionBySession.set(sessionKey, Date.now());
|
|
116
|
-
pruneExpiredCooldowns(cooldownMs ?? 3e5);
|
|
117
|
-
}
|
|
118
|
-
/** Store a learning derived from feedback reflection. */
|
|
119
|
-
async function storeSessionLearning(params) {
|
|
120
|
-
const store = openLearningStore();
|
|
121
45
|
const key = learningStoreKey(params.storePath, params.sessionKey);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (learnings.length > 10) learnings = learnings.slice(-10);
|
|
125
|
-
await store.register(key, {
|
|
46
|
+
if (!store.update) throw new Error("plugin state atomic update is unavailable");
|
|
47
|
+
await store.update(key, (existing) => ({
|
|
126
48
|
sessionKey: params.sessionKey,
|
|
127
|
-
learnings,
|
|
49
|
+
learnings: [...existing?.learnings ?? [], params.learning].slice(-10),
|
|
128
50
|
updatedAt: Date.now()
|
|
129
|
-
});
|
|
51
|
+
}));
|
|
130
52
|
}
|
|
131
53
|
//#endregion
|
|
132
54
|
//#region extensions/msteams/src/feedback-reflection.ts
|
|
@@ -143,144 +65,68 @@ function buildFeedbackEvent(params) {
|
|
|
143
65
|
conversationId: params.conversationId
|
|
144
66
|
};
|
|
145
67
|
}
|
|
146
|
-
function buildReflectionContext(params) {
|
|
147
|
-
const core = require_runtime.getMSTeamsRuntime();
|
|
148
|
-
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(params.cfg);
|
|
149
|
-
const body = core.channel.reply.formatAgentEnvelope({
|
|
150
|
-
channel: "Teams",
|
|
151
|
-
from: "system",
|
|
152
|
-
body: params.reflectionPrompt,
|
|
153
|
-
envelope: envelopeOptions
|
|
154
|
-
});
|
|
155
|
-
return { ctxPayload: core.channel.reply.finalizeInboundContext({
|
|
156
|
-
Body: body,
|
|
157
|
-
BodyForAgent: params.reflectionPrompt,
|
|
158
|
-
RawBody: params.reflectionPrompt,
|
|
159
|
-
CommandBody: params.reflectionPrompt,
|
|
160
|
-
From: `msteams:system:${params.conversationId}`,
|
|
161
|
-
To: `conversation:${params.conversationId}`,
|
|
162
|
-
SessionKey: params.sessionKey,
|
|
163
|
-
ChatType: "direct",
|
|
164
|
-
SenderName: "system",
|
|
165
|
-
SenderId: "system",
|
|
166
|
-
Provider: "msteams",
|
|
167
|
-
Surface: "msteams",
|
|
168
|
-
Timestamp: Date.now(),
|
|
169
|
-
WasMentioned: true,
|
|
170
|
-
CommandAuthorized: false,
|
|
171
|
-
OriginatingChannel: "msteams",
|
|
172
|
-
OriginatingTo: `conversation:${params.conversationId}`
|
|
173
|
-
}) };
|
|
174
|
-
}
|
|
175
|
-
function createReflectionCaptureDispatcher(params) {
|
|
176
|
-
const core = require_runtime.getMSTeamsRuntime();
|
|
177
|
-
let response = "";
|
|
178
|
-
const { dispatcher, replyOptions } = core.channel.reply.createReplyDispatcherWithTyping({
|
|
179
|
-
deliver: async (payload) => {
|
|
180
|
-
if (payload.text) response += (response ? "\n" : "") + payload.text;
|
|
181
|
-
},
|
|
182
|
-
typingCallbacks: {
|
|
183
|
-
onReplyStart: async () => {},
|
|
184
|
-
onIdle: () => {},
|
|
185
|
-
onCleanup: () => {}
|
|
186
|
-
},
|
|
187
|
-
humanDelay: core.channel.reply.resolveHumanDelayConfig(params.cfg, params.agentId),
|
|
188
|
-
onError: (err) => {
|
|
189
|
-
params.log.debug?.("reflection reply error", { error: require_setup_surface.formatUnknownError(err) });
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
return {
|
|
193
|
-
dispatcher,
|
|
194
|
-
replyOptions,
|
|
195
|
-
readResponse: () => response
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
async function sendReflectionFollowUp(params) {
|
|
199
|
-
const baseRef = require_messenger.buildConversationReference(params.conversationRef);
|
|
200
|
-
await require_messenger.sendMSTeamsActivityWithReference(params.app, baseRef, {
|
|
201
|
-
type: "message",
|
|
202
|
-
text: params.userMessage
|
|
203
|
-
}, { serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(params.cfg.channels?.msteams) });
|
|
204
|
-
}
|
|
205
68
|
/**
|
|
206
69
|
* Run a background reflection after negative feedback.
|
|
207
70
|
* This is designed to be called fire-and-forget (don't await in the invoke handler).
|
|
208
71
|
*/
|
|
209
72
|
async function runFeedbackReflection(params) {
|
|
210
73
|
const { cfg, log, sessionKey } = params;
|
|
211
|
-
const cooldownMs = cfg.channels?.msteams?.feedbackReflectionCooldownMs ??
|
|
212
|
-
|
|
213
|
-
log.debug?.("skipping reflection (cooldown active)", { sessionKey });
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
const reflectionPrompt = buildReflectionPrompt({
|
|
217
|
-
thumbedDownResponse: params.thumbedDownResponse,
|
|
218
|
-
userComment: params.userComment
|
|
219
|
-
});
|
|
220
|
-
const storePath = require_runtime.getMSTeamsRuntime().channel.session.resolveStorePath(cfg.session?.store, { agentId: params.agentId });
|
|
221
|
-
const { ctxPayload } = buildReflectionContext({
|
|
222
|
-
cfg,
|
|
223
|
-
conversationId: params.conversationId,
|
|
224
|
-
sessionKey: params.sessionKey,
|
|
225
|
-
reflectionPrompt
|
|
226
|
-
});
|
|
227
|
-
const capture = createReflectionCaptureDispatcher({
|
|
228
|
-
cfg,
|
|
229
|
-
agentId: params.agentId,
|
|
230
|
-
log
|
|
231
|
-
});
|
|
74
|
+
const cooldownMs = cfg.channels?.msteams?.feedbackReflectionCooldownMs ?? openclaw_plugin_sdk_channel_inbound.DEFAULT_CHANNEL_FEEDBACK_REFLECTION_COOLDOWN_MS;
|
|
75
|
+
let reflection;
|
|
232
76
|
try {
|
|
233
|
-
await (0, openclaw_plugin_sdk_channel_inbound.
|
|
234
|
-
ctxPayload,
|
|
77
|
+
reflection = await (0, openclaw_plugin_sdk_channel_inbound.runChannelFeedbackReflection)({
|
|
235
78
|
cfg,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
79
|
+
channel: "msteams",
|
|
80
|
+
channelLabel: "Teams",
|
|
81
|
+
agentId: params.agentId,
|
|
82
|
+
sessionKey,
|
|
83
|
+
conversationId: params.conversationId,
|
|
84
|
+
conversationKind: params.conversationKind,
|
|
85
|
+
thumbedDownResponse: params.thumbedDownResponse,
|
|
86
|
+
userComment: params.userComment,
|
|
87
|
+
cooldownMs,
|
|
88
|
+
onRecordError: (err) => log.debug?.("reflection session record failed", { error: require_setup_surface.formatUnknownError(err) }),
|
|
89
|
+
onDispatchError: (err) => log.debug?.("reflection reply error", { error: require_setup_surface.formatUnknownError(err) })
|
|
239
90
|
});
|
|
240
91
|
} catch (err) {
|
|
241
92
|
log.error("reflection dispatch failed", { error: require_setup_surface.formatUnknownError(err) });
|
|
242
93
|
return;
|
|
243
94
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
log.debug?.("reflection produced no output");
|
|
95
|
+
if (reflection.status === "cooldown") {
|
|
96
|
+
log.debug?.("skipping reflection (cooldown active)", { sessionKey });
|
|
247
97
|
return;
|
|
248
98
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
log.debug?.("reflection produced no structured output");
|
|
99
|
+
if (reflection.status === "empty") {
|
|
100
|
+
log.debug?.("reflection produced no output");
|
|
252
101
|
return;
|
|
253
102
|
}
|
|
254
|
-
recordReflectionTime(sessionKey, cooldownMs);
|
|
255
103
|
log.info("reflection complete", {
|
|
256
104
|
sessionKey,
|
|
257
|
-
responseLength:
|
|
258
|
-
followUp:
|
|
105
|
+
responseLength: reflection.responseLength,
|
|
106
|
+
followUp: reflection.followUp
|
|
259
107
|
});
|
|
260
108
|
try {
|
|
261
109
|
await storeSessionLearning({
|
|
262
|
-
storePath,
|
|
263
|
-
sessionKey
|
|
264
|
-
learning:
|
|
110
|
+
storePath: reflection.storePath,
|
|
111
|
+
sessionKey,
|
|
112
|
+
learning: reflection.learning
|
|
265
113
|
});
|
|
266
114
|
} catch (err) {
|
|
267
115
|
log.debug?.("failed to store reflection learning", { error: require_setup_surface.formatUnknownError(err) });
|
|
268
116
|
}
|
|
269
117
|
const conversationType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(params.conversationRef.conversation?.conversationType);
|
|
270
|
-
if (!(conversationType === "personal" &&
|
|
271
|
-
if (
|
|
118
|
+
if (!(conversationType === "personal" && reflection.followUp && Boolean(reflection.userMessage))) {
|
|
119
|
+
if (reflection.followUp && conversationType !== "personal") log.debug?.("skipping reflection follow-up outside direct message", {
|
|
272
120
|
sessionKey,
|
|
273
121
|
conversationType
|
|
274
122
|
});
|
|
275
123
|
return;
|
|
276
124
|
}
|
|
277
125
|
try {
|
|
278
|
-
await
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
userMessage: parsedReflection.userMessage
|
|
283
|
-
});
|
|
126
|
+
await require_messenger.sendMSTeamsActivityWithReference(params.app, require_messenger.buildConversationReference(params.conversationRef), {
|
|
127
|
+
type: "message",
|
|
128
|
+
text: reflection.userMessage
|
|
129
|
+
}, { serviceUrlBoundary: require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(cfg.channels?.msteams) });
|
|
284
130
|
log.info("sent reflection follow-up", { sessionKey });
|
|
285
131
|
} catch (err) {
|
|
286
132
|
log.debug?.("failed to send reflection follow-up", { error: require_setup_surface.formatUnknownError(err) });
|
|
@@ -1815,14 +1661,16 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1815
1661
|
}
|
|
1816
1662
|
if (ids.length > 0) params.onSentMessageIds?.(ids);
|
|
1817
1663
|
};
|
|
1818
|
-
const
|
|
1664
|
+
const dispatcherOptions = {
|
|
1819
1665
|
...replyPipeline,
|
|
1820
|
-
humanDelay:
|
|
1666
|
+
humanDelay: (0, openclaw_plugin_sdk_agent_runtime.resolveHumanDelayConfig)(params.cfg, params.agentId),
|
|
1821
1667
|
onReplyStart: async () => {
|
|
1822
1668
|
await streamController.onReplyStart();
|
|
1823
1669
|
if (typingIndicatorEnabled) await typingCallbacks?.onReplyStart?.();
|
|
1824
1670
|
},
|
|
1825
|
-
typingCallbacks
|
|
1671
|
+
typingCallbacks
|
|
1672
|
+
};
|
|
1673
|
+
const delivery = {
|
|
1826
1674
|
deliver: async (payload) => {
|
|
1827
1675
|
const preparedPayload = streamController.preparePayload(payload);
|
|
1828
1676
|
if (!preparedPayload) return;
|
|
@@ -1841,8 +1689,8 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1841
1689
|
hint
|
|
1842
1690
|
});
|
|
1843
1691
|
}
|
|
1844
|
-
}
|
|
1845
|
-
const
|
|
1692
|
+
};
|
|
1693
|
+
const settleDelivery = () => {
|
|
1846
1694
|
return flushPendingMessages().catch((err) => {
|
|
1847
1695
|
const errMsg = require_setup_surface.formatUnknownError(err);
|
|
1848
1696
|
const classification = require_setup_surface.classifyMSTeamsSendError(err);
|
|
@@ -1861,8 +1709,6 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1861
1709
|
queueReplyPayload(fallbackPayload);
|
|
1862
1710
|
await flushPendingMessages();
|
|
1863
1711
|
}
|
|
1864
|
-
}).finally(() => {
|
|
1865
|
-
baseMarkDispatchIdle();
|
|
1866
1712
|
});
|
|
1867
1713
|
};
|
|
1868
1714
|
const previewToolProgressEnabled = (0, openclaw_plugin_sdk_channel_outbound.resolveChannelStreamingPreviewToolProgress)(msteamsCfg);
|
|
@@ -1913,7 +1759,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1913
1759
|
},
|
|
1914
1760
|
onPlanUpdate: async (payload) => {
|
|
1915
1761
|
if (payload?.phase !== "update") return;
|
|
1916
|
-
await streamController.pushPlanProgress((0, openclaw_plugin_sdk_channel_outbound.normalizeAgentPlanSteps)(payload.
|
|
1762
|
+
await streamController.pushPlanProgress((0, openclaw_plugin_sdk_channel_outbound.normalizeAgentPlanSteps)(payload.steps), { explanation: typeof payload.explanation === "string" ? payload.explanation : void 0 });
|
|
1917
1763
|
},
|
|
1918
1764
|
onApprovalEvent: async (payload) => {
|
|
1919
1765
|
if (payload?.phase !== "requested") return;
|
|
@@ -1956,16 +1802,18 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1956
1802
|
}
|
|
1957
1803
|
} : {};
|
|
1958
1804
|
return {
|
|
1959
|
-
|
|
1805
|
+
dispatcherOptions: {
|
|
1806
|
+
...dispatcherOptions,
|
|
1807
|
+
onSettled: settleDelivery
|
|
1808
|
+
},
|
|
1809
|
+
delivery,
|
|
1960
1810
|
replyOptions: {
|
|
1961
|
-
...replyOptions,
|
|
1962
1811
|
...streamController.hasStream() ? { onPartialReply: (payload) => streamController.onPartialReply(payload) } : {},
|
|
1963
1812
|
...progressCallbacks,
|
|
1964
1813
|
...shouldSuppressDefaultToolProgressMessages ? { suppressDefaultToolProgressMessages: true } : {},
|
|
1965
1814
|
disableBlockStreaming: blockStreamingResolved == null ? void 0 : !blockStreamingResolved,
|
|
1966
1815
|
onModelSelected
|
|
1967
|
-
}
|
|
1968
|
-
markDispatchIdle
|
|
1816
|
+
}
|
|
1969
1817
|
};
|
|
1970
1818
|
}
|
|
1971
1819
|
//#endregion
|
|
@@ -2581,12 +2429,17 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2581
2429
|
const replies = repliesResult.status === "fulfilled" ? repliesResult.value : [];
|
|
2582
2430
|
if (parentResult.status === "rejected") log.debug?.("failed to fetch parent message", { error: require_setup_surface.formatUnknownError(parentResult.reason) });
|
|
2583
2431
|
if (repliesResult.status === "rejected") log.debug?.("failed to fetch thread replies", { error: require_setup_surface.formatUnknownError(repliesResult.reason) });
|
|
2584
|
-
const isThreadSenderAllowed = (msg) =>
|
|
2432
|
+
const isThreadSenderAllowed = (msg) => (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
|
|
2433
|
+
isGroup: isChannel,
|
|
2434
|
+
groupPolicy,
|
|
2585
2435
|
allowFrom: effectiveGroupAllowFrom,
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2436
|
+
isSenderAllowed: (allowFrom) => require_channel.resolveMSTeamsAllowlistMatch({
|
|
2437
|
+
allowFrom,
|
|
2438
|
+
senderId: msg.from?.user?.id ?? "",
|
|
2439
|
+
senderName: msg.from?.user?.displayName,
|
|
2440
|
+
allowNameMatching
|
|
2441
|
+
}).allowed
|
|
2442
|
+
});
|
|
2590
2443
|
const parentSummary = summarizeParentMessage(parentMsg);
|
|
2591
2444
|
const visibleParentMessages = parentMsg ? (0, openclaw_plugin_sdk_context_visibility_runtime.filterSupplementalContextItems)({
|
|
2592
2445
|
items: [parentMsg],
|
|
@@ -2617,17 +2470,14 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2617
2470
|
}
|
|
2618
2471
|
quoteSenderName ??= quoteInfo?.sender;
|
|
2619
2472
|
const envelopeFrom = isDirectMessage ? senderName : conversationType;
|
|
2620
|
-
const
|
|
2473
|
+
const buildEnvelope = (0, openclaw_plugin_sdk_channel_inbound.createChannelInboundEnvelopeBuilder)({
|
|
2621
2474
|
cfg,
|
|
2622
|
-
|
|
2623
|
-
sessionKey: route.sessionKey
|
|
2475
|
+
route
|
|
2624
2476
|
});
|
|
2625
|
-
let combinedBody =
|
|
2477
|
+
let combinedBody = buildEnvelope({
|
|
2626
2478
|
channel: "Teams",
|
|
2627
2479
|
from: envelopeFrom,
|
|
2628
2480
|
timestamp,
|
|
2629
|
-
previousTimestamp,
|
|
2630
|
-
envelope: envelopeOptions,
|
|
2631
2481
|
body: agentBody
|
|
2632
2482
|
});
|
|
2633
2483
|
const isRoomish = !isDirectMessage;
|
|
@@ -2636,12 +2486,12 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2636
2486
|
historyKey,
|
|
2637
2487
|
limit: historyLimit,
|
|
2638
2488
|
currentMessage: combinedBody,
|
|
2639
|
-
formatEntry: (entry) =>
|
|
2489
|
+
formatEntry: (entry) => buildEnvelope({
|
|
2640
2490
|
channel: "Teams",
|
|
2641
2491
|
from: conversationType,
|
|
2642
2492
|
timestamp: entry.timestamp,
|
|
2643
|
-
|
|
2644
|
-
|
|
2493
|
+
previousTimestamp: null,
|
|
2494
|
+
body: `${entry.sender}: ${entry.body}${entry.messageId ? ` [id:${entry.messageId}]` : ""}`
|
|
2645
2495
|
})
|
|
2646
2496
|
});
|
|
2647
2497
|
const inboundHistory = isRoomish && historyKey && historyLimit > 0 ? (0, openclaw_plugin_sdk_reply_history.createChannelHistoryWindow)({ historyMap: conversationHistories }).buildInboundHistory({
|
|
@@ -2649,17 +2499,21 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2649
2499
|
limit: historyLimit
|
|
2650
2500
|
}) : void 0;
|
|
2651
2501
|
const commandBody = text.trim();
|
|
2652
|
-
const quoteSenderAllowed = quoteInfo && quoteInfo.sender ?
|
|
2502
|
+
const quoteSenderAllowed = quoteInfo && quoteInfo.sender ? (0, openclaw_plugin_sdk_channel_inbound.resolveInboundSupplementalSenderAllowed)({
|
|
2503
|
+
isGroup: !isDirectMessage,
|
|
2504
|
+
groupPolicy,
|
|
2653
2505
|
allowFrom: effectiveGroupAllowFrom,
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2506
|
+
isSenderAllowed: (allowFrom) => require_channel.resolveMSTeamsAllowlistMatch({
|
|
2507
|
+
allowFrom,
|
|
2508
|
+
senderId: quoteSenderId ?? "",
|
|
2509
|
+
senderName: quoteSenderName,
|
|
2510
|
+
allowNameMatching
|
|
2511
|
+
}).allowed
|
|
2512
|
+
}) : true;
|
|
2658
2513
|
const bodyForAgent = threadContext ? `[Thread history]\n${threadContext}\n[/Thread history]\n\n${agentBody}` : agentBody;
|
|
2659
2514
|
const nativeChannelId = isChannel && teamAadGroupId ? `${teamAadGroupId}/${graphChannelId}` : void 0;
|
|
2660
2515
|
const ctxPayload = (0, openclaw_plugin_sdk_channel_inbound.buildChannelInboundEventContext)({
|
|
2661
2516
|
channel: "msteams",
|
|
2662
|
-
finalize: core.channel.reply.finalizeInboundContext,
|
|
2663
2517
|
contextVisibility: contextVisibilityMode,
|
|
2664
2518
|
supplemental: { quote: quoteInfo ? {
|
|
2665
2519
|
id: quoteInfo.id ?? activity.replyToId ?? void 0,
|
|
@@ -2684,6 +2538,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2684
2538
|
},
|
|
2685
2539
|
route: {
|
|
2686
2540
|
agentId: route.agentId,
|
|
2541
|
+
dmScope: route.dmScope,
|
|
2687
2542
|
accountId: route.accountId,
|
|
2688
2543
|
routeSessionKey: route.sessionKey
|
|
2689
2544
|
},
|
|
@@ -2714,7 +2569,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2714
2569
|
});
|
|
2715
2570
|
logVerboseMessage(`msteams inbound: from=${ctxPayload.From} preview="${preview}"`);
|
|
2716
2571
|
const sharePointSiteId = msteamsCfg?.sharePointSiteId;
|
|
2717
|
-
const {
|
|
2572
|
+
const { dispatcherOptions, delivery, replyOptions } = createMSTeamsReplyDispatcher({
|
|
2718
2573
|
cfg,
|
|
2719
2574
|
agentId: route.agentId,
|
|
2720
2575
|
sessionKey: route.sessionKey,
|
|
@@ -2734,10 +2589,16 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2734
2589
|
sharePointSiteId
|
|
2735
2590
|
});
|
|
2736
2591
|
const senderTimezone = (activity.entities?.find((e) => e.type === "clientInfo"))?.timezone || conversationRef.timezone;
|
|
2737
|
-
const
|
|
2738
|
-
...cfg
|
|
2739
|
-
|
|
2740
|
-
|
|
2592
|
+
const turnConfig = senderTimezone && !cfg.agents?.defaults?.userTimezone ? {
|
|
2593
|
+
...cfg,
|
|
2594
|
+
agents: {
|
|
2595
|
+
...cfg.agents,
|
|
2596
|
+
defaults: {
|
|
2597
|
+
...cfg.agents?.defaults,
|
|
2598
|
+
userTimezone: senderTimezone
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
} : cfg;
|
|
2741
2602
|
log.info("dispatching to agent", { sessionKey: route.sessionKey });
|
|
2742
2603
|
try {
|
|
2743
2604
|
const turnResult = await core.channel.inbound.run({
|
|
@@ -2754,12 +2615,14 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2754
2615
|
raw: activity
|
|
2755
2616
|
}),
|
|
2756
2617
|
resolveTurn: () => ({
|
|
2618
|
+
cfg: turnConfig,
|
|
2757
2619
|
channel: "msteams",
|
|
2758
2620
|
accountId: route.accountId,
|
|
2759
|
-
|
|
2760
|
-
|
|
2621
|
+
route: {
|
|
2622
|
+
agentId: route.agentId,
|
|
2623
|
+
sessionKey: route.sessionKey
|
|
2624
|
+
},
|
|
2761
2625
|
ctxPayload,
|
|
2762
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
2763
2626
|
record: { onRecordError: (err) => {
|
|
2764
2627
|
logVerboseMessage(`msteams: failed updating session meta: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2765
2628
|
} },
|
|
@@ -2769,18 +2632,12 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2769
2632
|
historyMap: conversationHistories,
|
|
2770
2633
|
limit: historyLimit
|
|
2771
2634
|
},
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
ctxPayload,
|
|
2779
|
-
dispatcher,
|
|
2780
|
-
onSettled: () => markDispatchIdle(),
|
|
2781
|
-
replyOptions,
|
|
2782
|
-
configOverride
|
|
2783
|
-
})
|
|
2635
|
+
dispatcherOptions,
|
|
2636
|
+
delivery,
|
|
2637
|
+
replyOptions: {
|
|
2638
|
+
...replyOptions,
|
|
2639
|
+
...params.turnAdoptionLifecycle ? (0, openclaw_plugin_sdk_channel_outbound.bindIngressLifecycleToReplyOptions)(params.turnAdoptionLifecycle) : {}
|
|
2640
|
+
}
|
|
2784
2641
|
})
|
|
2785
2642
|
}
|
|
2786
2643
|
});
|
|
@@ -2798,11 +2655,47 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2798
2655
|
} catch (err) {
|
|
2799
2656
|
log.error("dispatch failed", { error: require_setup_surface.formatUnknownError(err) });
|
|
2800
2657
|
runtime.error(`msteams dispatch failed: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2658
|
+
if (params.turnAdoptionLifecycle) throw err;
|
|
2801
2659
|
try {
|
|
2802
2660
|
await context.sendActivity("⚠️ Something went wrong. Please try again.");
|
|
2803
2661
|
} catch {}
|
|
2804
2662
|
}
|
|
2805
2663
|
};
|
|
2664
|
+
function buildFlushIngressLifecycle(entries) {
|
|
2665
|
+
const lifecycles = entries.map((entry) => entry.turnAdoptionLifecycle).filter((lifecycle) => lifecycle !== void 0);
|
|
2666
|
+
const [firstLifecycle] = lifecycles;
|
|
2667
|
+
if (!firstLifecycle) return {
|
|
2668
|
+
lifecycle: void 0,
|
|
2669
|
+
settle: async () => {}
|
|
2670
|
+
};
|
|
2671
|
+
let handedOff = false;
|
|
2672
|
+
const adoptAll = async () => {
|
|
2673
|
+
for (const lifecycle of lifecycles) await lifecycle.onAdopted();
|
|
2674
|
+
};
|
|
2675
|
+
return {
|
|
2676
|
+
lifecycle: {
|
|
2677
|
+
abortSignal: lifecycles.length === 1 ? firstLifecycle.abortSignal : AbortSignal.any(lifecycles.map((lifecycle) => lifecycle.abortSignal)),
|
|
2678
|
+
onAdopted: async () => {
|
|
2679
|
+
handedOff = true;
|
|
2680
|
+
await adoptAll();
|
|
2681
|
+
},
|
|
2682
|
+
onDeferred: () => {
|
|
2683
|
+
handedOff = true;
|
|
2684
|
+
for (const lifecycle of lifecycles) lifecycle.onDeferred();
|
|
2685
|
+
},
|
|
2686
|
+
onAdoptionFinalizing: () => {
|
|
2687
|
+
for (const lifecycle of lifecycles) lifecycle.onAdoptionFinalizing();
|
|
2688
|
+
},
|
|
2689
|
+
onAbandoned: async () => {
|
|
2690
|
+
handedOff = true;
|
|
2691
|
+
for (const lifecycle of lifecycles) await lifecycle.onAbandoned();
|
|
2692
|
+
}
|
|
2693
|
+
},
|
|
2694
|
+
settle: async () => {
|
|
2695
|
+
if (!handedOff) await adoptAll();
|
|
2696
|
+
}
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2806
2699
|
const inboundDebouncer = core.channel.debounce.createInboundDebouncer({
|
|
2807
2700
|
debounceMs: inboundDebounceMs,
|
|
2808
2701
|
buildKey: (entry) => {
|
|
@@ -2819,29 +2712,40 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2819
2712
|
onFlush: async (entries) => {
|
|
2820
2713
|
const last = entries.at(-1);
|
|
2821
2714
|
if (!last) return;
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2715
|
+
const { lifecycle, settle } = buildFlushIngressLifecycle(entries);
|
|
2716
|
+
try {
|
|
2717
|
+
if (entries.length === 1) await handleTeamsMessageNow(lifecycle ? {
|
|
2718
|
+
...last,
|
|
2719
|
+
turnAdoptionLifecycle: lifecycle
|
|
2720
|
+
} : last);
|
|
2721
|
+
else {
|
|
2722
|
+
const combinedText = entries.map((entry) => entry.text).filter(Boolean).join("\n");
|
|
2723
|
+
if (combinedText.trim()) {
|
|
2724
|
+
const combinedRawText = entries.map((entry) => entry.rawText).filter(Boolean).join("\n");
|
|
2725
|
+
const wasMentioned = entries.some((entry) => entry.wasMentioned);
|
|
2726
|
+
const implicitMentionKinds = entries.flatMap((entry) => entry.implicitMentionKinds);
|
|
2727
|
+
await handleTeamsMessageNow({
|
|
2728
|
+
context: last.context,
|
|
2729
|
+
rawText: combinedRawText,
|
|
2730
|
+
text: combinedText,
|
|
2731
|
+
attachments: [],
|
|
2732
|
+
wasMentioned,
|
|
2733
|
+
implicitMentionKinds,
|
|
2734
|
+
...lifecycle ? { turnAdoptionLifecycle: lifecycle } : {}
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
await settle();
|
|
2739
|
+
} catch (err) {
|
|
2740
|
+
await lifecycle?.onAbandoned();
|
|
2741
|
+
throw err;
|
|
2825
2742
|
}
|
|
2826
|
-
const combinedText = entries.map((entry) => entry.text).filter(Boolean).join("\n");
|
|
2827
|
-
if (!combinedText.trim()) return;
|
|
2828
|
-
const combinedRawText = entries.map((entry) => entry.rawText).filter(Boolean).join("\n");
|
|
2829
|
-
const wasMentioned = entries.some((entry) => entry.wasMentioned);
|
|
2830
|
-
const implicitMentionKinds = entries.flatMap((entry) => entry.implicitMentionKinds);
|
|
2831
|
-
await handleTeamsMessageNow({
|
|
2832
|
-
context: last.context,
|
|
2833
|
-
rawText: combinedRawText,
|
|
2834
|
-
text: combinedText,
|
|
2835
|
-
attachments: [],
|
|
2836
|
-
wasMentioned,
|
|
2837
|
-
implicitMentionKinds
|
|
2838
|
-
});
|
|
2839
2743
|
},
|
|
2840
2744
|
onError: (err) => {
|
|
2841
2745
|
runtime.error(`msteams debounce flush failed: ${require_setup_surface.formatUnknownError(err)}`);
|
|
2842
2746
|
}
|
|
2843
2747
|
});
|
|
2844
|
-
return async function handleTeamsMessage(context) {
|
|
2748
|
+
return async function handleTeamsMessage(context, turnAdoptionLifecycle) {
|
|
2845
2749
|
const activity = context.activity;
|
|
2846
2750
|
const attachments = Array.isArray(activity.attachments) ? activity.attachments : [];
|
|
2847
2751
|
const rawText = activity.text?.trim() ?? "";
|
|
@@ -2861,8 +2765,10 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2861
2765
|
text,
|
|
2862
2766
|
attachments,
|
|
2863
2767
|
wasMentioned,
|
|
2864
|
-
implicitMentionKinds
|
|
2768
|
+
implicitMentionKinds,
|
|
2769
|
+
turnAdoptionLifecycle
|
|
2865
2770
|
});
|
|
2771
|
+
if (turnAdoptionLifecycle) return { kind: "deferred" };
|
|
2866
2772
|
};
|
|
2867
2773
|
}
|
|
2868
2774
|
//#endregion
|
|
@@ -3059,29 +2965,37 @@ function registerMSTeamsHandlers(handler, deps) {
|
|
|
3059
2965
|
const handleTeamsMessage = createMSTeamsMessageHandler(deps);
|
|
3060
2966
|
const handleReaction = createMSTeamsReactionHandler(deps);
|
|
3061
2967
|
const originalRun = handler.run;
|
|
3062
|
-
if (originalRun) handler.run = async (context) => {
|
|
2968
|
+
if (originalRun) handler.run = async (context, turnAdoptionLifecycle) => {
|
|
3063
2969
|
const ctx = context;
|
|
3064
2970
|
if (ctx.activity?.type === "invoke" && ctx.activity?.name === "adaptiveCard/action") {
|
|
3065
2971
|
const text = serializeMSTeamsAdaptiveCardActionValue(ctx.activity?.value);
|
|
3066
|
-
if (text) await handleTeamsMessage({
|
|
2972
|
+
if (text) return await handleTeamsMessage({
|
|
3067
2973
|
...ctx,
|
|
3068
2974
|
activity: {
|
|
3069
2975
|
...ctx.activity,
|
|
3070
2976
|
type: "message",
|
|
3071
2977
|
text
|
|
3072
2978
|
}
|
|
3073
|
-
});
|
|
2979
|
+
}, turnAdoptionLifecycle);
|
|
3074
2980
|
return;
|
|
3075
2981
|
}
|
|
3076
|
-
return originalRun.call(handler, context);
|
|
2982
|
+
return originalRun.call(handler, context, turnAdoptionLifecycle);
|
|
3077
2983
|
};
|
|
3078
|
-
handler.onMessage(async (context, next) => {
|
|
2984
|
+
handler.onMessage(async (context, next, turnAdoptionLifecycle) => {
|
|
2985
|
+
let nextRan = false;
|
|
2986
|
+
const runNext = async () => {
|
|
2987
|
+
nextRan = true;
|
|
2988
|
+
await next();
|
|
2989
|
+
};
|
|
3079
2990
|
try {
|
|
3080
|
-
await handleTeamsMessage(context);
|
|
2991
|
+
const result = await handleTeamsMessage(context, turnAdoptionLifecycle);
|
|
2992
|
+
await runNext();
|
|
2993
|
+
return result;
|
|
3081
2994
|
} catch (err) {
|
|
2995
|
+
if (turnAdoptionLifecycle) throw err;
|
|
3082
2996
|
deps.runtime.error(`msteams handler failed: ${require_setup_surface.formatUnknownError(err)}`);
|
|
3083
2997
|
}
|
|
3084
|
-
await
|
|
2998
|
+
if (!nextRan) await runNext();
|
|
3085
2999
|
});
|
|
3086
3000
|
handler.onMembersAdded(async (context, next) => {
|
|
3087
3001
|
const ctx = context;
|
|
@@ -3182,8 +3096,7 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
|
|
|
3182
3096
|
const convType = (0, openclaw_plugin_sdk_string_coerce_runtime.normalizeOptionalLowercaseString)(activity.conversation?.conversationType);
|
|
3183
3097
|
const isDirectMessage = convType === "personal" || !convType && !activity.conversation?.isGroup;
|
|
3184
3098
|
const isChannel = convType === "channel";
|
|
3185
|
-
const
|
|
3186
|
-
const route = core.channel.routing.resolveAgentRoute({
|
|
3099
|
+
const route = require_runtime.getMSTeamsRuntime().channel.routing.resolveAgentRoute({
|
|
3187
3100
|
cfg: deps.cfg,
|
|
3188
3101
|
channel: "msteams",
|
|
3189
3102
|
peer: {
|
|
@@ -3212,13 +3125,12 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
|
|
|
3212
3125
|
hasComment: Boolean(userComment)
|
|
3213
3126
|
});
|
|
3214
3127
|
try {
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
}).catch(() => {});
|
|
3128
|
+
await (0, openclaw_plugin_sdk_channel_inbound.recordChannelFeedbackEvent)({
|
|
3129
|
+
cfg: deps.cfg,
|
|
3130
|
+
agentId: route.agentId,
|
|
3131
|
+
sessionKey: route.sessionKey,
|
|
3132
|
+
event: feedbackEvent
|
|
3133
|
+
});
|
|
3222
3134
|
} catch {}
|
|
3223
3135
|
const conversationRef = {
|
|
3224
3136
|
activityId: activity.id,
|
|
@@ -3243,12 +3155,11 @@ async function runMSTeamsFeedbackInvokeHandler(context, deps) {
|
|
|
3243
3155
|
if (isNegative && msteamsCfg?.feedbackReflection !== false) runFeedbackReflection({
|
|
3244
3156
|
cfg: deps.cfg,
|
|
3245
3157
|
app: deps.app,
|
|
3246
|
-
appId: deps.appId,
|
|
3247
3158
|
conversationRef,
|
|
3248
3159
|
sessionKey: route.sessionKey,
|
|
3249
3160
|
agentId: route.agentId,
|
|
3250
3161
|
conversationId,
|
|
3251
|
-
|
|
3162
|
+
conversationKind: isDirectMessage ? "direct" : isChannel ? "channel" : "group",
|
|
3252
3163
|
userComment,
|
|
3253
3164
|
log: deps.log
|
|
3254
3165
|
}).catch((err) => {
|
|
@@ -3366,6 +3277,175 @@ async function runMSTeamsFileConsentInvokeHandler(context, log) {
|
|
|
3366
3277
|
}
|
|
3367
3278
|
}
|
|
3368
3279
|
//#endregion
|
|
3280
|
+
//#region extensions/msteams/src/msteams-ingress.ts
|
|
3281
|
+
const MSTEAMS_INGRESS_VERSION = 1;
|
|
3282
|
+
const MSTEAMS_INGRESS_DRAIN_INTERVAL_MS = 500;
|
|
3283
|
+
const MSTEAMS_INGRESS_MAX_CONCURRENT_DELIVERIES = 8;
|
|
3284
|
+
const MSTEAMS_INGRESS_SCAN_LIMIT = 100;
|
|
3285
|
+
const MSTEAMS_INGRESS_TOMBSTONE_TTL_MS = 720 * 60 * 6e4;
|
|
3286
|
+
const MSTEAMS_INGRESS_COMPLETED_MAX_ENTRIES = 2e4;
|
|
3287
|
+
const MSTEAMS_INGRESS_FAILED_MAX_ENTRIES = 4096;
|
|
3288
|
+
var MSTeamsIngressPayloadError = class extends Error {
|
|
3289
|
+
constructor(reason, message, options) {
|
|
3290
|
+
super(message, options);
|
|
3291
|
+
this.reason = reason;
|
|
3292
|
+
this.name = "MSTeamsIngressPayloadError";
|
|
3293
|
+
}
|
|
3294
|
+
};
|
|
3295
|
+
function nonEmptyString(value) {
|
|
3296
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
3297
|
+
}
|
|
3298
|
+
function isDispatchableActivity(activity) {
|
|
3299
|
+
return activity.type === "message" || activity.type === "invoke" && activity.name === "adaptiveCard/action";
|
|
3300
|
+
}
|
|
3301
|
+
function inspectMSTeamsIngressActivity(activity) {
|
|
3302
|
+
if (!isDispatchableActivity(activity)) return null;
|
|
3303
|
+
const eventId = nonEmptyString(activity.id);
|
|
3304
|
+
if (!eventId) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams dispatchable activity is missing activity.id.");
|
|
3305
|
+
const laneKey = nonEmptyString(activity.conversation?.id);
|
|
3306
|
+
if (!laneKey) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams dispatchable activity is missing conversation.id.");
|
|
3307
|
+
return {
|
|
3308
|
+
eventId,
|
|
3309
|
+
laneKey
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
function parseClaimedActivity(payload, claimedId) {
|
|
3313
|
+
if (payload.version !== MSTEAMS_INGRESS_VERSION || typeof payload.rawActivity !== "string" || !Number.isFinite(payload.receivedAt)) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams ingress payload is invalid.");
|
|
3314
|
+
let activity;
|
|
3315
|
+
try {
|
|
3316
|
+
activity = JSON.parse(payload.rawActivity);
|
|
3317
|
+
} catch (error) {
|
|
3318
|
+
throw new MSTeamsIngressPayloadError("invalid-json", "Microsoft Teams ingress activity JSON is invalid.", { cause: error });
|
|
3319
|
+
}
|
|
3320
|
+
if (!activity || typeof activity !== "object" || Array.isArray(activity)) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams ingress activity must be an object.");
|
|
3321
|
+
const parsed = activity;
|
|
3322
|
+
const facts = inspectMSTeamsIngressActivity(parsed);
|
|
3323
|
+
if (!facts) throw new MSTeamsIngressPayloadError("unsupported-activity", "Microsoft Teams ingress row is not an agent-turn activity.");
|
|
3324
|
+
if (facts.eventId !== claimedId) throw new MSTeamsIngressPayloadError("invalid-activity", "Microsoft Teams activity id changed after durable admission.");
|
|
3325
|
+
return parsed;
|
|
3326
|
+
}
|
|
3327
|
+
function errorText(error) {
|
|
3328
|
+
return error instanceof Error ? error.message : String(error);
|
|
3329
|
+
}
|
|
3330
|
+
function createMSTeamsIngress(options) {
|
|
3331
|
+
const queue = options.queue ?? require_runtime.getMSTeamsRuntime().state.openChannelIngressQueue({ accountId: options.accountId });
|
|
3332
|
+
const shutdown = new AbortController();
|
|
3333
|
+
const liveContexts = /* @__PURE__ */ new Map();
|
|
3334
|
+
const activeDeliveries = /* @__PURE__ */ new Set();
|
|
3335
|
+
const drain = (0, openclaw_plugin_sdk_channel_outbound.createChannelIngressDrain)({
|
|
3336
|
+
queue,
|
|
3337
|
+
abortSignal: shutdown.signal,
|
|
3338
|
+
orderBy: "received",
|
|
3339
|
+
scanLimit: MSTEAMS_INGRESS_SCAN_LIMIT,
|
|
3340
|
+
startLimit: MSTEAMS_INGRESS_MAX_CONCURRENT_DELIVERIES,
|
|
3341
|
+
retryPolicy: {
|
|
3342
|
+
maxAttempts: openclaw_plugin_sdk_channel_outbound.DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
|
|
3343
|
+
deadLetterMinAgeMs: openclaw_plugin_sdk_channel_outbound.DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS
|
|
3344
|
+
},
|
|
3345
|
+
resolveNonRetryableFailure: (error) => {
|
|
3346
|
+
if (error instanceof MSTeamsIngressPayloadError) return {
|
|
3347
|
+
reason: error.reason,
|
|
3348
|
+
message: error.message
|
|
3349
|
+
};
|
|
3350
|
+
return require_setup_surface.classifyMSTeamsSendError(error).kind === "auth" ? {
|
|
3351
|
+
reason: "authentication-failed",
|
|
3352
|
+
message: errorText(error)
|
|
3353
|
+
} : null;
|
|
3354
|
+
},
|
|
3355
|
+
onLog: (message) => options.runtime.error?.(`msteams: ${message}`),
|
|
3356
|
+
dispatchClaimedEvent: async (claimed, lifecycle) => {
|
|
3357
|
+
const activity = parseClaimedActivity(claimed.payload, claimed.id);
|
|
3358
|
+
const liveContext = liveContexts.get(claimed.id);
|
|
3359
|
+
liveContexts.delete(claimed.id);
|
|
3360
|
+
const delivery = Promise.resolve(options.dispatch(activity, lifecycle, liveContext));
|
|
3361
|
+
activeDeliveries.add(delivery);
|
|
3362
|
+
try {
|
|
3363
|
+
return await delivery;
|
|
3364
|
+
} finally {
|
|
3365
|
+
activeDeliveries.delete(delivery);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
});
|
|
3369
|
+
let running = false;
|
|
3370
|
+
let stopped = false;
|
|
3371
|
+
let drainRequested = false;
|
|
3372
|
+
let drainTask;
|
|
3373
|
+
let drainTimer;
|
|
3374
|
+
const requestDrain = () => {
|
|
3375
|
+
if (!running || shutdown.signal.aborted) return;
|
|
3376
|
+
drainRequested = true;
|
|
3377
|
+
if (drainTask) return;
|
|
3378
|
+
drainTask = (async () => {
|
|
3379
|
+
while (drainRequested && !shutdown.signal.aborted) {
|
|
3380
|
+
if (!running) break;
|
|
3381
|
+
drainRequested = false;
|
|
3382
|
+
await drain.drainOnce({ shouldStop: () => !running || shutdown.signal.aborted || activeDeliveries.size >= MSTEAMS_INGRESS_MAX_CONCURRENT_DELIVERIES });
|
|
3383
|
+
}
|
|
3384
|
+
})().catch((error) => {
|
|
3385
|
+
options.runtime.error?.(`msteams ingress drain failed: ${errorText(error)}`);
|
|
3386
|
+
}).finally(() => {
|
|
3387
|
+
drainTask = void 0;
|
|
3388
|
+
if (running && drainRequested && !shutdown.signal.aborted) requestDrain();
|
|
3389
|
+
});
|
|
3390
|
+
};
|
|
3391
|
+
return {
|
|
3392
|
+
accept: async (activity, liveContext) => {
|
|
3393
|
+
const facts = inspectMSTeamsIngressActivity(activity);
|
|
3394
|
+
if (!facts) return;
|
|
3395
|
+
await queue.prune({
|
|
3396
|
+
completedTtlMs: MSTEAMS_INGRESS_TOMBSTONE_TTL_MS,
|
|
3397
|
+
completedMaxEntries: MSTEAMS_INGRESS_COMPLETED_MAX_ENTRIES,
|
|
3398
|
+
failedTtlMs: MSTEAMS_INGRESS_TOMBSTONE_TTL_MS,
|
|
3399
|
+
failedMaxEntries: MSTEAMS_INGRESS_FAILED_MAX_ENTRIES
|
|
3400
|
+
});
|
|
3401
|
+
const receivedAt = Date.now();
|
|
3402
|
+
const installedLiveContext = Boolean(liveContext) && !liveContexts.has(facts.eventId);
|
|
3403
|
+
if (liveContext && installedLiveContext) liveContexts.set(facts.eventId, liveContext);
|
|
3404
|
+
const uninstallLiveContext = () => {
|
|
3405
|
+
if (installedLiveContext && liveContexts.get(facts.eventId) === liveContext) liveContexts.delete(facts.eventId);
|
|
3406
|
+
};
|
|
3407
|
+
let result;
|
|
3408
|
+
try {
|
|
3409
|
+
result = await queue.enqueue(facts.eventId, {
|
|
3410
|
+
version: MSTEAMS_INGRESS_VERSION,
|
|
3411
|
+
receivedAt,
|
|
3412
|
+
rawActivity: JSON.stringify(activity)
|
|
3413
|
+
}, {
|
|
3414
|
+
receivedAt,
|
|
3415
|
+
laneKey: facts.laneKey
|
|
3416
|
+
});
|
|
3417
|
+
} catch (error) {
|
|
3418
|
+
uninstallLiveContext();
|
|
3419
|
+
throw error;
|
|
3420
|
+
}
|
|
3421
|
+
if (!(result.kind === "accepted" || result.kind === "pending")) uninstallLiveContext();
|
|
3422
|
+
requestDrain();
|
|
3423
|
+
},
|
|
3424
|
+
start: () => {
|
|
3425
|
+
if (running || stopped) return;
|
|
3426
|
+
running = true;
|
|
3427
|
+
requestDrain();
|
|
3428
|
+
drainTimer = setInterval(requestDrain, MSTEAMS_INGRESS_DRAIN_INTERVAL_MS);
|
|
3429
|
+
drainTimer.unref?.();
|
|
3430
|
+
},
|
|
3431
|
+
stop: async () => {
|
|
3432
|
+
if (stopped) return;
|
|
3433
|
+
stopped = true;
|
|
3434
|
+
running = false;
|
|
3435
|
+
if (drainTimer) {
|
|
3436
|
+
clearInterval(drainTimer);
|
|
3437
|
+
drainTimer = void 0;
|
|
3438
|
+
}
|
|
3439
|
+
await drainTask;
|
|
3440
|
+
await Promise.allSettled(activeDeliveries);
|
|
3441
|
+
await drain.waitForIdle();
|
|
3442
|
+
shutdown.abort();
|
|
3443
|
+
drain.dispose();
|
|
3444
|
+
liveContexts.clear();
|
|
3445
|
+
}
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
//#endregion
|
|
3369
3449
|
//#region extensions/msteams/src/webhook-timeouts.ts
|
|
3370
3450
|
const MSTEAMS_WEBHOOK_INACTIVITY_TIMEOUT_MS = 3e4;
|
|
3371
3451
|
const MSTEAMS_WEBHOOK_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -3548,6 +3628,15 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3548
3628
|
log
|
|
3549
3629
|
};
|
|
3550
3630
|
registerMSTeamsHandlers(handler, handlerDeps);
|
|
3631
|
+
const ingress = createMSTeamsIngress({
|
|
3632
|
+
accountId: appId,
|
|
3633
|
+
runtime,
|
|
3634
|
+
dispatch: async (activity, lifecycle, liveContext) => {
|
|
3635
|
+
if (liveContext) liveContext.activity = activity;
|
|
3636
|
+
const context = liveContext ?? createMSTeamsReplayContext(activity, app, require_resolve_allowlist.resolveMSTeamsSdkCloudOptions(msteamsCfg));
|
|
3637
|
+
return await handler.run(context, lifecycle);
|
|
3638
|
+
}
|
|
3639
|
+
});
|
|
3551
3640
|
app.on("card.action", async (ctx) => {
|
|
3552
3641
|
const adaptedCtx = adaptSdkContext(ctx, app);
|
|
3553
3642
|
try {
|
|
@@ -3624,9 +3713,7 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3624
3713
|
};
|
|
3625
3714
|
}
|
|
3626
3715
|
}
|
|
3627
|
-
|
|
3628
|
-
log.error("msteams card.action dispatch failed", { error: require_setup_surface.formatUnknownError(err) });
|
|
3629
|
-
});
|
|
3716
|
+
await ingress.accept(activity, adaptedCtx);
|
|
3630
3717
|
return {
|
|
3631
3718
|
statusCode: 200,
|
|
3632
3719
|
type: "application/vnd.microsoft.activity.message",
|
|
@@ -3708,24 +3795,30 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3708
3795
|
if (!await runMSTeamsFeedbackInvokeHandler(adaptSdkContext(ctx, app), handlerDeps)) await ctx.next?.call(ctx);
|
|
3709
3796
|
});
|
|
3710
3797
|
app.on("activity", async (ctx) => {
|
|
3798
|
+
const adaptedCtx = adaptSdkContext(ctx, app);
|
|
3799
|
+
const activity = adaptedCtx.activity;
|
|
3800
|
+
if (activity?.type === "invoke") {
|
|
3801
|
+
if (activity?.name === "adaptiveCard/action") return;
|
|
3802
|
+
if (activity?.name === "fileConsent/invoke") return;
|
|
3803
|
+
if (activity?.name === "signin/tokenExchange" || activity?.name === "signin/verifyState") return;
|
|
3804
|
+
}
|
|
3805
|
+
if (activity?.type === "message") {
|
|
3806
|
+
await ingress.accept(activity, adaptedCtx);
|
|
3807
|
+
return;
|
|
3808
|
+
}
|
|
3711
3809
|
try {
|
|
3712
|
-
const adaptedCtx = adaptSdkContext(ctx, app);
|
|
3713
|
-
const activity = adaptedCtx.activity;
|
|
3714
|
-
if (activity?.type === "invoke") {
|
|
3715
|
-
if (activity?.name === "adaptiveCard/action") return;
|
|
3716
|
-
if (activity?.name === "fileConsent/invoke") return;
|
|
3717
|
-
if (activity?.name === "signin/tokenExchange" || activity?.name === "signin/verifyState") return;
|
|
3718
|
-
}
|
|
3719
3810
|
await handler.run(adaptedCtx);
|
|
3720
3811
|
} catch (err) {
|
|
3721
|
-
log.error("msteams
|
|
3812
|
+
log.error("msteams non-turn activity failed", { error: require_setup_surface.formatUnknownError(err) });
|
|
3722
3813
|
}
|
|
3723
3814
|
});
|
|
3724
3815
|
await app.initialize();
|
|
3816
|
+
ingress.start();
|
|
3725
3817
|
const httpServer = await new Promise((resolve, reject) => {
|
|
3726
3818
|
const server = expressApp.listen(port, (err) => err ? reject(err) : resolve(server));
|
|
3727
|
-
}).catch((err) => {
|
|
3819
|
+
}).catch(async (err) => {
|
|
3728
3820
|
log.error("msteams server error", { error: require_setup_surface.formatUnknownError(err) });
|
|
3821
|
+
await ingress.stop();
|
|
3729
3822
|
throw err;
|
|
3730
3823
|
});
|
|
3731
3824
|
log.info(`msteams provider started on port ${port}`);
|
|
@@ -3735,12 +3828,13 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3735
3828
|
});
|
|
3736
3829
|
const shutdown = async () => {
|
|
3737
3830
|
log.info("shutting down msteams provider");
|
|
3738
|
-
|
|
3831
|
+
await new Promise((resolve) => {
|
|
3739
3832
|
httpServer.close((err) => {
|
|
3740
3833
|
if (err) log.debug?.("msteams server close error", { error: require_setup_surface.formatUnknownError(err) });
|
|
3741
3834
|
resolve();
|
|
3742
3835
|
});
|
|
3743
3836
|
});
|
|
3837
|
+
await ingress.stop();
|
|
3744
3838
|
};
|
|
3745
3839
|
await (0, openclaw_plugin_sdk_channel_outbound.keepHttpServerTaskAlive)({
|
|
3746
3840
|
server: httpServer,
|
|
@@ -3778,11 +3872,14 @@ function buildActivityHandler() {
|
|
|
3778
3872
|
reactionsRemovedHandlers.push(cb);
|
|
3779
3873
|
return handler;
|
|
3780
3874
|
},
|
|
3781
|
-
async run(context) {
|
|
3875
|
+
async run(context, turnAdoptionLifecycle) {
|
|
3782
3876
|
const ctx = context;
|
|
3783
3877
|
const activityType = ctx?.activity?.type;
|
|
3784
3878
|
const noop = async () => {};
|
|
3785
|
-
if (activityType === "message") for (const h of messageHandlers)
|
|
3879
|
+
if (activityType === "message") for (const h of messageHandlers) {
|
|
3880
|
+
const result = await h(context, noop, turnAdoptionLifecycle);
|
|
3881
|
+
if (result) return result;
|
|
3882
|
+
}
|
|
3786
3883
|
else if (activityType === "conversationUpdate") for (const h of membersAddedHandlers) await h(context, noop);
|
|
3787
3884
|
else if (activityType === "messageReaction") {
|
|
3788
3885
|
const activity = ctx?.activity;
|
|
@@ -3793,6 +3890,47 @@ function buildActivityHandler() {
|
|
|
3793
3890
|
};
|
|
3794
3891
|
return handler;
|
|
3795
3892
|
}
|
|
3893
|
+
function createMSTeamsReplayContext(activity, app, serviceUrlBoundary) {
|
|
3894
|
+
const rawConversationId = activity.conversation?.id ?? "";
|
|
3895
|
+
const conversationId = require_channel.normalizeMSTeamsConversationId(rawConversationId);
|
|
3896
|
+
const conversationType = activity.conversation?.conversationType ?? "personal";
|
|
3897
|
+
const threadActivityId = conversationType.toLowerCase() === "channel" ? require_channel.extractMSTeamsConversationMessageId(rawConversationId) ?? activity.replyToId : void 0;
|
|
3898
|
+
const tenantId = activity.channelData?.tenant?.id ?? activity.conversation?.tenantId;
|
|
3899
|
+
const reference = {
|
|
3900
|
+
activityId: activity.id,
|
|
3901
|
+
user: activity.from,
|
|
3902
|
+
agent: activity.recipient,
|
|
3903
|
+
conversation: {
|
|
3904
|
+
id: conversationId,
|
|
3905
|
+
conversationType,
|
|
3906
|
+
...tenantId ? { tenantId } : {}
|
|
3907
|
+
},
|
|
3908
|
+
channelId: activity.channelId,
|
|
3909
|
+
serviceUrl: activity.serviceUrl,
|
|
3910
|
+
locale: activity.locale,
|
|
3911
|
+
...tenantId ? { tenantId } : {},
|
|
3912
|
+
...activity.from?.aadObjectId ? { aadObjectId: activity.from.aadObjectId } : {}
|
|
3913
|
+
};
|
|
3914
|
+
const proactiveOptions = {
|
|
3915
|
+
...threadActivityId ? { threadActivityId } : {},
|
|
3916
|
+
serviceUrlBoundary
|
|
3917
|
+
};
|
|
3918
|
+
const sendActivity = (outbound) => require_messenger.sendMSTeamsActivityWithReference(app, reference, outbound, proactiveOptions);
|
|
3919
|
+
return {
|
|
3920
|
+
activity,
|
|
3921
|
+
sendActivity,
|
|
3922
|
+
sendActivities: async (activities) => {
|
|
3923
|
+
const results = [];
|
|
3924
|
+
for (const outbound of activities) results.push(await sendActivity(outbound));
|
|
3925
|
+
return results;
|
|
3926
|
+
},
|
|
3927
|
+
updateActivity: async (outbound) => await require_messenger.updateMSTeamsActivityWithReference(app, reference, typeof outbound.id === "string" ? outbound.id : "", outbound, proactiveOptions),
|
|
3928
|
+
deleteActivity: async (activityId) => {
|
|
3929
|
+
await require_messenger.deleteMSTeamsActivityWithReference(app, reference, activityId, proactiveOptions);
|
|
3930
|
+
},
|
|
3931
|
+
getTeamDetails: (teamId) => app.api.teams.getById(teamId)
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
3796
3934
|
/**
|
|
3797
3935
|
* Adapt a new @microsoft/teams.apps SDK context to the MSTeamsTurnContext interface
|
|
3798
3936
|
* our handlers expect. The new SDK uses reply()/send() instead of sendActivity().
|