@openclaw/msteams 2026.5.7 → 2026.5.10-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/api.js +2 -2
- package/dist/{channel-BOwKBAvY.js → channel-kz4Rzh2w.js} +45 -20
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-BC1ruIfN.js → channel.runtime-C1IGCVBT.js} +3 -3
- package/dist/{graph-users-9uQJepqr.js → graph-users-CCU0WVMZ.js} +44 -24
- package/dist/{policy-DTnU2GR7.js → policy-bM71GXRd.js} +2 -10
- package/dist/{probe-D_H8yFps.js → probe-CQKmxtlj.js} +83 -61
- package/dist/{resolve-allowlist-D41JSziq.js → resolve-allowlist-7CHP2hEA.js} +1 -1
- package/dist/{runtime-api-DV1iVMn1.js → runtime-api-C3EIaIpt.js} +3 -3
- package/dist/runtime-api.js +2 -2
- package/dist/setup-plugin-api.js +2 -2
- package/dist/{setup-surface-BLkFQYIQ.js → setup-surface-C7d0jGM5.js} +2 -2
- package/dist/{src-CP7V_TeZ.js → src-DQ7ARgDd.js} +238 -221
- package/dist/test-api.js +1 -1
- package/package.json +7 -7
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { A as ATTACHMENT_TAG_RE, B as isLikelyImageAttachment, C as loadMSTeamsSdkWithAuth, D as formatMSTeamsSendErrorHint, E as classifyMSTeamsSendError, F as estimateBase64DecodedBytes, G as resolveAttachmentFetchPolicy, H as isUrlAllowed, I as extractHtmlFromAttachment, J as safeFetchWithPolicy, K as resolveMediaSsrfPolicy, L as extractInlineImageCandidates, M as IMG_SRC_RE, N as applyAuthorizationHeaderForUrl, O as formatUnknownError, P as encodeGraphShareId, R as inferPlaceholder, S as createMSTeamsTokenProvider, T as ensureUserAgentHeader, U as normalizeContentType, V as isRecord, W as readNestedString, X as tryBuildGraphSharesUrlForSharedLink, Y as safeHostForUrl, _ as resolveMSTeamsStorePath, a as fetchGraphJson, b as createBotFrameworkJwtValidator, h as resolveMSTeamsCredentials, j as GRAPH_ROOT, q as resolveRequestUrl, w as buildUserAgent, x as createMSTeamsAdapter, z as isDownloadableAttachment } from "./graph-users-
|
|
3
|
-
import { c as resolveMSTeamsUserAllowlist, s as resolveMSTeamsChannelAllowlist } from "./resolve-allowlist-
|
|
4
|
-
import {
|
|
5
|
-
import { C as readJsonFile, S as createMSTeamsConversationStoreFs, T as writeJsonFile, _ as buildFileInfoCard, b as createMSTeamsPollStoreFs, c as renderReplyPayloadsToMessages, d as withRevokedProxyFallback, f as resolveGraphChatId, g as removePendingUploadFs, h as getPendingUploadFs, l as sendMSTeamsMessages, m as removePendingUpload, p as getPendingUpload, s as buildConversationReference, u as AI_GENERATED_ENTITY, v as parseFileConsentInvoke, w as withFileLock, x as extractMSTeamsPollVote, y as uploadToConsentUrl } from "./probe-
|
|
1
|
+
import { A as summarizeMapping, D as resolveDefaultGroupPolicy, E as resolveChannelMediaMaxBytes, M as getMSTeamsRuntime, N as getOptionalMSTeamsRuntime, _ as isDangerousNameMatchingEnabled, a as buildMediaPayload, b as logTypingFailure, c as createChannelMessageReplyPipeline, f as dispatchReplyFromConfigWithSettledDispatcher$1, l as createChannelPairingController, n as DEFAULT_WEBHOOK_MAX_BODY_BYTES, t as DEFAULT_ACCOUNT_ID, v as keepHttpServerTaskAlive, x as mergeAllowlist } from "./runtime-api-C3EIaIpt.js";
|
|
2
|
+
import { A as ATTACHMENT_TAG_RE, B as isLikelyImageAttachment, C as loadMSTeamsSdkWithAuth, D as formatMSTeamsSendErrorHint, E as classifyMSTeamsSendError, F as estimateBase64DecodedBytes, G as resolveAttachmentFetchPolicy, H as isUrlAllowed, I as extractHtmlFromAttachment, J as safeFetchWithPolicy, K as resolveMediaSsrfPolicy, L as extractInlineImageCandidates, M as IMG_SRC_RE, N as applyAuthorizationHeaderForUrl, O as formatUnknownError, P as encodeGraphShareId, R as inferPlaceholder, S as createMSTeamsTokenProvider, T as ensureUserAgentHeader, U as normalizeContentType, V as isRecord$1, W as readNestedString, X as tryBuildGraphSharesUrlForSharedLink, Y as safeHostForUrl, _ as resolveMSTeamsStorePath, a as fetchGraphJson, b as createBotFrameworkJwtValidator, h as resolveMSTeamsCredentials, j as GRAPH_ROOT, q as resolveRequestUrl, w as buildUserAgent, x as createMSTeamsAdapter, z as isDownloadableAttachment } from "./graph-users-CCU0WVMZ.js";
|
|
3
|
+
import { c as resolveMSTeamsUserAllowlist, s as resolveMSTeamsChannelAllowlist } from "./resolve-allowlist-7CHP2hEA.js";
|
|
4
|
+
import { i as resolveMSTeamsRouteConfig, r as resolveMSTeamsReplyPolicy, t as resolveMSTeamsAllowlistMatch } from "./policy-bM71GXRd.js";
|
|
5
|
+
import { C as readJsonFile, S as createMSTeamsConversationStoreFs, T as writeJsonFile, _ as buildFileInfoCard, b as createMSTeamsPollStoreFs, c as renderReplyPayloadsToMessages, d as withRevokedProxyFallback, f as resolveGraphChatId, g as removePendingUploadFs, h as getPendingUploadFs, l as sendMSTeamsMessages, m as removePendingUpload, p as getPendingUpload, s as buildConversationReference, u as AI_GENERATED_ENTITY, v as parseFileConsentInvoke, w as withFileLock, x as extractMSTeamsPollVote, y as uploadToConsentUrl } from "./probe-CQKmxtlj.js";
|
|
6
6
|
import { formatAllowlistMatchMeta } from "openclaw/plugin-sdk/allow-from";
|
|
7
|
+
import { createLiveMessageState, createPreviewMessageReceipt, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, markLiveMessageFinalized } from "openclaw/plugin-sdk/channel-message";
|
|
7
8
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/text-runtime";
|
|
8
9
|
import { createDraftStreamLoop } from "openclaw/plugin-sdk/channel-lifecycle";
|
|
9
10
|
import { readResponseWithLimit } from "openclaw/plugin-sdk/media-runtime";
|
|
@@ -11,14 +12,15 @@ import { dispatchReplyFromConfigWithSettledDispatcher, hasFinalInboundReplyDispa
|
|
|
11
12
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
12
13
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
13
14
|
import path from "node:path";
|
|
14
|
-
import
|
|
15
|
+
import { appendRegularFile } from "openclaw/plugin-sdk/security-runtime";
|
|
16
|
+
import { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
|
|
15
17
|
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
|
18
|
+
import fs from "node:fs/promises";
|
|
19
|
+
import { channelIngressRoutes, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
16
20
|
import { logInboundDrop, resolveInboundMentionDecision, resolveInboundSessionEnvelopeContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
17
|
-
import { resolveDualTextControlCommandGate } from "openclaw/plugin-sdk/command-gating";
|
|
18
21
|
import { filterSupplementalContextItems, resolveChannelContextVisibilityMode, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
19
|
-
import { evaluateSenderGroupAccessForPolicy } from "openclaw/plugin-sdk/group-access";
|
|
20
22
|
import { DEFAULT_GROUP_HISTORY_LIMIT, buildPendingHistoryContextFromMap, recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history";
|
|
21
|
-
import {
|
|
23
|
+
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelProgressDraftGate, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, resolveChannelPreviewStreamMode, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress } from "openclaw/plugin-sdk/channel-streaming";
|
|
22
24
|
//#region extensions/msteams/src/feedback-reflection-prompt.ts
|
|
23
25
|
/** Max chars of the thumbed-down response to include in the reflection prompt. */
|
|
24
26
|
const MAX_RESPONSE_CHARS = 500;
|
|
@@ -126,8 +128,7 @@ async function storeSessionLearning(params) {
|
|
|
126
128
|
let learnings = exists ? existingLearnings : legacyLearnings;
|
|
127
129
|
learnings.push(params.learning);
|
|
128
130
|
if (learnings.length > 10) learnings = learnings.slice(-10);
|
|
129
|
-
await
|
|
130
|
-
await fs.writeFile(learningsFile, JSON.stringify(learnings, null, 2), "utf-8");
|
|
131
|
+
await writeJsonFileAtomically(learningsFile, learnings);
|
|
131
132
|
if (!exists && legacyLearningsFile !== learningsFile) await fs.rm(legacyLearningsFile, { force: true }).catch(() => void 0);
|
|
132
133
|
}
|
|
133
134
|
//#endregion
|
|
@@ -490,6 +491,22 @@ async function respondToMSTeamsFileConsentInvoke(context, log) {
|
|
|
490
491
|
}
|
|
491
492
|
//#endregion
|
|
492
493
|
//#region extensions/msteams/src/monitor-handler/access.ts
|
|
494
|
+
const msteamsIngressIdentity = {
|
|
495
|
+
key: "sender-id",
|
|
496
|
+
normalize: normalizeIngressValue,
|
|
497
|
+
aliases: [{
|
|
498
|
+
key: "sender-name",
|
|
499
|
+
kind: "plugin:msteams-sender-name",
|
|
500
|
+
normalizeEntry: normalizeIngressValue,
|
|
501
|
+
normalizeSubject: normalizeIngressValue,
|
|
502
|
+
dangerous: true
|
|
503
|
+
}],
|
|
504
|
+
isWildcardEntry: (entry) => normalizeIngressValue(entry) === "*",
|
|
505
|
+
resolveEntryId: ({ entryIndex, fieldKey }) => `msteams-entry-${entryIndex + 1}:${fieldKey === "sender-name" ? "name" : "id"}`
|
|
506
|
+
};
|
|
507
|
+
function normalizeIngressValue(value) {
|
|
508
|
+
return normalizeOptionalLowercaseString(value) ?? null;
|
|
509
|
+
}
|
|
493
510
|
async function resolveMSTeamsSenderAccess(params) {
|
|
494
511
|
const activity = params.activity;
|
|
495
512
|
const msteamsCfg = params.cfg.channels?.msteams;
|
|
@@ -504,23 +521,10 @@ async function resolveMSTeamsSenderAccess(params) {
|
|
|
504
521
|
accountId: DEFAULT_ACCOUNT_ID
|
|
505
522
|
});
|
|
506
523
|
const dmPolicy = msteamsCfg?.dmPolicy ?? "pairing";
|
|
507
|
-
const storedAllowFrom = await readStoreAllowFromForDmPolicy({
|
|
508
|
-
provider: "msteams",
|
|
509
|
-
accountId: pairing.accountId,
|
|
510
|
-
dmPolicy,
|
|
511
|
-
readStore: pairing.readStoreForDmPolicy
|
|
512
|
-
});
|
|
513
524
|
const configuredDmAllowFrom = msteamsCfg?.allowFrom ?? [];
|
|
514
525
|
const groupAllowFrom = msteamsCfg?.groupAllowFrom;
|
|
515
|
-
const resolvedAllowFromLists = resolveEffectiveAllowFromLists({
|
|
516
|
-
allowFrom: configuredDmAllowFrom,
|
|
517
|
-
groupAllowFrom,
|
|
518
|
-
storeAllowFrom: storedAllowFrom,
|
|
519
|
-
dmPolicy
|
|
520
|
-
});
|
|
521
526
|
const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg);
|
|
522
527
|
const groupPolicy = !isDirectMessage && msteamsCfg ? msteamsCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist" : "disabled";
|
|
523
|
-
const effectiveGroupAllowFrom = resolvedAllowFromLists.effectiveGroupAllowFrom;
|
|
524
528
|
const allowNameMatching = isDangerousNameMatchingEnabled(msteamsCfg);
|
|
525
529
|
const channelGate = resolveMSTeamsRouteConfig({
|
|
526
530
|
cfg: msteamsCfg,
|
|
@@ -530,49 +534,55 @@ async function resolveMSTeamsSenderAccess(params) {
|
|
|
530
534
|
channelName: activity.channelData?.channel?.name,
|
|
531
535
|
allowNameMatching
|
|
532
536
|
});
|
|
533
|
-
const senderGroupPolicy = channelGate.allowlistConfigured && effectiveGroupAllowFrom.length === 0 ? groupPolicy : resolveSenderScopedGroupPolicy({
|
|
534
|
-
groupPolicy,
|
|
535
|
-
groupAllowFrom: effectiveGroupAllowFrom
|
|
536
|
-
});
|
|
537
|
-
const access = resolveDmGroupAccessWithLists({
|
|
538
|
-
isGroup: !isDirectMessage,
|
|
539
|
-
dmPolicy,
|
|
540
|
-
groupPolicy: senderGroupPolicy,
|
|
541
|
-
allowFrom: configuredDmAllowFrom,
|
|
542
|
-
groupAllowFrom,
|
|
543
|
-
storeAllowFrom: storedAllowFrom,
|
|
544
|
-
groupAllowFromFallbackToAllowFrom: false,
|
|
545
|
-
isSenderAllowed: (allowFrom) => resolveMSTeamsAllowlistMatch({
|
|
546
|
-
allowFrom,
|
|
547
|
-
senderId,
|
|
548
|
-
senderName,
|
|
549
|
-
allowNameMatching
|
|
550
|
-
}).allowed
|
|
551
|
-
});
|
|
552
537
|
return {
|
|
553
|
-
|
|
538
|
+
...await resolveStableChannelMessageIngress({
|
|
539
|
+
channelId: "msteams",
|
|
540
|
+
accountId: pairing.accountId,
|
|
541
|
+
identity: msteamsIngressIdentity,
|
|
542
|
+
cfg: params.cfg,
|
|
543
|
+
readStoreAllowFrom: pairing.readAllowFromStore,
|
|
544
|
+
subject: {
|
|
545
|
+
stableId: senderId,
|
|
546
|
+
aliases: { "sender-name": senderName }
|
|
547
|
+
},
|
|
548
|
+
conversation: {
|
|
549
|
+
kind: isDirectMessage ? "direct" : convType === "channel" ? "channel" : "group",
|
|
550
|
+
id: conversationId,
|
|
551
|
+
parentId: activity.channelData?.team?.id
|
|
552
|
+
},
|
|
553
|
+
route: channelIngressRoutes(!isDirectMessage && channelGate.allowlistConfigured && {
|
|
554
|
+
id: "msteams:team-channel",
|
|
555
|
+
kind: "nestedAllowlist",
|
|
556
|
+
allowed: channelGate.allowed,
|
|
557
|
+
precedence: 0,
|
|
558
|
+
matchId: "msteams-route",
|
|
559
|
+
...channelGate.allowed && groupPolicy === "allowlist" ? {
|
|
560
|
+
senderPolicy: "deny-when-empty",
|
|
561
|
+
senderAllowFromSource: "effective-group"
|
|
562
|
+
} : {}
|
|
563
|
+
}),
|
|
564
|
+
dmPolicy,
|
|
565
|
+
groupPolicy,
|
|
566
|
+
policy: {
|
|
567
|
+
groupAllowFromFallbackToAllowFrom: true,
|
|
568
|
+
mutableIdentifierMatching: allowNameMatching ? "enabled" : "disabled"
|
|
569
|
+
},
|
|
570
|
+
allowFrom: configuredDmAllowFrom,
|
|
571
|
+
groupAllowFrom,
|
|
572
|
+
command: {
|
|
573
|
+
allowTextCommands: true,
|
|
574
|
+
hasControlCommand: params.hasControlCommand === true,
|
|
575
|
+
directGroupAllowFrom: isDirectMessage ? "effective" : "none"
|
|
576
|
+
}
|
|
577
|
+
}),
|
|
554
578
|
pairing,
|
|
555
579
|
isDirectMessage,
|
|
556
580
|
conversationId,
|
|
557
581
|
senderId,
|
|
558
582
|
senderName,
|
|
583
|
+
msteamsCfg,
|
|
559
584
|
dmPolicy,
|
|
560
585
|
channelGate,
|
|
561
|
-
access,
|
|
562
|
-
senderGroupAccess: evaluateSenderGroupAccessForPolicy$1({
|
|
563
|
-
groupPolicy,
|
|
564
|
-
groupAllowFrom: effectiveGroupAllowFrom,
|
|
565
|
-
senderId,
|
|
566
|
-
isSenderAllowed: (_senderId, allowFrom) => resolveMSTeamsAllowlistMatch({
|
|
567
|
-
allowFrom,
|
|
568
|
-
senderId,
|
|
569
|
-
senderName,
|
|
570
|
-
allowNameMatching
|
|
571
|
-
}).allowed
|
|
572
|
-
}),
|
|
573
|
-
configuredDmAllowFrom,
|
|
574
|
-
effectiveDmAllowFrom: access.effectiveAllowFrom,
|
|
575
|
-
effectiveGroupAllowFrom,
|
|
576
586
|
allowNameMatching,
|
|
577
587
|
groupPolicy
|
|
578
588
|
};
|
|
@@ -842,7 +852,7 @@ function resolveDownloadCandidate(att) {
|
|
|
842
852
|
const contentType = normalizeContentType(att.contentType);
|
|
843
853
|
const name = normalizeOptionalString(att.name) ?? "";
|
|
844
854
|
if (contentType === "application/vnd.microsoft.teams.file.download.info") {
|
|
845
|
-
if (!isRecord(att.content)) return null;
|
|
855
|
+
if (!isRecord$1(att.content)) return null;
|
|
846
856
|
const downloadUrl = normalizeOptionalString(att.content.downloadUrl) ?? "";
|
|
847
857
|
if (!downloadUrl) return null;
|
|
848
858
|
const fileType = normalizeOptionalString(att.content.fileType) ?? "";
|
|
@@ -1612,6 +1622,7 @@ var TeamsHttpStream = class {
|
|
|
1612
1622
|
this.finalized = false;
|
|
1613
1623
|
this.streamFailed = false;
|
|
1614
1624
|
this.lastStreamedText = "";
|
|
1625
|
+
this.finalMessageId = void 0;
|
|
1615
1626
|
this.streamStartedAt = void 0;
|
|
1616
1627
|
this.sendActivity = options.sendActivity;
|
|
1617
1628
|
this.feedbackLoopEnabled = options.feedbackLoopEnabled ?? false;
|
|
@@ -1678,22 +1689,23 @@ var TeamsHttpStream = class {
|
|
|
1678
1689
|
* Finalize the stream — send the final message activity.
|
|
1679
1690
|
*/
|
|
1680
1691
|
async finalize() {
|
|
1681
|
-
if (this.finalized) return;
|
|
1692
|
+
if (this.finalized) return this.finalMessageId;
|
|
1682
1693
|
this.finalized = true;
|
|
1683
1694
|
this.stopped = true;
|
|
1684
1695
|
this.loop.stop();
|
|
1685
1696
|
await this.loop.waitForInFlight();
|
|
1686
|
-
if (!this.accumulatedText.trim()) return;
|
|
1697
|
+
if (!this.accumulatedText.trim()) return this.finalMessageId;
|
|
1687
1698
|
if (this.streamFailed) {
|
|
1688
1699
|
if (this.streamId) try {
|
|
1689
|
-
await this.sendActivity({
|
|
1700
|
+
const response = await this.sendActivity({
|
|
1690
1701
|
type: "message",
|
|
1691
1702
|
text: this.lastStreamedText || "",
|
|
1692
1703
|
channelData: { feedbackLoopEnabled: this.feedbackLoopEnabled },
|
|
1693
1704
|
entities: [AI_GENERATED_ENTITY, buildStreamInfoEntity(this.streamId, "final")]
|
|
1694
1705
|
});
|
|
1706
|
+
this.finalMessageId = extractId(response);
|
|
1695
1707
|
} catch {}
|
|
1696
|
-
return;
|
|
1708
|
+
return this.finalMessageId;
|
|
1697
1709
|
}
|
|
1698
1710
|
try {
|
|
1699
1711
|
const entities = [AI_GENERATED_ENTITY];
|
|
@@ -1704,11 +1716,13 @@ var TeamsHttpStream = class {
|
|
|
1704
1716
|
channelData: { feedbackLoopEnabled: this.feedbackLoopEnabled },
|
|
1705
1717
|
entities
|
|
1706
1718
|
};
|
|
1707
|
-
await this.sendActivity(finalActivity);
|
|
1719
|
+
const response = await this.sendActivity(finalActivity);
|
|
1720
|
+
this.finalMessageId = extractId(response);
|
|
1708
1721
|
} catch (err) {
|
|
1709
1722
|
this.streamFailed = true;
|
|
1710
1723
|
this.onError?.(err);
|
|
1711
1724
|
}
|
|
1725
|
+
return this.finalMessageId;
|
|
1712
1726
|
}
|
|
1713
1727
|
/** Whether streaming successfully delivered content (at least one chunk sent, not failed). */
|
|
1714
1728
|
get hasContent() {
|
|
@@ -1726,6 +1740,14 @@ var TeamsHttpStream = class {
|
|
|
1726
1740
|
get isFinalized() {
|
|
1727
1741
|
return this.finalized;
|
|
1728
1742
|
}
|
|
1743
|
+
/** Platform id returned by the final message activity, when available. */
|
|
1744
|
+
get messageId() {
|
|
1745
|
+
return this.finalMessageId;
|
|
1746
|
+
}
|
|
1747
|
+
/** Stream id returned by the first streaminfo activity, when available. */
|
|
1748
|
+
get previewStreamId() {
|
|
1749
|
+
return this.streamId;
|
|
1750
|
+
}
|
|
1729
1751
|
/** Whether streaming fell back (not used in this implementation). */
|
|
1730
1752
|
get isFallback() {
|
|
1731
1753
|
return false;
|
|
@@ -1781,6 +1803,13 @@ function createTeamsReplyStreamController(params) {
|
|
|
1781
1803
|
let progressLines = [];
|
|
1782
1804
|
let lastInformativeText = "";
|
|
1783
1805
|
let pendingFinalize;
|
|
1806
|
+
let liveState = createLiveMessageState({ canFinalizeInPlace: Boolean(stream) });
|
|
1807
|
+
const markStreamFinalized = () => {
|
|
1808
|
+
if (!stream || stream.isFailed) return;
|
|
1809
|
+
const messageId = stream.messageId ?? stream.previewStreamId;
|
|
1810
|
+
if (!messageId) return;
|
|
1811
|
+
liveState = markLiveMessageFinalized(liveState, createPreviewMessageReceipt({ id: messageId }));
|
|
1812
|
+
};
|
|
1784
1813
|
const renderInformativeUpdate = async () => {
|
|
1785
1814
|
if (!stream) return;
|
|
1786
1815
|
const informativeText = formatChannelProgressDraftText({
|
|
@@ -1806,9 +1835,12 @@ function createTeamsReplyStreamController(params) {
|
|
|
1806
1835
|
if (!stream || streamMode !== "progress") return;
|
|
1807
1836
|
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
|
|
1808
1837
|
if (shouldStreamPreviewToolProgress) {
|
|
1809
|
-
const normalized = line
|
|
1838
|
+
const normalized = normalizeProgressLineIdentity(line);
|
|
1810
1839
|
if (normalized) {
|
|
1811
|
-
if (progressLines.at(-1) !== normalized)
|
|
1840
|
+
if (normalizeProgressLineIdentity(progressLines.at(-1)) !== normalized) {
|
|
1841
|
+
const progressLine = typeof line === "object" && line !== void 0 ? line : normalized;
|
|
1842
|
+
progressLines = [...progressLines, progressLine].slice(-resolveChannelProgressDraftMaxLines(params.msteamsConfig));
|
|
1843
|
+
}
|
|
1812
1844
|
}
|
|
1813
1845
|
}
|
|
1814
1846
|
await noteProgressWork();
|
|
@@ -1827,6 +1859,39 @@ function createTeamsReplyStreamController(params) {
|
|
|
1827
1859
|
text: remainingText
|
|
1828
1860
|
};
|
|
1829
1861
|
};
|
|
1862
|
+
const finalizeProgressPayload = async (payload, hasMedia) => {
|
|
1863
|
+
if (!stream || !payload.text) return payload;
|
|
1864
|
+
return (await deliverWithFinalizableLivePreviewAdapter({
|
|
1865
|
+
kind: "final",
|
|
1866
|
+
payload,
|
|
1867
|
+
liveState,
|
|
1868
|
+
adapter: defineFinalizableLivePreviewAdapter({
|
|
1869
|
+
draft: {
|
|
1870
|
+
flush: async () => {},
|
|
1871
|
+
clear: async () => {},
|
|
1872
|
+
id: () => stream.previewStreamId
|
|
1873
|
+
},
|
|
1874
|
+
buildFinalEdit: (candidate) => candidate.text ? { text: candidate.text } : void 0,
|
|
1875
|
+
editFinal: async (_previewId, edit) => {
|
|
1876
|
+
const finalized = await stream.replaceInformativeWithFinal(edit.text);
|
|
1877
|
+
informativeUpdateSent = false;
|
|
1878
|
+
if (!finalized || stream.isFailed) throw new Error("Teams progress stream finalization failed");
|
|
1879
|
+
},
|
|
1880
|
+
resolveFinalizedId: (previewId) => stream.messageId ?? stream.previewStreamId ?? previewId,
|
|
1881
|
+
createPreviewReceipt: (id) => createPreviewMessageReceipt({ id }),
|
|
1882
|
+
onPreviewFinalized: (_id, _receipt, state) => {
|
|
1883
|
+
liveState = state;
|
|
1884
|
+
},
|
|
1885
|
+
logPreviewEditFailure: (err) => {
|
|
1886
|
+
params.log.debug?.(`stream finalization failed: ${formatUnknownError(err)}`);
|
|
1887
|
+
}
|
|
1888
|
+
}),
|
|
1889
|
+
deliverNormally: async () => false
|
|
1890
|
+
})).kind === "preview-finalized" ? hasMedia ? {
|
|
1891
|
+
...payload,
|
|
1892
|
+
text: void 0
|
|
1893
|
+
} : void 0 : payload;
|
|
1894
|
+
};
|
|
1830
1895
|
return {
|
|
1831
1896
|
async onReplyStart() {},
|
|
1832
1897
|
async noteProgressWork(options) {
|
|
@@ -1851,13 +1916,7 @@ function createTeamsReplyStreamController(params) {
|
|
|
1851
1916
|
const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
|
|
1852
1917
|
if (stream && streamMode === "progress" && informativeUpdateSent && !stream.isFinalized) {
|
|
1853
1918
|
if (!payload.text) return payload;
|
|
1854
|
-
|
|
1855
|
-
informativeUpdateSent = false;
|
|
1856
|
-
if (!finalized || stream.isFailed) return payload;
|
|
1857
|
-
return hasMedia ? {
|
|
1858
|
-
...payload,
|
|
1859
|
-
text: void 0
|
|
1860
|
-
} : void 0;
|
|
1919
|
+
return await finalizeProgressPayload(payload, hasMedia);
|
|
1861
1920
|
}
|
|
1862
1921
|
if (!stream || !streamReceivedTokens) return payload;
|
|
1863
1922
|
if (stream.isFailed) {
|
|
@@ -1866,7 +1925,9 @@ function createTeamsReplyStreamController(params) {
|
|
|
1866
1925
|
}
|
|
1867
1926
|
if (!stream.hasContent || stream.isFinalized) return payload;
|
|
1868
1927
|
streamReceivedTokens = false;
|
|
1869
|
-
pendingFinalize = stream.finalize()
|
|
1928
|
+
pendingFinalize = stream.finalize().then(() => {
|
|
1929
|
+
markStreamFinalized();
|
|
1930
|
+
});
|
|
1870
1931
|
if (!hasMedia) return;
|
|
1871
1932
|
return {
|
|
1872
1933
|
...payload,
|
|
@@ -1876,11 +1937,17 @@ function createTeamsReplyStreamController(params) {
|
|
|
1876
1937
|
async finalize() {
|
|
1877
1938
|
progressDraftGate.cancel();
|
|
1878
1939
|
await pendingFinalize;
|
|
1879
|
-
|
|
1940
|
+
if (!pendingFinalize) {
|
|
1941
|
+
await stream?.finalize();
|
|
1942
|
+
markStreamFinalized();
|
|
1943
|
+
}
|
|
1880
1944
|
},
|
|
1881
1945
|
hasStream() {
|
|
1882
1946
|
return Boolean(stream);
|
|
1883
1947
|
},
|
|
1948
|
+
liveState() {
|
|
1949
|
+
return liveState;
|
|
1950
|
+
},
|
|
1884
1951
|
/**
|
|
1885
1952
|
* Whether the Teams streaming card is currently receiving LLM tokens.
|
|
1886
1953
|
* Used to gate side-channel keepalive activity so we don't overlay plain
|
|
@@ -1905,6 +1972,9 @@ function createTeamsReplyStreamController(params) {
|
|
|
1905
1972
|
}
|
|
1906
1973
|
};
|
|
1907
1974
|
}
|
|
1975
|
+
function normalizeProgressLineIdentity(line) {
|
|
1976
|
+
return (typeof line === "string" ? line : line?.text)?.replace(/\s+/g, " ").trim() ?? "";
|
|
1977
|
+
}
|
|
1908
1978
|
//#endregion
|
|
1909
1979
|
//#region extensions/msteams/src/reply-dispatcher.ts
|
|
1910
1980
|
function createMSTeamsReplyDispatcher(params) {
|
|
@@ -1952,7 +2022,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
1952
2022
|
if (streamActiveRef.current()) return;
|
|
1953
2023
|
await rawSendTypingIndicator();
|
|
1954
2024
|
} : async () => {};
|
|
1955
|
-
const { onModelSelected, typingCallbacks, ...replyPipeline } =
|
|
2025
|
+
const { onModelSelected, typingCallbacks, ...replyPipeline } = createChannelMessageReplyPipeline({
|
|
1956
2026
|
cfg: params.cfg,
|
|
1957
2027
|
agentId: params.agentId,
|
|
1958
2028
|
channel: "msteams",
|
|
@@ -2145,7 +2215,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2145
2215
|
...streamController.shouldSuppressDefaultToolProgressMessages() ? { suppressDefaultToolProgressMessages: true } : {},
|
|
2146
2216
|
...streamController.shouldStreamPreviewToolProgress() ? {
|
|
2147
2217
|
onToolStart: async (payload) => {
|
|
2148
|
-
await streamController.pushProgressLine(
|
|
2218
|
+
await streamController.pushProgressLine(buildChannelProgressDraftLineForEntry(msteamsCfg, {
|
|
2149
2219
|
event: "tool",
|
|
2150
2220
|
name: payload.name,
|
|
2151
2221
|
phase: payload.phase,
|
|
@@ -2153,7 +2223,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2153
2223
|
}, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
|
|
2154
2224
|
},
|
|
2155
2225
|
onItemEvent: async (payload) => {
|
|
2156
|
-
await streamController.pushProgressLine(
|
|
2226
|
+
await streamController.pushProgressLine(buildChannelProgressDraftLineForEntry(msteamsCfg, {
|
|
2157
2227
|
event: "item",
|
|
2158
2228
|
itemKind: payload.kind,
|
|
2159
2229
|
title: payload.title,
|
|
@@ -2167,7 +2237,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2167
2237
|
},
|
|
2168
2238
|
onPlanUpdate: async (payload) => {
|
|
2169
2239
|
if (payload.phase !== "update") return;
|
|
2170
|
-
await streamController.pushProgressLine(
|
|
2240
|
+
await streamController.pushProgressLine(buildChannelProgressDraftLine({
|
|
2171
2241
|
event: "plan",
|
|
2172
2242
|
phase: payload.phase,
|
|
2173
2243
|
title: payload.title,
|
|
@@ -2177,7 +2247,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2177
2247
|
},
|
|
2178
2248
|
onApprovalEvent: async (payload) => {
|
|
2179
2249
|
if (payload.phase !== "requested") return;
|
|
2180
|
-
await streamController.pushProgressLine(
|
|
2250
|
+
await streamController.pushProgressLine(buildChannelProgressDraftLine({
|
|
2181
2251
|
event: "approval",
|
|
2182
2252
|
phase: payload.phase,
|
|
2183
2253
|
title: payload.title,
|
|
@@ -2188,7 +2258,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2188
2258
|
},
|
|
2189
2259
|
onCommandOutput: async (payload) => {
|
|
2190
2260
|
if (payload.phase !== "end") return;
|
|
2191
|
-
await streamController.pushProgressLine(
|
|
2261
|
+
await streamController.pushProgressLine(buildChannelProgressDraftLine({
|
|
2192
2262
|
event: "command-output",
|
|
2193
2263
|
phase: payload.phase,
|
|
2194
2264
|
title: payload.title,
|
|
@@ -2199,7 +2269,7 @@ function createMSTeamsReplyDispatcher(params) {
|
|
|
2199
2269
|
},
|
|
2200
2270
|
onPatchSummary: async (payload) => {
|
|
2201
2271
|
if (payload.phase !== "end") return;
|
|
2202
|
-
await streamController.pushProgressLine(
|
|
2272
|
+
await streamController.pushProgressLine(buildChannelProgressDraftLine({
|
|
2203
2273
|
event: "patch",
|
|
2204
2274
|
phase: payload.phase,
|
|
2205
2275
|
title: payload.title,
|
|
@@ -2430,13 +2500,29 @@ function extractTextFromHtmlAttachments(attachments) {
|
|
|
2430
2500
|
for (const attachment of attachments) {
|
|
2431
2501
|
if (attachment.contentType !== "text/html") continue;
|
|
2432
2502
|
const content = attachment.content;
|
|
2433
|
-
const raw = typeof content === "string" ? content : isRecord(content) && typeof content.text === "string" ? content.text : isRecord(content) && typeof content.body === "string" ? content.body : "";
|
|
2503
|
+
const raw = typeof content === "string" ? content : isRecord$1(content) && typeof content.text === "string" ? content.text : isRecord$1(content) && typeof content.body === "string" ? content.body : "";
|
|
2434
2504
|
if (!raw) continue;
|
|
2435
2505
|
const text = raw.replace(/<at[^>]*>.*?<\/at>/gis, " ").replace(/<a\b[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gis, "$2 $1").replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<[^>]+>/g, " ").replace(/ /gi, " ").replace(/&/gi, "&").replace(/\s+/g, " ").trim();
|
|
2436
2506
|
if (text) return text;
|
|
2437
2507
|
}
|
|
2438
2508
|
return "";
|
|
2439
2509
|
}
|
|
2510
|
+
function formatMSTeamsSenderReason(params) {
|
|
2511
|
+
switch (params.reasonCode) {
|
|
2512
|
+
case "dm_policy_open": return "dmPolicy=open";
|
|
2513
|
+
case "dm_policy_disabled": return "dmPolicy=disabled";
|
|
2514
|
+
case "dm_policy_pairing_required": return "dmPolicy=pairing (not allowlisted)";
|
|
2515
|
+
case "dm_policy_allowlisted": return `dmPolicy=${params.dmPolicy ?? "allowlist"} (allowlisted)`;
|
|
2516
|
+
case "dm_policy_not_allowlisted": return `dmPolicy=${params.dmPolicy ?? "allowlist"} (not allowlisted)`;
|
|
2517
|
+
case "group_policy_disabled": return "groupPolicy=disabled";
|
|
2518
|
+
case "group_policy_empty_allowlist":
|
|
2519
|
+
case "route_sender_empty": return "groupPolicy=allowlist (empty allowlist)";
|
|
2520
|
+
case "group_policy_not_allowlisted": return "groupPolicy=allowlist (not allowlisted)";
|
|
2521
|
+
case "group_policy_open": return "groupPolicy=open";
|
|
2522
|
+
case "group_policy_allowed": return `groupPolicy=${params.groupPolicy ?? "allowlist"}`;
|
|
2523
|
+
default: return params.reasonCode;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2440
2526
|
function buildStoredConversationReference(params) {
|
|
2441
2527
|
const { activity, conversationId, conversationType, teamId, threadId } = params;
|
|
2442
2528
|
const from = activity.from;
|
|
@@ -2532,14 +2618,17 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2532
2618
|
teamId,
|
|
2533
2619
|
threadId: conversationType === "channel" ? conversationMessageId ?? activity.replyToId ?? void 0 : void 0
|
|
2534
2620
|
});
|
|
2535
|
-
const { dmPolicy, senderId, senderName, pairing, isDirectMessage, channelGate,
|
|
2621
|
+
const { dmPolicy, senderId, senderName, pairing, isDirectMessage, channelGate, senderAccess, commandAccess, allowNameMatching, groupPolicy } = await resolveMSTeamsSenderAccess({
|
|
2536
2622
|
cfg,
|
|
2537
|
-
activity
|
|
2623
|
+
activity,
|
|
2624
|
+
hasControlCommand: core.channel.text.hasControlCommand(text, cfg)
|
|
2538
2625
|
});
|
|
2539
|
-
const
|
|
2626
|
+
const commandAuthorized = commandAccess.requested ? commandAccess.authorized : void 0;
|
|
2627
|
+
const effectiveDmAllowFrom = senderAccess.effectiveAllowFrom;
|
|
2628
|
+
const effectiveGroupAllowFrom = senderAccess.effectiveGroupAllowFrom;
|
|
2540
2629
|
const isChannel = conversationType === "channel";
|
|
2541
|
-
if (isDirectMessage && msteamsCfg &&
|
|
2542
|
-
if (
|
|
2630
|
+
if (isDirectMessage && msteamsCfg && senderAccess.decision !== "allow") {
|
|
2631
|
+
if (senderAccess.reasonCode === "dm_policy_disabled") {
|
|
2543
2632
|
log.info("dropping dm (dms disabled)", {
|
|
2544
2633
|
sender: senderId,
|
|
2545
2634
|
label: senderName
|
|
@@ -2553,7 +2642,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2553
2642
|
senderName,
|
|
2554
2643
|
allowNameMatching
|
|
2555
2644
|
});
|
|
2556
|
-
if (
|
|
2645
|
+
if (senderAccess.decision === "pairing") {
|
|
2557
2646
|
conversationStore.upsert(conversationId, conversationRef).catch((err) => {
|
|
2558
2647
|
log.debug?.("failed to save conversation reference", { error: formatUnknownError(err) });
|
|
2559
2648
|
});
|
|
@@ -2574,7 +2663,11 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2574
2663
|
sender: senderId,
|
|
2575
2664
|
label: senderName,
|
|
2576
2665
|
dmPolicy,
|
|
2577
|
-
reason:
|
|
2666
|
+
reason: formatMSTeamsSenderReason({
|
|
2667
|
+
reasonCode: senderAccess.reasonCode,
|
|
2668
|
+
dmPolicy,
|
|
2669
|
+
groupPolicy
|
|
2670
|
+
}),
|
|
2578
2671
|
allowlistMatch: formatAllowlistMatchMeta(allowMatch)
|
|
2579
2672
|
});
|
|
2580
2673
|
return;
|
|
@@ -2597,28 +2690,17 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2597
2690
|
});
|
|
2598
2691
|
return;
|
|
2599
2692
|
}
|
|
2600
|
-
|
|
2601
|
-
groupPolicy,
|
|
2602
|
-
groupAllowFrom: effectiveGroupAllowFrom,
|
|
2603
|
-
senderId,
|
|
2604
|
-
isSenderAllowed: (_senderId, allowFrom) => resolveMSTeamsAllowlistMatch({
|
|
2605
|
-
allowFrom,
|
|
2606
|
-
senderId,
|
|
2607
|
-
senderName,
|
|
2608
|
-
allowNameMatching
|
|
2609
|
-
}).allowed
|
|
2610
|
-
});
|
|
2611
|
-
if (!senderGroupAccess.allowed && senderGroupAccess.reason === "disabled") {
|
|
2693
|
+
if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_disabled") {
|
|
2612
2694
|
log.info("dropping group message (groupPolicy: disabled)", { conversationId });
|
|
2613
2695
|
log.debug?.("dropping group message (groupPolicy: disabled)", { conversationId });
|
|
2614
2696
|
return;
|
|
2615
2697
|
}
|
|
2616
|
-
if (!
|
|
2698
|
+
if (!senderAccess.allowed && (senderAccess.reasonCode === "group_policy_empty_allowlist" || senderAccess.reasonCode === "route_sender_empty")) {
|
|
2617
2699
|
log.info("dropping group message (groupPolicy: allowlist, no allowlist)", { conversationId });
|
|
2618
2700
|
log.debug?.("dropping group message (groupPolicy: allowlist, no allowlist)", { conversationId });
|
|
2619
2701
|
return;
|
|
2620
2702
|
}
|
|
2621
|
-
if (!
|
|
2703
|
+
if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_not_allowlisted") {
|
|
2622
2704
|
const allowMatch = resolveMSTeamsAllowlistMatch({
|
|
2623
2705
|
allowFrom: effectiveGroupAllowFrom,
|
|
2624
2706
|
senderId,
|
|
@@ -2638,30 +2720,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
2638
2720
|
return;
|
|
2639
2721
|
}
|
|
2640
2722
|
}
|
|
2641
|
-
|
|
2642
|
-
const ownerAllowedForCommands = isMSTeamsGroupAllowed({
|
|
2643
|
-
groupPolicy: "allowlist",
|
|
2644
|
-
allowFrom: commandDmAllowFrom,
|
|
2645
|
-
senderId,
|
|
2646
|
-
senderName,
|
|
2647
|
-
allowNameMatching
|
|
2648
|
-
});
|
|
2649
|
-
const groupAllowedForCommands = isMSTeamsGroupAllowed({
|
|
2650
|
-
groupPolicy: "allowlist",
|
|
2651
|
-
allowFrom: effectiveGroupAllowFrom,
|
|
2652
|
-
senderId,
|
|
2653
|
-
senderName,
|
|
2654
|
-
allowNameMatching
|
|
2655
|
-
});
|
|
2656
|
-
const { commandAuthorized, shouldBlock } = resolveDualTextControlCommandGate({
|
|
2657
|
-
useAccessGroups,
|
|
2658
|
-
primaryConfigured: commandDmAllowFrom.length > 0,
|
|
2659
|
-
primaryAllowed: ownerAllowedForCommands,
|
|
2660
|
-
secondaryConfigured: effectiveGroupAllowFrom.length > 0,
|
|
2661
|
-
secondaryAllowed: groupAllowedForCommands,
|
|
2662
|
-
hasControlCommand: core.channel.text.hasControlCommand(text, cfg)
|
|
2663
|
-
});
|
|
2664
|
-
if (shouldBlock) {
|
|
2723
|
+
if (commandAccess.shouldBlockControlCommand) {
|
|
2665
2724
|
logInboundDrop({
|
|
2666
2725
|
log: logVerboseMessage,
|
|
2667
2726
|
channel: "msteams",
|
|
@@ -3021,7 +3080,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
3021
3080
|
logVerboseMessage(`msteams: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${teamsTo}`);
|
|
3022
3081
|
} catch (err) {
|
|
3023
3082
|
log.error("dispatch failed", { error: formatUnknownError(err) });
|
|
3024
|
-
runtime.error
|
|
3083
|
+
runtime.error(`msteams dispatch failed: ${formatUnknownError(err)}`);
|
|
3025
3084
|
try {
|
|
3026
3085
|
await context.sendActivity("⚠️ Something went wrong. Please try again.");
|
|
3027
3086
|
} catch {}
|
|
@@ -3062,7 +3121,7 @@ function createMSTeamsMessageHandler(deps) {
|
|
|
3062
3121
|
});
|
|
3063
3122
|
},
|
|
3064
3123
|
onError: (err) => {
|
|
3065
|
-
runtime.error
|
|
3124
|
+
runtime.error(`msteams debounce flush failed: ${formatUnknownError(err)}`);
|
|
3066
3125
|
}
|
|
3067
3126
|
});
|
|
3068
3127
|
return async function handleTeamsMessage(context) {
|
|
@@ -3114,11 +3173,6 @@ function createMSTeamsReactionHandler(deps) {
|
|
|
3114
3173
|
const { cfg, log } = deps;
|
|
3115
3174
|
const core = getMSTeamsRuntime();
|
|
3116
3175
|
const msteamsCfg = cfg.channels?.msteams;
|
|
3117
|
-
const pairing = createChannelPairingController({
|
|
3118
|
-
core,
|
|
3119
|
-
channel: "msteams",
|
|
3120
|
-
accountId: DEFAULT_ACCOUNT_ID
|
|
3121
|
-
});
|
|
3122
3176
|
return async function handleReaction(context, direction) {
|
|
3123
3177
|
const activity = context.activity;
|
|
3124
3178
|
const reactions = direction === "added" ? activity.reactionsAdded ?? [] : activity.reactionsRemoved ?? [];
|
|
@@ -3138,74 +3192,19 @@ function createMSTeamsReactionHandler(deps) {
|
|
|
3138
3192
|
const isDirectMessage = !isGroupChat && !isChannel;
|
|
3139
3193
|
const senderId = from.aadObjectId ?? from.id;
|
|
3140
3194
|
const senderName = from.name ?? from.id;
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
dmPolicy,
|
|
3146
|
-
readStore: pairing.readStoreForDmPolicy
|
|
3147
|
-
});
|
|
3148
|
-
const dmAllowFrom = msteamsCfg?.allowFrom ?? [];
|
|
3149
|
-
const groupAllowFrom = msteamsCfg?.groupAllowFrom;
|
|
3150
|
-
const resolvedAllowFromLists = resolveEffectiveAllowFromLists({
|
|
3151
|
-
allowFrom: dmAllowFrom,
|
|
3152
|
-
groupAllowFrom,
|
|
3153
|
-
storeAllowFrom: storedAllowFrom,
|
|
3154
|
-
dmPolicy
|
|
3155
|
-
});
|
|
3156
|
-
const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
|
|
3157
|
-
if (isDirectMessage && msteamsCfg) {
|
|
3158
|
-
const access = resolveDmGroupAccessWithLists({
|
|
3159
|
-
isGroup: false,
|
|
3160
|
-
dmPolicy,
|
|
3161
|
-
groupPolicy: msteamsCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist",
|
|
3162
|
-
allowFrom: dmAllowFrom,
|
|
3163
|
-
groupAllowFrom,
|
|
3164
|
-
storeAllowFrom: storedAllowFrom,
|
|
3165
|
-
groupAllowFromFallbackToAllowFrom: false,
|
|
3166
|
-
isSenderAllowed: (allowFrom) => resolveMSTeamsAllowlistMatch({
|
|
3167
|
-
allowFrom,
|
|
3168
|
-
senderId,
|
|
3169
|
-
senderName,
|
|
3170
|
-
allowNameMatching: isDangerousNameMatchingEnabled(msteamsCfg)
|
|
3171
|
-
}).allowed
|
|
3195
|
+
if (msteamsCfg) {
|
|
3196
|
+
const senderAccess = await resolveMSTeamsSenderAccess({
|
|
3197
|
+
cfg,
|
|
3198
|
+
activity
|
|
3172
3199
|
});
|
|
3173
|
-
if (
|
|
3174
|
-
log.debug?.("dropping reaction (
|
|
3200
|
+
if (senderAccess.senderAccess.decision !== "allow") {
|
|
3201
|
+
log.debug?.("dropping reaction (access denied)", {
|
|
3175
3202
|
sender: senderId,
|
|
3176
|
-
reason:
|
|
3203
|
+
reason: senderAccess.senderAccess.reasonCode
|
|
3177
3204
|
});
|
|
3178
3205
|
return;
|
|
3179
3206
|
}
|
|
3180
3207
|
}
|
|
3181
|
-
if (!isDirectMessage && msteamsCfg) {
|
|
3182
|
-
const teamId = activity.channelData?.team?.id;
|
|
3183
|
-
const teamName = activity.channelData?.team?.name;
|
|
3184
|
-
const channelName = activity.channelData?.channel?.name;
|
|
3185
|
-
const channelGate = resolveMSTeamsRouteConfig({
|
|
3186
|
-
cfg: msteamsCfg,
|
|
3187
|
-
teamId,
|
|
3188
|
-
teamName,
|
|
3189
|
-
conversationId,
|
|
3190
|
-
channelName,
|
|
3191
|
-
allowNameMatching: isDangerousNameMatchingEnabled(msteamsCfg)
|
|
3192
|
-
});
|
|
3193
|
-
if (channelGate.allowlistConfigured && !channelGate.allowed) {
|
|
3194
|
-
log.debug?.("dropping reaction (not in team/channel allowlist)", { conversationId });
|
|
3195
|
-
return;
|
|
3196
|
-
}
|
|
3197
|
-
const effectiveGroupAllowFrom = resolvedAllowFromLists.effectiveGroupAllowFrom;
|
|
3198
|
-
if (!isMSTeamsGroupAllowed({
|
|
3199
|
-
groupPolicy: msteamsCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist",
|
|
3200
|
-
allowFrom: effectiveGroupAllowFrom,
|
|
3201
|
-
senderId,
|
|
3202
|
-
senderName,
|
|
3203
|
-
allowNameMatching: isDangerousNameMatchingEnabled(msteamsCfg)
|
|
3204
|
-
})) {
|
|
3205
|
-
log.debug?.("dropping reaction (sender not in group allowlist)", { sender: senderId });
|
|
3206
|
-
return;
|
|
3207
|
-
}
|
|
3208
|
-
}
|
|
3209
3208
|
const teamId = isDirectMessage ? void 0 : activity.channelData?.team?.id;
|
|
3210
3209
|
const route = core.channel.routing.resolveAgentRoute({
|
|
3211
3210
|
cfg,
|
|
@@ -3489,7 +3488,7 @@ async function isInvokeAuthorized(params) {
|
|
|
3489
3488
|
const { msteamsCfg, isDirectMessage, conversationId, senderId } = resolved;
|
|
3490
3489
|
if (!msteamsCfg) return true;
|
|
3491
3490
|
const maybeInvokeName = includeInvokeName ? { name: context.activity.name } : void 0;
|
|
3492
|
-
if (isDirectMessage && resolved.
|
|
3491
|
+
if (isDirectMessage && resolved.senderAccess.decision !== "allow") {
|
|
3493
3492
|
deps.log.debug?.(deniedLogs.dm, {
|
|
3494
3493
|
sender: senderId,
|
|
3495
3494
|
conversationId,
|
|
@@ -3506,7 +3505,7 @@ async function isInvokeAuthorized(params) {
|
|
|
3506
3505
|
});
|
|
3507
3506
|
return false;
|
|
3508
3507
|
}
|
|
3509
|
-
if (!isDirectMessage && !resolved.
|
|
3508
|
+
if (!isDirectMessage && !resolved.senderAccess.allowed) {
|
|
3510
3509
|
deps.log.debug?.(deniedLogs.group, {
|
|
3511
3510
|
sender: senderId,
|
|
3512
3511
|
conversationId,
|
|
@@ -3603,8 +3602,11 @@ async function handleFeedbackInvoke(context, deps) {
|
|
|
3603
3602
|
try {
|
|
3604
3603
|
const storePath = core.channel.session.resolveStorePath(deps.cfg.session?.store, { agentId: route.agentId });
|
|
3605
3604
|
const safeKey = route.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3606
|
-
|
|
3607
|
-
|
|
3605
|
+
await appendRegularFile({
|
|
3606
|
+
filePath: path.join(storePath, `${safeKey}.jsonl`),
|
|
3607
|
+
content: `${JSON.stringify(feedbackEvent)}\n`,
|
|
3608
|
+
rejectSymlinkParents: true
|
|
3609
|
+
}).catch(() => {});
|
|
3608
3610
|
} catch {}
|
|
3609
3611
|
const conversationRef = {
|
|
3610
3612
|
activityId: activity.id,
|
|
@@ -3744,7 +3746,7 @@ function registerMSTeamsHandlers(handler, deps) {
|
|
|
3744
3746
|
try {
|
|
3745
3747
|
await handleTeamsMessage(context);
|
|
3746
3748
|
} catch (err) {
|
|
3747
|
-
deps.runtime.error
|
|
3749
|
+
deps.runtime.error(`msteams handler failed: ${formatUnknownError(err)}`);
|
|
3748
3750
|
}
|
|
3749
3751
|
await next();
|
|
3750
3752
|
});
|
|
@@ -3788,7 +3790,7 @@ function registerMSTeamsHandlers(handler, deps) {
|
|
|
3788
3790
|
try {
|
|
3789
3791
|
await handleReaction(context, "added");
|
|
3790
3792
|
} catch (err) {
|
|
3791
|
-
deps.runtime.error
|
|
3793
|
+
deps.runtime.error(`msteams reaction handler failed: ${String(err)}`);
|
|
3792
3794
|
}
|
|
3793
3795
|
await next();
|
|
3794
3796
|
});
|
|
@@ -3796,7 +3798,7 @@ function registerMSTeamsHandlers(handler, deps) {
|
|
|
3796
3798
|
try {
|
|
3797
3799
|
await handleReaction(context, "removed");
|
|
3798
3800
|
} catch (err) {
|
|
3799
|
-
deps.runtime.error
|
|
3801
|
+
deps.runtime.error(`msteams reaction handler failed: ${String(err)}`);
|
|
3800
3802
|
}
|
|
3801
3803
|
await next();
|
|
3802
3804
|
});
|
|
@@ -3941,7 +3943,17 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3941
3943
|
let allowFrom = msteamsCfg.allowFrom;
|
|
3942
3944
|
let groupAllowFrom = msteamsCfg.groupAllowFrom;
|
|
3943
3945
|
let teamsConfig = msteamsCfg.teams;
|
|
3946
|
+
const allowNameMatching = isDangerousNameMatchingEnabled(msteamsCfg);
|
|
3944
3947
|
const cleanAllowEntry = (entry) => entry.replace(/^(msteams|teams):/i, "").replace(/^user:/i, "").trim();
|
|
3948
|
+
const isStableUserId = (entry) => /^[0-9a-fA-F-]{16,}$/.test(entry);
|
|
3949
|
+
const cleanAllowEntries = (entries) => entries?.map((entry) => cleanAllowEntry(entry)).filter((entry) => entry && entry !== "*") ?? [];
|
|
3950
|
+
const mergeStableUserIds = (entries) => {
|
|
3951
|
+
const additions = cleanAllowEntries(entries).filter((entry) => isStableUserId(entry));
|
|
3952
|
+
return additions.length > 0 ? mergeAllowlist({
|
|
3953
|
+
existing: entries,
|
|
3954
|
+
additions
|
|
3955
|
+
}) : entries;
|
|
3956
|
+
};
|
|
3945
3957
|
const resolveAllowlistUsers = async (label, entries) => {
|
|
3946
3958
|
if (entries.length === 0) return {
|
|
3947
3959
|
additions: [],
|
|
@@ -3962,23 +3974,27 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
3962
3974
|
};
|
|
3963
3975
|
};
|
|
3964
3976
|
try {
|
|
3965
|
-
|
|
3966
|
-
if (
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
additions
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
if (Array.isArray(groupAllowFrom) && groupAllowFrom.length > 0) {
|
|
3974
|
-
const groupEntries = groupAllowFrom.map((entry) => cleanAllowEntry(entry)).filter((entry) => entry && entry !== "*");
|
|
3975
|
-
if (groupEntries.length > 0) {
|
|
3976
|
-
const { additions } = await resolveAllowlistUsers("msteams group users", groupEntries);
|
|
3977
|
-
groupAllowFrom = mergeAllowlist({
|
|
3978
|
-
existing: groupAllowFrom,
|
|
3977
|
+
allowFrom = mergeStableUserIds(allowFrom);
|
|
3978
|
+
if (Array.isArray(groupAllowFrom) && groupAllowFrom.length > 0) groupAllowFrom = mergeStableUserIds(groupAllowFrom);
|
|
3979
|
+
if (allowNameMatching) {
|
|
3980
|
+
const allowEntries = cleanAllowEntries(allowFrom).filter((entry) => !isStableUserId(entry));
|
|
3981
|
+
if (allowEntries.length > 0) {
|
|
3982
|
+
const { additions } = await resolveAllowlistUsers("msteams users", allowEntries);
|
|
3983
|
+
allowFrom = mergeAllowlist({
|
|
3984
|
+
existing: allowFrom,
|
|
3979
3985
|
additions
|
|
3980
3986
|
});
|
|
3981
3987
|
}
|
|
3988
|
+
if (Array.isArray(groupAllowFrom) && groupAllowFrom.length > 0) {
|
|
3989
|
+
const groupEntries = cleanAllowEntries(groupAllowFrom).filter((entry) => !isStableUserId(entry));
|
|
3990
|
+
if (groupEntries.length > 0) {
|
|
3991
|
+
const { additions } = await resolveAllowlistUsers("msteams group users", groupEntries);
|
|
3992
|
+
groupAllowFrom = mergeAllowlist({
|
|
3993
|
+
existing: groupAllowFrom,
|
|
3994
|
+
additions
|
|
3995
|
+
});
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3982
3998
|
}
|
|
3983
3999
|
if (teamsConfig && Object.keys(teamsConfig).length > 0) {
|
|
3984
4000
|
const entries = [];
|
|
@@ -4046,7 +4062,7 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4046
4062
|
}
|
|
4047
4063
|
}
|
|
4048
4064
|
} catch (err) {
|
|
4049
|
-
runtime
|
|
4065
|
+
runtime?.error(`msteams resolve failed; falling back to raw config entries — allowlist members resolved via Graph may be missing. ${formatUnknownError(err)}`);
|
|
4050
4066
|
}
|
|
4051
4067
|
msteamsCfg = {
|
|
4052
4068
|
...msteamsCfg,
|
|
@@ -4116,7 +4132,8 @@ async function monitorMSTeamsProvider(opts) {
|
|
|
4116
4132
|
}
|
|
4117
4133
|
next();
|
|
4118
4134
|
}).catch((err) => {
|
|
4119
|
-
|
|
4135
|
+
if (err instanceof Error && /ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ETIMEDOUT|ECONNRESET/i.test(err.code ?? err.message)) runtime?.error(`msteams: JWKS key fetch failed — check egress to login.botframework.com:443 (firewall or DNS may be blocking it). Bot will 401 all inbound requests until this is resolved. Error: ${formatUnknownError(err)}`);
|
|
4136
|
+
else log.debug?.(`JWT validation error: ${formatUnknownError(err)}`);
|
|
4120
4137
|
res.status(401).json({ error: "Unauthorized" });
|
|
4121
4138
|
});
|
|
4122
4139
|
});
|