openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
970 lines (969 loc) • 36.8 kB
JavaScript
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js";
import { l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js";
import { t as DEFAULT_ACCOUNT_ID } from "./account-id-Df9e41E6.js";
import { l as normalizeMessagePresentation } from "./payload-CZlThETZ.js";
import { i as createLazyRuntimeNamedExport } from "./lazy-runtime-D-7_JraP.js";
import { u as createTopLevelChannelConfigAdapter } from "./channel-config-helpers-lj5quus3.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { t as chunkTextForOutbound } from "./text-chunking-D45TeeEs.js";
import { n as describeAccountSnapshot } from "./account-helpers-pcH3lGFY.js";
import { d as stripTargetKindPrefix, i as createChatChannelPlugin, t as buildChannelOutboundSessionRoute, u as stripChannelTargetPrefix } from "./core-DSxVv-v1.js";
import "./channel-core-IukkNCd-.js";
import { t as formatAllowFromLowercase } from "./allow-from-DCaV3hzI.js";
import { D as projectConfigWarningCollector, g as createAllowlistProviderGroupPolicyWarningCollector, n as createDangerousNameMatchingMutableAllowlistWarningCollector } from "./channel-policy-CIW58SA5.js";
import { t as createChannelDirectoryAdapter } from "./directory-runtime-om8jJAZt.js";
import { a as listDirectoryEntriesFromSources } from "./directory-config-helpers-DEDTHx2P.js";
import { n as createRuntimeOutboundDelegates, t as createRuntimeDirectoryLiveAdapter } from "./runtime-forwarders-Dxil5z45.js";
import { t as resolveApprovalApprovers } from "./approval-approvers-CM-4zRvR.js";
import { t as createResolvedApproverActionAuthAdapter } from "./approval-auth-helpers-DU3OxkhL.js";
import { w as createChannelMessageAdapterFromOutbound } from "./channel-outbound-B3_Zy-kG.js";
import { t as PAIRING_APPROVED_MESSAGE } from "./pairing-message-DNhqI-OE.js";
import { d as createDefaultChannelRuntimeState, i as buildProbeChannelStatusSummary, u as createComputedAccountStatusAdapter } from "./status-helpers-BevxX6Pu.js";
import { i as createPairingPrefixStripper } from "./channel-pairing-Bk6_JhTm.js";
import "./runtime-api-B9a52KOD.js";
import { h as resolveMSTeamsCredentials } from "./graph-users-DhhsJ2VT.js";
import { a as parseMSTeamsTeamChannelInput, c as resolveMSTeamsUserAllowlist, i as parseMSTeamsConversationId, n as normalizeMSTeamsMessagingTarget, r as normalizeMSTeamsUserInput, s as resolveMSTeamsChannelAllowlist, t as looksLikeMSTeamsTargetId } from "./resolve-allowlist-5LIHeNAD.js";
import { t as MSTeamsChannelConfigSchema } from "./config-schema-C7CAVdj8.js";
import { n as resolveMSTeamsGroupToolPolicy } from "./policy-DZBX7LmO.js";
import { n as buildMSTeamsPresentationCard, t as MSTEAMS_PRESENTATION_CAPABILITIES } from "./presentation-B9NzXv2T.js";
import { i as msteamsSetupAdapter, t as msteamsSetupWizard } from "./setup-surface-DM1BIn3v.js";
import { Type } from "typebox";
//#region extensions/msteams/src/approval-auth.ts
const MSTEAMS_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function normalizeMSTeamsApproverId(value) {
const normalized = normalizeMSTeamsMessagingTarget(String(value));
if (!normalized?.startsWith("user:")) return;
const id = normalizeOptionalLowercaseString(normalized.slice(5));
if (!id) return;
return MSTEAMS_ID_RE.test(id) ? id : void 0;
}
function resolveMSTeamsChannelConfig$1(cfg) {
return cfg.channels?.msteams;
}
const msTeamsApprovalAuth = createResolvedApproverActionAuthAdapter({
channelLabel: "Microsoft Teams",
resolveApprovers: ({ cfg }) => {
const channel = resolveMSTeamsChannelConfig$1(cfg);
return resolveApprovalApprovers({
allowFrom: channel?.allowFrom,
defaultTo: channel?.defaultTo,
normalizeApprover: normalizeMSTeamsApproverId
});
},
normalizeSenderId: (value) => {
const trimmed = normalizeOptionalLowercaseString(value);
if (!trimmed) return;
return MSTEAMS_ID_RE.test(trimmed) ? trimmed : void 0;
}
});
//#endregion
//#region extensions/msteams/src/doctor.ts
function isMSTeamsMutableAllowEntry(raw) {
const text = raw.trim();
if (!text || text === "*") return false;
const withoutPrefix = text.replace(/^(msteams|user):/i, "").trim();
return /\s/.test(withoutPrefix) || withoutPrefix.includes("@");
}
const collectMSTeamsMutableAllowlistWarnings = createDangerousNameMatchingMutableAllowlistWarningCollector({
channel: "msteams",
detector: isMSTeamsMutableAllowEntry,
collectLists: (scope) => [{
pathLabel: `${scope.prefix}.allowFrom`,
list: scope.account.allowFrom
}, {
pathLabel: `${scope.prefix}.groupAllowFrom`,
list: scope.account.groupAllowFrom
}]
});
//#endregion
//#region extensions/msteams/src/session-route.ts
function resolveMSTeamsOutboundSessionRoute(params) {
const trimmed = stripChannelTargetPrefix(params.target, "msteams", "teams");
if (!trimmed) return null;
const isUser = normalizeLowercaseStringOrEmpty(trimmed).startsWith("user:");
const rawId = stripTargetKindPrefix(trimmed);
if (!rawId) return null;
const conversationId = rawId.split(";")[0] ?? rawId;
const isChannel = !isUser && /@thread\.tacv2/i.test(conversationId);
return buildChannelOutboundSessionRoute({
cfg: params.cfg,
agentId: params.agentId,
channel: "msteams",
accountId: params.accountId,
peer: {
kind: isUser ? "direct" : isChannel ? "channel" : "group",
id: conversationId
},
chatType: isUser ? "direct" : isChannel ? "channel" : "group",
from: isUser ? `msteams:${conversationId}` : isChannel ? `msteams:channel:${conversationId}` : `msteams:group:${conversationId}`,
to: isUser ? `user:${conversationId}` : `conversation:${conversationId}`
});
}
//#endregion
//#region extensions/msteams/src/channel.ts
const meta = {
id: "msteams",
label: "Microsoft Teams",
selectionLabel: "Microsoft Teams (Bot Framework)",
docsPath: "/channels/msteams",
docsLabel: "msteams",
blurb: "Teams SDK; enterprise support.",
aliases: ["teams"],
order: 60
};
const TEAMS_GRAPH_PERMISSION_HINTS = {
"ChannelMessage.Read.All": "channel history",
"Chat.Read.All": "chat history",
"Channel.ReadBasic.All": "channel list",
"Team.ReadBasic.All": "team list",
"TeamsActivity.Read.All": "teams activity",
"Sites.Read.All": "files (SharePoint)",
"Files.Read.All": "files (OneDrive)"
};
const MSTEAMS_GROUP_MANAGEMENT_ACTIONS = new Set([
"addParticipant",
"removeParticipant",
"renameGroup"
]);
const collectMSTeamsSecurityWarnings = createAllowlistProviderGroupPolicyWarningCollector({
providerConfigPresent: (cfg) => cfg.channels?.msteams !== void 0,
resolveGroupPolicy: ({ cfg }) => cfg.channels?.msteams?.groupPolicy,
collect: ({ groupPolicy }) => groupPolicy === "open" ? ["- MS Teams groups: groupPolicy=\"open\" allows any member to trigger (mention-gated). Set channels.msteams.groupPolicy=\"allowlist\" + channels.msteams.groupAllowFrom to restrict senders."] : []
});
const loadMSTeamsChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-i8r-BTdl.js"), "msTeamsChannelRuntime");
const resolveMSTeamsChannelConfig = (cfg) => ({
allowFrom: cfg.channels?.msteams?.allowFrom,
defaultTo: cfg.channels?.msteams?.defaultTo
});
const msteamsConfigAdapter = createTopLevelChannelConfigAdapter({
sectionKey: "msteams",
resolveAccount: (cfg) => ({
accountId: DEFAULT_ACCOUNT_ID,
enabled: cfg.channels?.msteams?.enabled !== false,
configured: Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams))
}),
resolveAccessorAccount: ({ cfg }) => resolveMSTeamsChannelConfig(cfg),
resolveAllowFrom: (account) => account.allowFrom,
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
resolveDefaultTo: (account) => account.defaultTo
});
function jsonActionResult(data) {
return {
content: [{
type: "text",
text: JSON.stringify(data)
}],
details: data
};
}
function jsonMSTeamsActionResult(action, data = {}) {
return jsonActionResult({
channel: "msteams",
action,
...data
});
}
function jsonMSTeamsOkActionResult(action, data = {}) {
return jsonActionResult({
ok: true,
channel: "msteams",
action,
...data
});
}
function jsonMSTeamsConversationResult(conversationId) {
return jsonActionResultWithDetails({
ok: true,
channel: "msteams",
conversationId
}, {
ok: true,
channel: "msteams"
});
}
function jsonActionResultWithDetails(contentData, details) {
return {
content: [{
type: "text",
text: JSON.stringify(contentData)
}],
details
};
}
const MSTEAMS_REACTION_TYPES = [
"like",
"heart",
"laugh",
"surprised",
"sad",
"angry"
];
function actionError(message) {
return {
isError: true,
content: [{
type: "text",
text: message
}],
details: { error: message }
};
}
function requireMSTeamsGroupManagementAuthorization(ctx) {
if (ctx.senderIsOwner === true || ctx.gatewayClientScopes?.includes("operator.admin")) return null;
return actionError("Microsoft Teams group management requires an owner or operator.admin requester.");
}
function resolveActionTarget(params, currentChannelId) {
return typeof params.to === "string" ? params.to.trim() : typeof params.target === "string" ? params.target.trim() : currentChannelId?.trim() ?? "";
}
function resolveGraphActionTarget(params, currentChannelId, currentGraphChannelId) {
return resolveActionTarget(params, currentGraphChannelId ?? currentChannelId);
}
function resolveActionMessageId(params) {
return normalizeOptionalString(params.messageId) ?? "";
}
function resolveActionPinnedMessageId(params) {
return typeof params.pinnedMessageId === "string" ? params.pinnedMessageId.trim() : typeof params.messageId === "string" ? params.messageId.trim() : "";
}
function resolveActionQuery(params) {
return normalizeOptionalString(params.query) ?? "";
}
function resolveActionContent(params) {
return typeof params.text === "string" ? params.text : typeof params.content === "string" ? params.content : typeof params.message === "string" ? params.message : "";
}
function readOptionalTrimmedString(params, key) {
return typeof params[key] === "string" ? params[key].trim() || void 0 : void 0;
}
function resolveActionUploadFilePath(params) {
for (const key of [
"filePath",
"path",
"media"
]) if (typeof params[key] === "string") {
const value = params[key];
if (value.trim()) return value;
}
}
function resolveRequiredActionTarget(params) {
const to = params.graphOnly ? resolveGraphActionTarget(params.toolParams, params.currentChannelId, params.currentGraphChannelId) : resolveActionTarget(params.toolParams, params.currentChannelId);
if (!to) return actionError(`${params.actionLabel} requires a target (to).`);
return to;
}
function resolveRequiredActionMessageTarget(params) {
const to = params.graphOnly ? resolveGraphActionTarget(params.toolParams, params.currentChannelId, params.currentGraphChannelId) : resolveActionTarget(params.toolParams, params.currentChannelId);
const messageId = resolveActionMessageId(params.toolParams);
if (!to || !messageId) return actionError(`${params.actionLabel} requires a target (to) and messageId.`);
return {
to,
messageId
};
}
function resolveRequiredActionPinnedMessageTarget(params) {
const to = params.graphOnly ? resolveGraphActionTarget(params.toolParams, params.currentChannelId, params.currentGraphChannelId) : resolveActionTarget(params.toolParams, params.currentChannelId);
const pinnedMessageId = resolveActionPinnedMessageId(params.toolParams);
if (!to || !pinnedMessageId) return actionError(`${params.actionLabel} requires a target (to) and pinnedMessageId.`);
return {
to,
pinnedMessageId
};
}
async function runWithRequiredActionTarget(params) {
const to = resolveRequiredActionTarget({
actionLabel: params.actionLabel,
toolParams: params.toolParams,
currentChannelId: params.currentChannelId,
currentGraphChannelId: params.currentGraphChannelId,
graphOnly: params.graphOnly
});
if (typeof to !== "string") return to;
return await params.run(to);
}
async function runWithRequiredActionMessageTarget(params) {
const target = resolveRequiredActionMessageTarget({
actionLabel: params.actionLabel,
toolParams: params.toolParams,
currentChannelId: params.currentChannelId,
currentGraphChannelId: params.currentGraphChannelId,
graphOnly: params.graphOnly
});
if ("isError" in target) return target;
return await params.run(target);
}
async function runWithRequiredActionPinnedMessageTarget(params) {
const target = resolveRequiredActionPinnedMessageTarget({
actionLabel: params.actionLabel,
toolParams: params.toolParams,
currentChannelId: params.currentChannelId,
currentGraphChannelId: params.currentGraphChannelId,
graphOnly: params.graphOnly
});
if ("isError" in target) return target;
return await params.run(target);
}
function describeMSTeamsMessageTool({ cfg }) {
const enabled = cfg.channels?.msteams?.enabled !== false && Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams));
return {
actions: enabled ? [
"upload-file",
"poll",
"edit",
"delete",
"pin",
"unpin",
"list-pins",
"read",
"react",
"reactions",
"search",
"member-info",
"channel-list",
"channel-info",
"addParticipant",
"removeParticipant",
"renameGroup"
] : [],
capabilities: enabled ? ["presentation"] : [],
schema: enabled ? {
actions: ["unpin"],
properties: { pinnedMessageId: Type.Optional(Type.String({ description: "Pinned message resource ID for unpin (from pin or list-pins, not the chat message ID)." })) }
} : null
};
}
const msteamsChannelOutbound = {
deliveryMode: "direct",
chunker: chunkTextForOutbound,
chunkerMode: "markdown",
textChunkLimit: 4e3,
pollMaxOptions: 12,
deliveryCapabilities: { durableFinal: {
text: true,
media: true,
payload: true,
messageSendingHooks: true
} },
presentationCapabilities: MSTEAMS_PRESENTATION_CAPABILITIES,
...createRuntimeOutboundDelegates({
getRuntime: loadMSTeamsChannelRuntime,
renderPresentation: { resolve: (runtime) => runtime.msteamsOutbound.renderPresentation },
sendPayload: { resolve: (runtime) => runtime.msteamsOutbound.sendPayload },
sendText: { resolve: (runtime) => runtime.msteamsOutbound.sendText },
sendMedia: { resolve: (runtime) => runtime.msteamsOutbound.sendMedia },
sendPoll: { resolve: (runtime) => runtime.msteamsOutbound.sendPoll }
})
};
const msteamsMessageAdapter = createChannelMessageAdapterFromOutbound({
id: "msteams",
outbound: msteamsChannelOutbound,
live: {
capabilities: {
draftPreview: true,
previewFinalization: true,
progressUpdates: true,
nativeStreaming: true
},
finalizer: { capabilities: {
finalEdit: true,
normalFallback: true,
previewReceipt: true
} }
}
});
const msteamsPlugin = createChatChannelPlugin({
base: {
id: "msteams",
meta: {
...meta,
aliases: [...meta.aliases]
},
setupWizard: msteamsSetupWizard,
capabilities: {
chatTypes: [
"direct",
"channel",
"thread"
],
polls: true,
threads: true,
media: true
},
streaming: { blockStreamingCoalesceDefaults: {
minChars: 1500,
idleMs: 1e3
} },
agentPrompt: { messageToolHints: () => ["- Adaptive Cards supported. Use `action=send` with `card={type,version,body}` to send rich cards.", "- MSTeams targeting: omit `target` to reply to the current conversation (auto-inferred). Explicit targets: `user:ID` or `user:Display Name` (requires Graph API) for DMs, `conversation:19:...@thread.tacv2` for groups/channels. Prefer IDs over display names for speed."] },
groups: { resolveToolPolicy: resolveMSTeamsGroupToolPolicy },
reload: { configPrefixes: ["channels.msteams"] },
configSchema: MSTeamsChannelConfigSchema,
config: {
...msteamsConfigAdapter,
isConfigured: (_account, cfg) => Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)),
describeAccount: (account) => describeAccountSnapshot({
account,
configured: account.configured
})
},
approvalCapability: msTeamsApprovalAuth,
doctor: {
dmAllowFromMode: "topOnly",
groupModel: "hybrid",
groupAllowFromFallbackToAllowFrom: true,
warnOnEmptyGroupSenderAllowlist: true,
collectMutableAllowlistWarnings: collectMSTeamsMutableAllowlistWarnings
},
setup: msteamsSetupAdapter,
messaging: {
targetPrefixes: ["msteams", "teams"],
normalizeTarget: normalizeMSTeamsMessagingTarget,
resolveOutboundSessionRoute: (params) => resolveMSTeamsOutboundSessionRoute(params),
targetResolver: {
looksLikeId: (raw) => looksLikeMSTeamsTargetId(raw),
hint: "<conversationId|user:ID|conversation:ID>"
}
},
message: msteamsMessageAdapter,
directory: createChannelDirectoryAdapter({
self: async ({ cfg }) => {
const creds = resolveMSTeamsCredentials(cfg.channels?.msteams);
if (!creds) return null;
return {
kind: "user",
id: creds.appId,
name: creds.appId
};
},
listPeers: async ({ cfg, query, limit }) => listDirectoryEntriesFromSources({
kind: "user",
sources: [cfg.channels?.msteams?.allowFrom ?? [], Object.keys(cfg.channels?.msteams?.dms ?? {})],
query,
limit,
normalizeId: (raw) => {
const normalized = normalizeMSTeamsMessagingTarget(raw) ?? raw;
const lowered = normalized.toLowerCase();
if (lowered.startsWith("user:") || lowered.startsWith("conversation:")) return normalized;
return `user:${normalized}`;
}
}),
listGroups: async ({ cfg, query, limit }) => listDirectoryEntriesFromSources({
kind: "group",
sources: [Object.values(cfg.channels?.msteams?.teams ?? {}).flatMap((team) => Object.keys(team.channels ?? {}))],
query,
limit,
normalizeId: (raw) => `conversation:${raw.replace(/^conversation:/i, "").trim()}`
}),
...createRuntimeDirectoryLiveAdapter({
getRuntime: loadMSTeamsChannelRuntime,
listPeersLive: (runtime) => runtime.listMSTeamsDirectoryPeersLive,
listGroupsLive: (runtime) => runtime.listMSTeamsDirectoryGroupsLive
})
}),
resolver: { resolveTargets: async ({ cfg, inputs, kind, runtime }) => {
const results = inputs.map((input) => ({
input,
resolved: false,
id: void 0,
name: void 0,
note: void 0
}));
const stripPrefix = (value) => normalizeMSTeamsUserInput(value);
const markPendingLookupFailed = (pending) => {
pending.forEach(({ index }) => {
const entry = results[index];
if (entry) entry.note = "lookup failed";
});
};
const resolvePending = async (pending, resolveEntries, applyResolvedEntry) => {
if (pending.length === 0) return;
try {
(await resolveEntries(pending.map((entry) => entry.query))).forEach((entry, idx) => {
const target = results[pending[idx]?.index ?? -1];
if (!target) return;
applyResolvedEntry(target, entry);
});
} catch (err) {
runtime.error?.(`msteams resolve failed: ${String(err)}`);
markPendingLookupFailed(pending);
}
};
if (kind === "user") {
const pending = [];
results.forEach((entry, index) => {
const trimmed = entry.input.trim();
if (!trimmed) {
entry.note = "empty input";
return;
}
const cleaned = stripPrefix(trimmed);
if (/^[0-9a-fA-F-]{16,}$/.test(cleaned) || cleaned.includes("@")) {
entry.resolved = true;
entry.id = cleaned;
return;
}
pending.push({
input: entry.input,
query: cleaned,
index
});
});
await resolvePending(pending, (entries) => resolveMSTeamsUserAllowlist({
cfg,
entries
}), (target, entry) => {
target.resolved = entry.resolved;
target.id = entry.id;
target.name = entry.name;
target.note = entry.note;
});
return results;
}
const pending = [];
results.forEach((entry, index) => {
const trimmed = entry.input.trim();
if (!trimmed) {
entry.note = "empty input";
return;
}
const conversationId = parseMSTeamsConversationId(trimmed);
if (conversationId !== null) {
entry.resolved = Boolean(conversationId);
entry.id = conversationId || void 0;
entry.note = conversationId ? "conversation id" : "empty conversation id";
return;
}
const parsed = parseMSTeamsTeamChannelInput(trimmed);
if (!parsed.team) {
entry.note = "missing team";
return;
}
const query = parsed.channel ? `${parsed.team}/${parsed.channel}` : parsed.team;
pending.push({
input: entry.input,
query,
index
});
});
await resolvePending(pending, (entries) => resolveMSTeamsChannelAllowlist({
cfg,
entries
}), (target, entry) => {
if (!entry.resolved || !entry.teamId) {
target.resolved = false;
target.note = entry.note;
return;
}
target.resolved = true;
if (entry.channelId) {
target.id = `${entry.teamId}/${entry.channelId}`;
target.name = entry.channelName && entry.teamName ? `${entry.teamName}/${entry.channelName}` : entry.channelName ?? entry.teamName;
} else {
target.id = entry.teamId;
target.name = entry.teamName;
target.note = "team id";
}
if (entry.note) target.note = entry.note;
});
return results;
} },
actions: {
describeMessageTool: describeMSTeamsMessageTool,
requiresTrustedRequesterSender: ({ action, toolContext }) => normalizeOptionalString(toolContext?.currentChannelProvider)?.toLowerCase() === "msteams" && MSTEAMS_GROUP_MANAGEMENT_ACTIONS.has(action),
handleAction: async (ctx) => {
if (MSTEAMS_GROUP_MANAGEMENT_ACTIONS.has(ctx.action)) {
const authError = requireMSTeamsGroupManagementAuthorization(ctx);
if (authError) return authError;
}
const presentation = ctx.action === "send" ? normalizeMessagePresentation(ctx.params.presentation) : void 0;
if (ctx.action === "send" && presentation) {
const card = buildMSTeamsPresentationCard({
presentation,
text: resolveActionContent(ctx.params)
});
return await runWithRequiredActionTarget({
actionLabel: "Card send",
toolParams: ctx.params,
run: async (to) => {
const { sendAdaptiveCardMSTeams } = await loadMSTeamsChannelRuntime();
const result = await sendAdaptiveCardMSTeams({
cfg: ctx.cfg,
to,
card
});
return jsonActionResultWithDetails({
ok: true,
channel: "msteams",
messageId: result.messageId,
conversationId: result.conversationId
}, {
ok: true,
channel: "msteams",
messageId: result.messageId
});
}
});
}
if (ctx.action === "upload-file") {
const mediaUrl = resolveActionUploadFilePath(ctx.params);
if (!mediaUrl) return actionError("Upload-file requires media, filePath, or path.");
return await runWithRequiredActionTarget({
actionLabel: "Upload-file",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
run: async (to) => {
const { sendMessageMSTeams } = await loadMSTeamsChannelRuntime();
const result = await sendMessageMSTeams({
cfg: ctx.cfg,
to,
text: resolveActionContent(ctx.params),
mediaUrl,
filename: readOptionalTrimmedString(ctx.params, "filename") ?? readOptionalTrimmedString(ctx.params, "title"),
mediaLocalRoots: ctx.mediaLocalRoots,
mediaReadFile: ctx.mediaReadFile
});
return jsonActionResultWithDetails({
ok: true,
channel: "msteams",
action: "upload-file",
messageId: result.messageId,
conversationId: result.conversationId,
...result.pendingUploadId ? { pendingUploadId: result.pendingUploadId } : {}
}, {
ok: true,
channel: "msteams",
messageId: result.messageId,
...result.pendingUploadId ? { pendingUploadId: result.pendingUploadId } : {}
});
}
});
}
if (ctx.action === "edit") {
const content = resolveActionContent(ctx.params);
if (!content) return actionError("Edit requires content.");
return await runWithRequiredActionMessageTarget({
actionLabel: "Edit",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
run: async (target) => {
const { editMessageMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsConversationResult((await editMessageMSTeams({
cfg: ctx.cfg,
to: target.to,
activityId: target.messageId,
text: content
})).conversationId);
}
});
}
if (ctx.action === "delete") return await runWithRequiredActionMessageTarget({
actionLabel: "Delete",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
run: async (target) => {
const { deleteMessageMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsConversationResult((await deleteMessageMSTeams({
cfg: ctx.cfg,
to: target.to,
activityId: target.messageId
})).conversationId);
}
});
if (ctx.action === "read") return await runWithRequiredActionMessageTarget({
actionLabel: "Read",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
graphOnly: true,
run: async (target) => {
const { getMessageMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("read", { message: await getMessageMSTeams({
cfg: ctx.cfg,
to: target.to,
messageId: target.messageId
}) });
}
});
if (ctx.action === "pin") return await runWithRequiredActionMessageTarget({
actionLabel: "Pin",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
graphOnly: true,
run: async (target) => {
const { pinMessageMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsActionResult("pin", await pinMessageMSTeams({
cfg: ctx.cfg,
to: target.to,
messageId: target.messageId
}));
}
});
if (ctx.action === "unpin") return await runWithRequiredActionPinnedMessageTarget({
actionLabel: "Unpin",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
graphOnly: true,
run: async (target) => {
const { unpinMessageMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsActionResult("unpin", await unpinMessageMSTeams({
cfg: ctx.cfg,
to: target.to,
pinnedMessageId: target.pinnedMessageId
}));
}
});
if (ctx.action === "list-pins") return await runWithRequiredActionTarget({
actionLabel: "List-pins",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
graphOnly: true,
run: async (to) => {
const { listPinsMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("list-pins", await listPinsMSTeams({
cfg: ctx.cfg,
to
}));
}
});
if (ctx.action === "react") return await runWithRequiredActionMessageTarget({
actionLabel: "React",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
graphOnly: true,
run: async (target) => {
const emoji = typeof ctx.params.emoji === "string" ? ctx.params.emoji.trim() : "";
const remove = typeof ctx.params.remove === "boolean" ? ctx.params.remove : false;
if (!emoji) return {
isError: true,
content: [{
type: "text",
text: `React requires an emoji (reaction type). Valid types: ${MSTEAMS_REACTION_TYPES.join(", ")}.`
}],
details: {
error: "React requires an emoji (reaction type).",
validTypes: [...MSTEAMS_REACTION_TYPES]
}
};
if (remove) {
const { unreactMessageMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsActionResult("react", {
removed: true,
reactionType: emoji,
...await unreactMessageMSTeams({
cfg: ctx.cfg,
to: target.to,
messageId: target.messageId,
reactionType: emoji
})
});
}
const { reactMessageMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsActionResult("react", {
reactionType: emoji,
...await reactMessageMSTeams({
cfg: ctx.cfg,
to: target.to,
messageId: target.messageId,
reactionType: emoji
})
});
}
});
if (ctx.action === "reactions") return await runWithRequiredActionMessageTarget({
actionLabel: "Reactions",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
graphOnly: true,
run: async (target) => {
const { listReactionsMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("reactions", await listReactionsMSTeams({
cfg: ctx.cfg,
to: target.to,
messageId: target.messageId
}));
}
});
if (ctx.action === "search") return await runWithRequiredActionTarget({
actionLabel: "Search",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
currentGraphChannelId: ctx.toolContext?.currentGraphChannelId,
graphOnly: true,
run: async (to) => {
const query = resolveActionQuery(ctx.params);
if (!query) return actionError("Search requires a target (to) and query.");
const limit = typeof ctx.params.limit === "number" ? ctx.params.limit : void 0;
const from = typeof ctx.params.from === "string" ? ctx.params.from.trim() : void 0;
const { searchMessagesMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("search", await searchMessagesMSTeams({
cfg: ctx.cfg,
to,
query,
from: from || void 0,
limit
}));
}
});
if (ctx.action === "member-info") {
const userId = normalizeOptionalString(ctx.params.userId) ?? "";
if (!userId) return actionError("member-info requires a userId.");
const { getMemberInfoMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("member-info", await getMemberInfoMSTeams({
cfg: ctx.cfg,
userId
}));
}
if (ctx.action === "channel-list") {
const teamId = normalizeOptionalString(ctx.params.teamId) ?? "";
if (!teamId) return actionError("channel-list requires a teamId.");
const { listChannelsMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("channel-list", await listChannelsMSTeams({
cfg: ctx.cfg,
teamId
}));
}
if (ctx.action === "channel-info") {
const teamId = normalizeOptionalString(ctx.params.teamId) ?? "";
const channelId = normalizeOptionalString(ctx.params.channelId) ?? "";
if (!teamId || !channelId) return actionError("channel-info requires teamId and channelId.");
const { getChannelInfoMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("channel-info", { channelInfo: (await getChannelInfoMSTeams({
cfg: ctx.cfg,
teamId,
channelId
})).channel });
}
if (ctx.action === "addParticipant") {
const userId = typeof ctx.params.userId === "string" ? ctx.params.userId.trim() : "";
if (!userId) return actionError("addParticipant requires a userId.");
return await runWithRequiredActionTarget({
actionLabel: "addParticipant",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
run: async (to) => {
const role = readOptionalTrimmedString(ctx.params, "role");
const { addParticipantMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("addParticipant", await addParticipantMSTeams({
cfg: ctx.cfg,
to,
userId,
role
}));
}
});
}
if (ctx.action === "removeParticipant") {
const userId = typeof ctx.params.userId === "string" ? ctx.params.userId.trim() : "";
if (!userId) return actionError("removeParticipant requires a userId.");
return await runWithRequiredActionTarget({
actionLabel: "removeParticipant",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
run: async (to) => {
const { removeParticipantMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("removeParticipant", await removeParticipantMSTeams({
cfg: ctx.cfg,
to,
userId
}));
}
});
}
if (ctx.action === "renameGroup") {
const name = typeof ctx.params.name === "string" ? ctx.params.name.trim() : "";
if (!name) return actionError("renameGroup requires a name.");
return await runWithRequiredActionTarget({
actionLabel: "renameGroup",
toolParams: ctx.params,
currentChannelId: ctx.toolContext?.currentChannelId,
run: async (to) => {
const { renameGroupMSTeams } = await loadMSTeamsChannelRuntime();
return jsonMSTeamsOkActionResult("renameGroup", await renameGroupMSTeams({
cfg: ctx.cfg,
to,
name
}));
}
});
}
return null;
}
},
status: createComputedAccountStatusAdapter({
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, { port: null }),
buildChannelSummary: ({ snapshot }) => buildProbeChannelStatusSummary(snapshot, { port: snapshot.port ?? null }),
probeAccount: async ({ cfg }) => await (await loadMSTeamsChannelRuntime()).probeMSTeams(cfg.channels?.msteams),
formatCapabilitiesProbe: ({ probe }) => {
const teamsProbe = probe;
const lines = [];
const appId = typeof teamsProbe?.appId === "string" ? teamsProbe.appId.trim() : "";
if (appId) lines.push({ text: `App: ${appId}` });
const graph = teamsProbe?.graph;
if (graph) {
const roles = Array.isArray(graph.roles) ? normalizeStringEntries(graph.roles) : [];
const scopes = Array.isArray(graph.scopes) ? normalizeStringEntries(graph.scopes) : [];
const formatPermission = (permission) => {
const hint = TEAMS_GRAPH_PERMISSION_HINTS[permission];
return hint ? `${permission} (${hint})` : permission;
};
if (!graph.ok) lines.push({
text: `Graph: ${graph.error ?? "failed"}`,
tone: "error"
});
else if (roles.length > 0 || scopes.length > 0) {
if (roles.length > 0) lines.push({ text: `Graph roles: ${roles.map(formatPermission).join(", ")}` });
if (scopes.length > 0) lines.push({ text: `Graph scopes: ${scopes.map(formatPermission).join(", ")}` });
} else if (graph.ok) lines.push({ text: "Graph: ok" });
}
return lines;
},
resolveAccountSnapshot: ({ account, runtime }) => ({
accountId: account.accountId,
enabled: account.enabled,
configured: account.configured,
extra: { port: runtime?.port ?? null }
})
}),
gateway: { startAccount: async (ctx) => {
const { monitorMSTeamsProvider } = await import("./src-DkdiCzc5.js");
const port = ctx.cfg.channels?.msteams?.webhook?.port ?? 3978;
ctx.setStatus({
accountId: ctx.accountId,
port
});
ctx.log?.info(`starting provider (port ${port})`);
return monitorMSTeamsProvider({
cfg: ctx.cfg,
runtime: ctx.runtime,
abortSignal: ctx.abortSignal
});
} }
},
security: { collectWarnings: projectConfigWarningCollector(collectMSTeamsSecurityWarnings) },
pairing: { text: {
idLabel: "msteamsUserId",
message: PAIRING_APPROVED_MESSAGE,
normalizeAllowEntry: createPairingPrefixStripper(/^(msteams|user):/i),
notify: async ({ cfg, id, message }) => {
const { sendMessageMSTeams } = await loadMSTeamsChannelRuntime();
await sendMessageMSTeams({
cfg,
to: id,
text: message
});
}
} },
threading: { buildToolContext: ({ context, hasRepliedRef }) => {
const nativeChannelId = context.NativeChannelId?.trim();
const hasChannelRoute = Boolean(nativeChannelId && nativeChannelId.includes("/"));
return {
currentChannelId: normalizeOptionalString(context.To),
currentGraphChannelId: hasChannelRoute ? nativeChannelId : void 0,
currentThreadTs: context.ReplyToId,
hasRepliedRef
};
} },
outbound: msteamsChannelOutbound
});
//#endregion
export { msteamsPlugin as t };