import { o as __toESM } from "./chunk-D_gEzPfs.js";
import { SILENT_REPLY_TOKEN, buildChannelKeyCandidates, detectMime, extensionForMime, extractOriginalFilename, getFileExtension, isSilentReplyText, loadWebMedia, maybeInjectDisclosure, normalizeChannelSlug, resolveAllowlistMatchSimple, resolveChannelEntryMatchWithFallback, resolveChannelMediaMaxBytes, resolveDisclosureConfig, resolveNestedAllowlistDecision, resolveToolsBySender, sleep, withFileLock as withFileLock$1 } from "minion/plugin-sdk";
import crypto from "node:crypto";
import path from "node:path";
import fs from "node:fs";
import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk";

//#region extensions/msteams/src/attachments/shared.ts
const IMAGE_EXT_RE = /\.(avif|bmp|gif|heic|heif|jpe?g|png|tiff?|webp)$/i;
const IMG_SRC_RE = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
const ATTACHMENT_TAG_RE = /<attachment[^>]+id=["']([^"']+)["'][^>]*>/gi;
const DEFAULT_MEDIA_HOST_ALLOWLIST = [
	"graph.microsoft.com",
	"graph.microsoft.us",
	"graph.microsoft.de",
	"graph.microsoft.cn",
	"sharepoint.com",
	"sharepoint.us",
	"sharepoint.de",
	"sharepoint.cn",
	"sharepoint-df.com",
	"1drv.ms",
	"onedrive.com",
	"teams.microsoft.com",
	"teams.cdn.office.net",
	"statics.teams.cdn.office.net",
	"office.com",
	"office.net",
	"asm.skype.com",
	"ams.skype.com",
	"media.ams.skype.com",
	"trafficmanager.net",
	"blob.core.windows.net",
	"azureedge.net",
	"microsoft.com"
];
const DEFAULT_MEDIA_AUTH_HOST_ALLOWLIST = [
	"api.botframework.com",
	"botframework.com",
	"graph.microsoft.com",
	"graph.microsoft.us",
	"graph.microsoft.de",
	"graph.microsoft.cn"
];
const GRAPH_ROOT$1 = "https://graph.microsoft.com/v1.0";
function isRecord$2(value) {
	return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function normalizeContentType(value) {
	if (typeof value !== "string") return;
	const trimmed = value.trim();
	return trimmed ? trimmed : void 0;
}
function inferPlaceholder(params) {
	const mime = params.contentType?.toLowerCase() ?? "";
	const name = params.fileName?.toLowerCase() ?? "";
	const fileType = params.fileType?.toLowerCase() ?? "";
	return mime.startsWith("image/") || IMAGE_EXT_RE.test(name) || IMAGE_EXT_RE.test(`x.${fileType}`) ? "<media:image>" : "<media:document>";
}
function isLikelyImageAttachment(att) {
	const contentType = normalizeContentType(att.contentType) ?? "";
	const name = typeof att.name === "string" ? att.name : "";
	if (contentType.startsWith("image/")) return true;
	if (IMAGE_EXT_RE.test(name)) return true;
	if (contentType === "application/vnd.microsoft.teams.file.download.info" && isRecord$2(att.content)) {
		const fileType = typeof att.content.fileType === "string" ? att.content.fileType : "";
		if (fileType && IMAGE_EXT_RE.test(`x.${fileType}`)) return true;
		const fileName = typeof att.content.fileName === "string" ? att.content.fileName : "";
		if (fileName && IMAGE_EXT_RE.test(fileName)) return true;
	}
	return false;
}
/**
* Returns true if the attachment can be downloaded (any file type).
* Used when downloading all files, not just images.
*/
function isDownloadableAttachment(att) {
	if ((normalizeContentType(att.contentType) ?? "") === "application/vnd.microsoft.teams.file.download.info" && isRecord$2(att.content) && typeof att.content.downloadUrl === "string") return true;
	if (typeof att.contentUrl === "string" && att.contentUrl.trim()) return true;
	return false;
}
function isHtmlAttachment(att) {
	return (normalizeContentType(att.contentType) ?? "").startsWith("text/html");
}
function extractHtmlFromAttachment(att) {
	if (!isHtmlAttachment(att)) return;
	if (typeof att.content === "string") return att.content;
	if (!isRecord$2(att.content)) return;
	return typeof att.content.text === "string" ? att.content.text : typeof att.content.body === "string" ? att.content.body : typeof att.content.content === "string" ? att.content.content : void 0;
}
function decodeDataImage(src) {
	const match = /^data:(image\/[a-z0-9.+-]+)?(;base64)?,(.*)$/i.exec(src);
	if (!match) return null;
	const contentType = match[1]?.toLowerCase();
	if (!Boolean(match[2])) return null;
	const payload = match[3] ?? "";
	if (!payload) return null;
	try {
		return {
			kind: "data",
			data: Buffer.from(payload, "base64"),
			contentType,
			placeholder: "<media:image>"
		};
	} catch {
		return null;
	}
}
function fileHintFromUrl(src) {
	try {
		return new URL(src).pathname.split("/").pop() || void 0;
	} catch {
		return;
	}
}
function extractInlineImageCandidates(attachments) {
	const out = [];
	for (const att of attachments) {
		const html = extractHtmlFromAttachment(att);
		if (!html) continue;
		IMG_SRC_RE.lastIndex = 0;
		let match = IMG_SRC_RE.exec(html);
		while (match) {
			const src = match[1]?.trim();
			if (src && !src.startsWith("cid:")) if (src.startsWith("data:")) {
				const decoded = decodeDataImage(src);
				if (decoded) out.push(decoded);
			} else out.push({
				kind: "url",
				url: src,
				fileHint: fileHintFromUrl(src),
				placeholder: "<media:image>"
			});
			match = IMG_SRC_RE.exec(html);
		}
	}
	return out;
}
function safeHostForUrl(url) {
	try {
		return new URL(url).hostname.toLowerCase();
	} catch {
		return "invalid-url";
	}
}
function normalizeAllowHost(value) {
	const trimmed = value.trim().toLowerCase();
	if (!trimmed) return "";
	if (trimmed === "*") return "*";
	return trimmed.replace(/^\*\.?/, "");
}
function resolveAllowedHosts(input) {
	if (!Array.isArray(input) || input.length === 0) return DEFAULT_MEDIA_HOST_ALLOWLIST.slice();
	const normalized = input.map(normalizeAllowHost).filter(Boolean);
	if (normalized.includes("*")) return ["*"];
	return normalized;
}
function resolveAuthAllowedHosts(input) {
	if (!Array.isArray(input) || input.length === 0) return DEFAULT_MEDIA_AUTH_HOST_ALLOWLIST.slice();
	const normalized = input.map(normalizeAllowHost).filter(Boolean);
	if (normalized.includes("*")) return ["*"];
	return normalized;
}
function isHostAllowed(host, allowlist) {
	if (allowlist.includes("*")) return true;
	const normalized = host.toLowerCase();
	return allowlist.some((entry) => normalized === entry || normalized.endsWith(`.${entry}`));
}
function isUrlAllowed(url, allowlist) {
	try {
		const parsed = new URL(url);
		if (parsed.protocol !== "https:") return false;
		return isHostAllowed(parsed.hostname, allowlist);
	} catch {
		return false;
	}
}

//#endregion
//#region extensions/msteams/src/sdk.ts
async function loadMSTeamsSdk() {
	return await import("./src-D-saK1Df.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
}
function buildMSTeamsAuthConfig(creds, sdk) {
	return sdk.getAuthConfigWithDefaults({
		clientId: creds.appId,
		clientSecret: creds.appPassword,
		tenantId: creds.tenantId
	});
}
function createMSTeamsAdapter(authConfig, sdk) {
	return new sdk.CloudAdapter(authConfig);
}
async function loadMSTeamsSdkWithAuth(creds) {
	const sdk = await loadMSTeamsSdk();
	return {
		sdk,
		authConfig: buildMSTeamsAuthConfig(creds, sdk)
	};
}

//#endregion
//#region extensions/msteams/src/token.ts
function resolveMSTeamsCredentials(cfg) {
	const appId = cfg?.appId?.trim() || process.env.MSTEAMS_APP_ID?.trim();
	const appPassword = cfg?.appPassword?.trim() || process.env.MSTEAMS_APP_PASSWORD?.trim();
	const tenantId = cfg?.tenantId?.trim() || process.env.MSTEAMS_TENANT_ID?.trim();
	if (!appId || !appPassword || !tenantId) return;
	return {
		appId,
		appPassword,
		tenantId
	};
}

//#endregion
//#region extensions/msteams/src/graph.ts
function readAccessToken$1(value) {
	if (typeof value === "string") return value;
	if (value && typeof value === "object") {
		const token = value.accessToken ?? value.token;
		return typeof token === "string" ? token : null;
	}
	return null;
}
function normalizeQuery(value) {
	return value?.trim() ?? "";
}
function escapeOData(value) {
	return value.replace(/'/g, "''");
}
async function fetchGraphJson(params) {
	const res = await fetch(`${GRAPH_ROOT$1}${params.path}`, { headers: {
		Authorization: `Bearer ${params.token}`,
		...params.headers
	} });
	if (!res.ok) {
		const text = await res.text().catch(() => "");
		throw new Error(`Graph ${params.path} failed (${res.status}): ${text || "unknown error"}`);
	}
	return await res.json();
}
async function resolveGraphToken(cfg) {
	const creds = resolveMSTeamsCredentials(cfg?.channels?.msteams);
	if (!creds) throw new Error("MS Teams credentials missing");
	const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds);
	const accessToken = readAccessToken$1(await new sdk.MsalTokenProvider(authConfig).getAccessToken("https://graph.microsoft.com"));
	if (!accessToken) throw new Error("MS Teams graph token unavailable");
	return accessToken;
}
async function listTeamsByName(token, query) {
	const filter = `resourceProvisioningOptions/Any(x:x eq 'Team') and startsWith(displayName,'${escapeOData(query)}')`;
	return (await fetchGraphJson({
		token,
		path: `/groups?$filter=${encodeURIComponent(filter)}&$select=id,displayName`
	})).value ?? [];
}
async function listChannelsForTeam(token, teamId) {
	return (await fetchGraphJson({
		token,
		path: `/teams/${encodeURIComponent(teamId)}/channels?$select=id,displayName`
	})).value ?? [];
}

//#endregion
//#region extensions/msteams/src/resolve-allowlist.ts
function stripProviderPrefix(raw) {
	return raw.replace(/^(msteams|teams):/i, "");
}
function normalizeMSTeamsMessagingTarget(raw) {
	let trimmed = raw.trim();
	if (!trimmed) return;
	trimmed = stripProviderPrefix(trimmed).trim();
	if (/^conversation:/i.test(trimmed)) {
		const id = trimmed.slice(13).trim();
		return id ? `conversation:${id}` : void 0;
	}
	if (/^user:/i.test(trimmed)) {
		const id = trimmed.slice(5).trim();
		return id ? `user:${id}` : void 0;
	}
	return trimmed || void 0;
}
function normalizeMSTeamsUserInput(raw) {
	return stripProviderPrefix(raw).replace(/^(user|conversation):/i, "").trim();
}
function parseMSTeamsConversationId(raw) {
	const trimmed = stripProviderPrefix(raw).trim();
	if (!/^conversation:/i.test(trimmed)) return null;
	return trimmed.slice(13).trim();
}
function normalizeMSTeamsTeamKey(raw) {
	return stripProviderPrefix(raw).replace(/^team:/i, "").trim() || void 0;
}
function normalizeMSTeamsChannelKey(raw) {
	return (raw?.trim().replace(/^#/, "").trim() ?? "") || void 0;
}
function parseMSTeamsTeamChannelInput(raw) {
	const trimmed = stripProviderPrefix(raw).trim();
	if (!trimmed) return {};
	const parts = trimmed.split("/");
	const team = normalizeMSTeamsTeamKey(parts[0] ?? "");
	const channel = parts.length > 1 ? normalizeMSTeamsChannelKey(parts.slice(1).join("/")) : void 0;
	return {
		...team ? { team } : {},
		...channel ? { channel } : {}
	};
}
function parseMSTeamsTeamEntry(raw) {
	const { team, channel } = parseMSTeamsTeamChannelInput(raw);
	if (!team) return null;
	return {
		teamKey: team,
		...channel ? { channelKey: channel } : {}
	};
}
async function resolveMSTeamsChannelAllowlist(params) {
	const token = await resolveGraphToken(params.cfg);
	const results = [];
	for (const input of params.entries) {
		const { team, channel } = parseMSTeamsTeamChannelInput(input);
		if (!team) {
			results.push({
				input,
				resolved: false
			});
			continue;
		}
		const teams = /^[0-9a-fA-F-]{16,}$/.test(team) ? [{
			id: team,
			displayName: team
		}] : await listTeamsByName(token, team);
		if (teams.length === 0) {
			results.push({
				input,
				resolved: false,
				note: "team not found"
			});
			continue;
		}
		const teamMatch = teams[0];
		const teamId = teamMatch.id?.trim();
		const teamName = teamMatch.displayName?.trim() || team;
		if (!teamId) {
			results.push({
				input,
				resolved: false,
				note: "team id missing"
			});
			continue;
		}
		if (!channel) {
			results.push({
				input,
				resolved: true,
				teamId,
				teamName,
				note: teams.length > 1 ? "multiple teams; chose first" : void 0
			});
			continue;
		}
		const channels = await listChannelsForTeam(token, teamId);
		const channelMatch = channels.find((item) => item.id === channel) ?? channels.find((item) => item.displayName?.toLowerCase() === channel.toLowerCase()) ?? channels.find((item) => item.displayName?.toLowerCase().includes(channel.toLowerCase() ?? ""));
		if (!channelMatch?.id) {
			results.push({
				input,
				resolved: false,
				note: "channel not found"
			});
			continue;
		}
		results.push({
			input,
			resolved: true,
			teamId,
			teamName,
			channelId: channelMatch.id,
			channelName: channelMatch.displayName ?? channel,
			note: channels.length > 1 ? "multiple channels; chose first" : void 0
		});
	}
	return results;
}
async function resolveMSTeamsUserAllowlist(params) {
	const token = await resolveGraphToken(params.cfg);
	const results = [];
	for (const input of params.entries) {
		const query = normalizeQuery(normalizeMSTeamsUserInput(input));
		if (!query) {
			results.push({
				input,
				resolved: false
			});
			continue;
		}
		if (/^[0-9a-fA-F-]{16,}$/.test(query)) {
			results.push({
				input,
				resolved: true,
				id: query
			});
			continue;
		}
		let users = [];
		if (query.includes("@")) {
			const escaped = escapeOData(query);
			const filter = `(mail eq '${escaped}' or userPrincipalName eq '${escaped}')`;
			users = (await fetchGraphJson({
				token,
				path: `/users?$filter=${encodeURIComponent(filter)}&$select=id,displayName,mail,userPrincipalName`
			})).value ?? [];
		} else users = (await fetchGraphJson({
			token,
			path: `/users?$search=${encodeURIComponent(`"displayName:${query}"`)}&$select=id,displayName,mail,userPrincipalName&$top=10`,
			headers: { ConsistencyLevel: "eventual" }
		})).value ?? [];
		const match = users[0];
		if (!match?.id) {
			results.push({
				input,
				resolved: false
			});
			continue;
		}
		results.push({
			input,
			resolved: true,
			id: match.id,
			name: match.displayName ?? void 0,
			note: users.length > 1 ? "multiple matches; chose first" : void 0
		});
	}
	return results;
}

//#endregion
//#region extensions/msteams/src/runtime.ts
let runtime = null;
function setMSTeamsRuntime(next) {
	runtime = next;
}
function getMSTeamsRuntime() {
	if (!runtime) throw new Error("MSTeams runtime not initialized");
	return runtime;
}

//#endregion
//#region extensions/msteams/src/storage.ts
function resolveMSTeamsStorePath(params) {
	if (params.storePath) return params.storePath;
	if (params.stateDir) return path.join(params.stateDir, params.filename);
	const env = params.env ?? process.env;
	const stateDir = params.homedir ? getMSTeamsRuntime().state.resolveStateDir(env, params.homedir) : getMSTeamsRuntime().state.resolveStateDir(env);
	return path.join(stateDir, params.filename);
}

//#endregion
//#region extensions/msteams/src/store-fs.ts
const STORE_LOCK_OPTIONS = {
	retries: {
		retries: 10,
		factor: 2,
		minTimeout: 100,
		maxTimeout: 1e4,
		randomize: true
	},
	stale: 3e4
};
async function readJsonFile(filePath, fallback) {
	return await readJsonFileWithFallback(filePath, fallback);
}
async function writeJsonFile(filePath, value) {
	await writeJsonFileAtomically(filePath, value);
}
async function ensureJsonFile(filePath, fallback) {
	try {
		await fs.promises.access(filePath);
	} catch {
		await writeJsonFile(filePath, fallback);
	}
}
async function withFileLock(filePath, fallback, fn) {
	await ensureJsonFile(filePath, fallback);
	return await withFileLock$1(filePath, STORE_LOCK_OPTIONS, async () => {
		return await fn();
	});
}

//#endregion
//#region extensions/msteams/src/polls.ts
const STORE_FILENAME$1 = "msteams-polls.json";
const MAX_POLLS = 1e3;
const POLL_TTL_MS = 720 * 60 * 60 * 1e3;
function isRecord$1(value) {
	return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function normalizeChoiceValue(value) {
	if (typeof value === "string") {
		const trimmed = value.trim();
		return trimmed ? trimmed : null;
	}
	if (typeof value === "number" && Number.isFinite(value)) return String(value);
	return null;
}
function extractSelections(value) {
	if (Array.isArray(value)) return value.map(normalizeChoiceValue).filter((entry) => Boolean(entry));
	const normalized = normalizeChoiceValue(value);
	if (!normalized) return [];
	if (normalized.includes(",")) return normalized.split(",").map((entry) => entry.trim()).filter(Boolean);
	return [normalized];
}
function readNestedValue(value, keys) {
	let current = value;
	for (const key of keys) {
		if (!isRecord$1(current)) return;
		current = current[key];
	}
	return current;
}
function readNestedString(value, keys) {
	const found = readNestedValue(value, keys);
	return typeof found === "string" && found.trim() ? found.trim() : void 0;
}
function extractMSTeamsPollVote(activity) {
	const value = activity?.value;
	if (!value || !isRecord$1(value)) return null;
	const pollId = readNestedString(value, ["minionPollId"]) ?? readNestedString(value, ["openclawPollId"]) ?? readNestedString(value, ["pollId"]) ?? readNestedString(value, ["minion", "pollId"]) ?? readNestedString(value, [
		"minion",
		"poll",
		"id"
	]) ?? readNestedString(value, ["data", "minionPollId"]) ?? readNestedString(value, ["data", "pollId"]) ?? readNestedString(value, [
		"data",
		"minion",
		"pollId"
	]);
	if (!pollId) return null;
	const directSelections = extractSelections(value.choices);
	const nestedSelections = extractSelections(readNestedValue(value, ["choices"]));
	const dataSelections = extractSelections(readNestedValue(value, ["data", "choices"]));
	const selections = directSelections.length > 0 ? directSelections : nestedSelections.length > 0 ? nestedSelections : dataSelections;
	if (selections.length === 0) return null;
	return {
		pollId,
		selections
	};
}
function buildMSTeamsPollCard(params) {
	const pollId = params.pollId ?? crypto.randomUUID();
	const maxSelections = typeof params.maxSelections === "number" && params.maxSelections > 1 ? Math.floor(params.maxSelections) : 1;
	const cappedMaxSelections = Math.min(Math.max(1, maxSelections), params.options.length);
	const choices = params.options.map((option, index) => ({
		title: option,
		value: String(index)
	}));
	const hint = cappedMaxSelections > 1 ? `Select up to ${cappedMaxSelections} option${cappedMaxSelections === 1 ? "" : "s"}.` : "Select one option.";
	const card = {
		type: "AdaptiveCard",
		version: "1.5",
		body: [
			{
				type: "TextBlock",
				text: params.question,
				wrap: true,
				weight: "Bolder",
				size: "Medium"
			},
			{
				type: "Input.ChoiceSet",
				id: "choices",
				isMultiSelect: cappedMaxSelections > 1,
				style: "expanded",
				choices
			},
			{
				type: "TextBlock",
				text: hint,
				wrap: true,
				isSubtle: true,
				spacing: "Small"
			}
		],
		actions: [{
			type: "Action.Submit",
			title: "Vote",
			data: {
				minionPollId: pollId,
				pollId
			},
			msteams: {
				type: "messageBack",
				text: "minion poll vote",
				displayText: "Vote recorded",
				value: {
					minionPollId: pollId,
					pollId
				}
			}
		}]
	};
	const fallbackLines = [`Poll: ${params.question}`, ...params.options.map((option, index) => `${index + 1}. ${option}`)];
	return {
		pollId,
		question: params.question,
		options: params.options,
		maxSelections: cappedMaxSelections,
		card,
		fallbackText: fallbackLines.join("\n")
	};
}
function parseTimestamp$1(value) {
	if (!value) return null;
	const parsed = Date.parse(value);
	return Number.isFinite(parsed) ? parsed : null;
}
function pruneExpired$1(polls) {
	const cutoff = Date.now() - POLL_TTL_MS;
	const entries = Object.entries(polls).filter(([, poll]) => {
		return (parseTimestamp$1(poll.updatedAt ?? poll.createdAt) ?? 0) >= cutoff;
	});
	return Object.fromEntries(entries);
}
function pruneToLimit$1(polls) {
	const entries = Object.entries(polls);
	if (entries.length <= MAX_POLLS) return polls;
	entries.sort((a, b) => {
		return (parseTimestamp$1(a[1].updatedAt ?? a[1].createdAt) ?? 0) - (parseTimestamp$1(b[1].updatedAt ?? b[1].createdAt) ?? 0);
	});
	const keep = entries.slice(entries.length - MAX_POLLS);
	return Object.fromEntries(keep);
}
function normalizeMSTeamsPollSelections(poll, selections) {
	const maxSelections = Math.max(1, poll.maxSelections);
	const mapped = selections.map((entry) => Number.parseInt(entry, 10)).filter((value) => Number.isFinite(value)).filter((value) => value >= 0 && value < poll.options.length).map((value) => String(value));
	const limited = maxSelections > 1 ? mapped.slice(0, maxSelections) : mapped.slice(0, 1);
	return Array.from(new Set(limited));
}
function createMSTeamsPollStoreFs(params) {
	const filePath = resolveMSTeamsStorePath({
		filename: STORE_FILENAME$1,
		env: params?.env,
		homedir: params?.homedir,
		stateDir: params?.stateDir,
		storePath: params?.storePath
	});
	const empty = {
		version: 1,
		polls: {}
	};
	const readStore = async () => {
		const { value } = await readJsonFile(filePath, empty);
		return {
			version: 1,
			polls: pruneToLimit$1(pruneExpired$1(value.polls ?? {}))
		};
	};
	const writeStore = async (data) => {
		await writeJsonFile(filePath, data);
	};
	const createPoll = async (poll) => {
		await withFileLock(filePath, empty, async () => {
			const data = await readStore();
			data.polls[poll.id] = poll;
			await writeStore({
				version: 1,
				polls: pruneToLimit$1(data.polls)
			});
		});
	};
	const getPoll = async (pollId) => await withFileLock(filePath, empty, async () => {
		return (await readStore()).polls[pollId] ?? null;
	});
	const recordVote = async (params) => await withFileLock(filePath, empty, async () => {
		const data = await readStore();
		const poll = data.polls[params.pollId];
		if (!poll) return null;
		const normalized = normalizeMSTeamsPollSelections(poll, params.selections);
		poll.votes[params.voterId] = normalized;
		poll.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
		data.polls[poll.id] = poll;
		await writeStore({
			version: 1,
			polls: pruneToLimit$1(data.polls)
		});
		return poll;
	});
	return {
		createPoll,
		getPoll,
		recordVote
	};
}

//#endregion
//#region extensions/msteams/src/conversation-store-fs.ts
const STORE_FILENAME = "msteams-conversations.json";
const MAX_CONVERSATIONS = 1e3;
const CONVERSATION_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
function parseTimestamp(value) {
	if (!value) return null;
	const parsed = Date.parse(value);
	if (!Number.isFinite(parsed)) return null;
	return parsed;
}
function pruneToLimit(conversations) {
	const entries = Object.entries(conversations);
	if (entries.length <= MAX_CONVERSATIONS) return conversations;
	entries.sort((a, b) => {
		return (parseTimestamp(a[1].lastSeenAt) ?? 0) - (parseTimestamp(b[1].lastSeenAt) ?? 0);
	});
	const keep = entries.slice(entries.length - MAX_CONVERSATIONS);
	return Object.fromEntries(keep);
}
function pruneExpired(conversations, nowMs, ttlMs) {
	let removed = false;
	const kept = {};
	for (const [conversationId, reference] of Object.entries(conversations)) {
		const lastSeenAt = parseTimestamp(reference.lastSeenAt);
		if (lastSeenAt != null && nowMs - lastSeenAt > ttlMs) {
			removed = true;
			continue;
		}
		kept[conversationId] = reference;
	}
	return {
		conversations: kept,
		removed
	};
}
function normalizeConversationId$1(raw) {
	return raw.split(";")[0] ?? raw;
}
function createMSTeamsConversationStoreFs(params) {
	const ttlMs = params?.ttlMs ?? CONVERSATION_TTL_MS;
	const filePath = resolveMSTeamsStorePath({
		filename: STORE_FILENAME,
		env: params?.env,
		homedir: params?.homedir,
		stateDir: params?.stateDir,
		storePath: params?.storePath
	});
	const empty = {
		version: 1,
		conversations: {}
	};
	const readStore = async () => {
		const { value } = await readJsonFile(filePath, empty);
		if (value.version !== 1 || !value.conversations || typeof value.conversations !== "object" || Array.isArray(value.conversations)) return empty;
		const nowMs = Date.now();
		const pruned = pruneExpired(value.conversations, nowMs, ttlMs).conversations;
		return {
			version: 1,
			conversations: pruneToLimit(pruned)
		};
	};
	const list = async () => {
		const store = await readStore();
		return Object.entries(store.conversations).map(([conversationId, reference]) => ({
			conversationId,
			reference
		}));
	};
	const get = async (conversationId) => {
		return (await readStore()).conversations[normalizeConversationId$1(conversationId)] ?? null;
	};
	const findByUserId = async (id) => {
		const target = id.trim();
		if (!target) return null;
		for (const entry of await list()) {
			const { conversationId, reference } = entry;
			if (reference.user?.aadObjectId === target) return {
				conversationId,
				reference
			};
			if (reference.user?.id === target) return {
				conversationId,
				reference
			};
		}
		return null;
	};
	const upsert = async (conversationId, reference) => {
		const normalizedId = normalizeConversationId$1(conversationId);
		await withFileLock(filePath, empty, async () => {
			const store = await readStore();
			store.conversations[normalizedId] = {
				...reference,
				lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
			};
			const nowMs = Date.now();
			store.conversations = pruneExpired(store.conversations, nowMs, ttlMs).conversations;
			store.conversations = pruneToLimit(store.conversations);
			await writeJsonFile(filePath, store);
		});
	};
	const remove = async (conversationId) => {
		const normalizedId = normalizeConversationId$1(conversationId);
		return await withFileLock(filePath, empty, async () => {
			const store = await readStore();
			if (!(normalizedId in store.conversations)) return false;
			delete store.conversations[normalizedId];
			await writeJsonFile(filePath, store);
			return true;
		});
	};
	return {
		upsert,
		get,
		list,
		remove,
		findByUserId
	};
}

//#endregion
//#region extensions/msteams/src/errors.ts
function formatUnknownError(err) {
	if (err instanceof Error) return err.message;
	if (typeof err === "string") return err;
	if (err === null) return "null";
	if (err === void 0) return "undefined";
	if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") return String(err);
	if (typeof err === "symbol") return err.description ?? err.toString();
	if (typeof err === "function") return err.name ? `[function ${err.name}]` : "[function]";
	try {
		return JSON.stringify(err) ?? "unknown error";
	} catch {
		return "unknown error";
	}
}
function isRecord(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
function extractStatusCode(err) {
	if (!isRecord(err)) return null;
	const direct = err.statusCode ?? err.status;
	if (typeof direct === "number" && Number.isFinite(direct)) return direct;
	if (typeof direct === "string") {
		const parsed = Number.parseInt(direct, 10);
		if (Number.isFinite(parsed)) return parsed;
	}
	const response = err.response;
	if (isRecord(response)) {
		const status = response.status;
		if (typeof status === "number" && Number.isFinite(status)) return status;
		if (typeof status === "string") {
			const parsed = Number.parseInt(status, 10);
			if (Number.isFinite(parsed)) return parsed;
		}
	}
	return null;
}
function extractRetryAfterMs(err) {
	if (!isRecord(err)) return null;
	const direct = err.retryAfterMs ?? err.retry_after_ms;
	if (typeof direct === "number" && Number.isFinite(direct) && direct >= 0) return direct;
	const retryAfter = err.retryAfter ?? err.retry_after;
	if (typeof retryAfter === "number" && Number.isFinite(retryAfter)) return retryAfter >= 0 ? retryAfter * 1e3 : null;
	if (typeof retryAfter === "string") {
		const parsed = Number.parseFloat(retryAfter);
		if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1e3;
	}
	const response = err.response;
	if (!isRecord(response)) return null;
	const headers = response.headers;
	if (!headers) return null;
	if (isRecord(headers)) {
		const raw = headers["retry-after"] ?? headers["Retry-After"];
		if (typeof raw === "string") {
			const parsed = Number.parseFloat(raw);
			if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1e3;
		}
	}
	if (typeof headers === "object" && headers !== null && "get" in headers && typeof headers.get === "function") {
		const raw = headers.get("retry-after");
		if (raw) {
			const parsed = Number.parseFloat(raw);
			if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1e3;
		}
	}
	return null;
}
/**
* Classify outbound send errors for safe retries and actionable logs.
*
* Important: We only mark errors as retryable when we have an explicit HTTP
* status code that indicates the message was not accepted (e.g. 429, 5xx).
* For transport-level errors where delivery is ambiguous, we prefer to avoid
* retries to reduce the chance of duplicate posts.
*/
function classifyMSTeamsSendError(err) {
	const statusCode = extractStatusCode(err);
	const retryAfterMs = extractRetryAfterMs(err);
	if (statusCode === 401 || statusCode === 403) return {
		kind: "auth",
		statusCode
	};
	if (statusCode === 429) return {
		kind: "throttled",
		statusCode,
		retryAfterMs: retryAfterMs ?? void 0
	};
	if (statusCode === 408 || statusCode != null && statusCode >= 500) return {
		kind: "transient",
		statusCode,
		retryAfterMs: retryAfterMs ?? void 0
	};
	if (statusCode != null && statusCode >= 400) return {
		kind: "permanent",
		statusCode
	};
	return {
		kind: "unknown",
		statusCode: statusCode ?? void 0,
		retryAfterMs: retryAfterMs ?? void 0
	};
}
function formatMSTeamsSendErrorHint(classification) {
	if (classification.kind === "auth") return "check msteams appId/appPassword/tenantId (or env vars MSTEAMS_APP_ID/MSTEAMS_APP_PASSWORD/MSTEAMS_TENANT_ID)";
	if (classification.kind === "throttled") return "Teams throttled the bot; backing off may help";
	if (classification.kind === "transient") return "transient Teams/Bot Framework error; retry may succeed";
}

//#endregion
//#region extensions/msteams/src/file-consent.ts
/**
* Build a FileConsentCard attachment for requesting upload permission.
* Use this for files >= 4MB in personal (1:1) chats.
*/
function buildFileConsentCard(params) {
	return {
		contentType: "application/vnd.microsoft.teams.card.file.consent",
		name: params.filename,
		content: {
			description: params.description ?? `File: ${params.filename}`,
			sizeInBytes: params.sizeInBytes,
			acceptContext: {
				filename: params.filename,
				...params.context
			},
			declineContext: {
				filename: params.filename,
				...params.context
			}
		}
	};
}
/**
* Build a FileInfoCard attachment for confirming upload completion.
* Send this after successfully uploading the file to the consent URL.
*/
function buildFileInfoCard(params) {
	return {
		contentType: "application/vnd.microsoft.teams.card.file.info",
		contentUrl: params.contentUrl,
		name: params.filename,
		content: {
			uniqueId: params.uniqueId,
			fileType: params.fileType
		}
	};
}
/**
* Parse a fileConsent/invoke activity.
* Returns null if the activity is not a file consent invoke.
*/
function parseFileConsentInvoke(activity) {
	if (activity.name !== "fileConsent/invoke") return null;
	const value = activity.value;
	if (value?.type !== "fileUpload") return null;
	return {
		action: value.action === "accept" ? "accept" : "decline",
		uploadInfo: value.uploadInfo,
		context: value.context
	};
}
/**
* Upload a file to the consent URL provided by Teams.
* The URL is provided in the fileConsent/invoke response after user accepts.
*/
async function uploadToConsentUrl(params) {
	const res = await (params.fetchFn ?? fetch)(params.url, {
		method: "PUT",
		headers: {
			"Content-Type": params.contentType ?? "application/octet-stream",
			"Content-Range": `bytes 0-${params.buffer.length - 1}/${params.buffer.length}`
		},
		body: new Uint8Array(params.buffer)
	});
	if (!res.ok) throw new Error(`File upload to consent URL failed: ${res.status} ${res.statusText}`);
}

//#endregion
//#region extensions/msteams/src/pending-uploads.ts
/**
* In-memory storage for files awaiting user consent in the FileConsentCard flow.
*
* When sending large files (>=4MB) in personal chats, Teams requires user consent
* before upload. This module stores the file data temporarily until the user
* accepts or declines, or until the TTL expires.
*/
const pendingUploads = /* @__PURE__ */ new Map();
/** TTL for pending uploads: 5 minutes */
const PENDING_UPLOAD_TTL_MS = 300 * 1e3;
/**
* Store a file pending user consent.
* Returns the upload ID to include in the FileConsentCard context.
*/
function storePendingUpload(upload) {
	const id = crypto.randomUUID();
	const entry = {
		...upload,
		id,
		createdAt: Date.now()
	};
	pendingUploads.set(id, entry);
	setTimeout(() => {
		pendingUploads.delete(id);
	}, PENDING_UPLOAD_TTL_MS);
	return id;
}
/**
* Retrieve a pending upload by ID.
* Returns undefined if not found or expired.
*/
function getPendingUpload(id) {
	if (!id) return;
	const entry = pendingUploads.get(id);
	if (!entry) return;
	if (Date.now() - entry.createdAt > PENDING_UPLOAD_TTL_MS) {
		pendingUploads.delete(id);
		return;
	}
	return entry;
}
/**
* Remove a pending upload (after successful upload or user decline).
*/
function removePendingUpload(id) {
	if (id) pendingUploads.delete(id);
}

//#endregion
//#region extensions/msteams/src/file-consent-helpers.ts
/**
* Shared helpers for FileConsentCard flow in MSTeams.
*
* FileConsentCard is required for:
* - Personal (1:1) chats with large files (>=4MB)
* - Personal chats with non-image files (PDFs, documents, etc.)
*
* This module consolidates the logic used by both send.ts (proactive sends)
* and messenger.ts (reply path) to avoid duplication.
*/
/**
* Prepare a FileConsentCard activity for large files or non-images in personal chats.
* Returns the activity object and uploadId - caller is responsible for sending.
*/
function prepareFileConsentActivity(params) {
	const { media, conversationId, description } = params;
	const uploadId = storePendingUpload({
		buffer: media.buffer,
		filename: media.filename,
		contentType: media.contentType,
		conversationId
	});
	return {
		activity: {
			type: "message",
			attachments: [buildFileConsentCard({
				filename: media.filename,
				description: description || `File: ${media.filename}`,
				sizeInBytes: media.buffer.length,
				context: { uploadId }
			})]
		},
		uploadId
	};
}
/**
* Check if a file requires FileConsentCard flow.
* True for: personal chat AND (large file OR non-image)
*/
function requiresFileConsent(params) {
	const isPersonal = params.conversationType?.toLowerCase() === "personal";
	const isImage = params.contentType?.startsWith("image/") ?? false;
	const isLargeFile = params.bufferSize >= params.thresholdBytes;
	return isPersonal && (isLargeFile || !isImage);
}

//#endregion
//#region extensions/msteams/src/graph-chat.ts
/**
* Build a native Teams file card attachment for Bot Framework.
*
* This uses the `application/vnd.microsoft.teams.card.file.info` content type
* which is supported by Bot Framework and produces native Teams file cards
* (the same display as when a user manually shares a file).
*
* @param file - DriveItem properties from getDriveItemProperties()
* @returns Attachment object for Bot Framework sendActivity()
*/
function buildTeamsFileInfoCard(file) {
	const rawETag = file.eTag;
	const uniqueId = rawETag.replace(/^["']|["']$/g, "").replace(/[{}]/g, "").split(",")[0] ?? rawETag;
	const lastDot = file.name.lastIndexOf(".");
	const fileType = lastDot >= 0 ? file.name.slice(lastDot + 1).toLowerCase() : "";
	return {
		contentType: "application/vnd.microsoft.teams.card.file.info",
		contentUrl: file.webDavUrl,
		name: file.name,
		content: {
			uniqueId,
			fileType
		}
	};
}

//#endregion
//#region extensions/msteams/src/graph-upload.ts
const GRAPH_ROOT = "https://graph.microsoft.com/v1.0";
const GRAPH_BETA = "https://graph.microsoft.com/beta";
const GRAPH_SCOPE = "https://graph.microsoft.com";
/**
* Upload a file to the user's OneDrive root folder.
* For larger files, this uses the simple upload endpoint (up to 4MB).
* TODO: For files >4MB, implement resumable upload session.
*/
async function uploadToOneDrive(params) {
	const fetchFn = params.fetchFn ?? fetch;
	const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE);
	const res = await fetchFn(`${GRAPH_ROOT}/me/drive/root:${`/MinionShared/${encodeURIComponent(params.filename)}`}:/content`, {
		method: "PUT",
		headers: {
			Authorization: `Bearer ${token}`,
			"Content-Type": params.contentType ?? "application/octet-stream"
		},
		body: new Uint8Array(params.buffer)
	});
	if (!res.ok) {
		const body = await res.text().catch(() => "");
		throw new Error(`OneDrive upload failed: ${res.status} ${res.statusText} - ${body}`);
	}
	const data = await res.json();
	if (!data.id || !data.webUrl || !data.name) throw new Error("OneDrive upload response missing required fields");
	return {
		id: data.id,
		webUrl: data.webUrl,
		name: data.name
	};
}
/**
* Create a sharing link for a OneDrive file.
* The link allows organization members to view the file.
*/
async function createSharingLink(params) {
	const fetchFn = params.fetchFn ?? fetch;
	const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE);
	const res = await fetchFn(`${GRAPH_ROOT}/me/drive/items/${params.itemId}/createLink`, {
		method: "POST",
		headers: {
			Authorization: `Bearer ${token}`,
			"Content-Type": "application/json"
		},
		body: JSON.stringify({
			type: "view",
			scope: params.scope ?? "organization"
		})
	});
	if (!res.ok) {
		const body = await res.text().catch(() => "");
		throw new Error(`Create sharing link failed: ${res.status} ${res.statusText} - ${body}`);
	}
	const data = await res.json();
	if (!data.link?.webUrl) throw new Error("Create sharing link response missing webUrl");
	return { webUrl: data.link.webUrl };
}
/**
* Upload a file to OneDrive and create a sharing link.
* Convenience function for the common case.
*/
async function uploadAndShareOneDrive(params) {
	const uploaded = await uploadToOneDrive({
		buffer: params.buffer,
		filename: params.filename,
		contentType: params.contentType,
		tokenProvider: params.tokenProvider,
		fetchFn: params.fetchFn
	});
	const shareLink = await createSharingLink({
		itemId: uploaded.id,
		tokenProvider: params.tokenProvider,
		scope: params.scope,
		fetchFn: params.fetchFn
	});
	return {
		itemId: uploaded.id,
		webUrl: uploaded.webUrl,
		shareUrl: shareLink.webUrl,
		name: uploaded.name
	};
}
/**
* Upload a file to a SharePoint site.
* This is used for group chats and channels where /me/drive doesn't work for bots.
*
* @param params.siteId - SharePoint site ID (e.g., "contoso.sharepoint.com,guid1,guid2")
*/
async function uploadToSharePoint(params) {
	const fetchFn = params.fetchFn ?? fetch;
	const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE);
	const uploadPath = `/MinionShared/${encodeURIComponent(params.filename)}`;
	const res = await fetchFn(`${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content`, {
		method: "PUT",
		headers: {
			Authorization: `Bearer ${token}`,
			"Content-Type": params.contentType ?? "application/octet-stream"
		},
		body: new Uint8Array(params.buffer)
	});
	if (!res.ok) {
		const body = await res.text().catch(() => "");
		throw new Error(`SharePoint upload failed: ${res.status} ${res.statusText} - ${body}`);
	}
	const data = await res.json();
	if (!data.id || !data.webUrl || !data.name) throw new Error("SharePoint upload response missing required fields");
	return {
		id: data.id,
		webUrl: data.webUrl,
		name: data.name
	};
}
/**
* Get driveItem properties needed for native Teams file card attachments.
* This fetches the eTag and webDavUrl which are required for "reference" type attachments.
*
* @param params.siteId - SharePoint site ID
* @param params.itemId - The driveItem ID (returned from upload)
*/
async function getDriveItemProperties(params) {
	const fetchFn = params.fetchFn ?? fetch;
	const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE);
	const res = await fetchFn(`${GRAPH_ROOT}/sites/${params.siteId}/drive/items/${params.itemId}?$select=eTag,webDavUrl,name`, { headers: { Authorization: `Bearer ${token}` } });
	if (!res.ok) {
		const body = await res.text().catch(() => "");
		throw new Error(`Get driveItem properties failed: ${res.status} ${res.statusText} - ${body}`);
	}
	const data = await res.json();
	if (!data.eTag || !data.webDavUrl || !data.name) throw new Error("DriveItem response missing required properties (eTag, webDavUrl, or name)");
	return {
		eTag: data.eTag,
		webDavUrl: data.webDavUrl,
		name: data.name
	};
}
/**
* Get members of a Teams chat for per-user sharing.
* Used to create sharing links scoped to only the chat participants.
*/
async function getChatMembers(params) {
	const fetchFn = params.fetchFn ?? fetch;
	const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE);
	const res = await fetchFn(`${GRAPH_ROOT}/chats/${params.chatId}/members`, { headers: { Authorization: `Bearer ${token}` } });
	if (!res.ok) {
		const body = await res.text().catch(() => "");
		throw new Error(`Get chat members failed: ${res.status} ${res.statusText} - ${body}`);
	}
	return ((await res.json()).value ?? []).map((m) => ({
		aadObjectId: m.userId ?? "",
		displayName: m.displayName
	})).filter((m) => m.aadObjectId);
}
/**
* Create a sharing link for a SharePoint drive item.
* For organization scope (default), uses v1.0 API.
* For per-user scope, uses beta API with recipients.
*/
async function createSharePointSharingLink(params) {
	const fetchFn = params.fetchFn ?? fetch;
	const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE);
	const scope = params.scope ?? "organization";
	const apiRoot = scope === "users" ? GRAPH_BETA : GRAPH_ROOT;
	const body = {
		type: "view",
		scope: scope === "users" ? "users" : "organization"
	};
	if (scope === "users" && params.recipientObjectIds?.length) body.recipients = params.recipientObjectIds.map((id) => ({ objectId: id }));
	const res = await fetchFn(`${apiRoot}/sites/${params.siteId}/drive/items/${params.itemId}/createLink`, {
		method: "POST",
		headers: {
			Authorization: `Bearer ${token}`,
			"Content-Type": "application/json"
		},
		body: JSON.stringify(body)
	});
	if (!res.ok) {
		const respBody = await res.text().catch(() => "");
		throw new Error(`Create SharePoint sharing link failed: ${res.status} ${res.statusText} - ${respBody}`);
	}
	const data = await res.json();
	if (!data.link?.webUrl) throw new Error("Create SharePoint sharing link response missing webUrl");
	return { webUrl: data.link.webUrl };
}
/**
* Upload a file to SharePoint and create a sharing link.
*
* For group chats, this creates a per-user sharing link scoped to chat members.
* For channels, this creates an organization-wide sharing link.
*
* @param params.siteId - SharePoint site ID
* @param params.chatId - Optional chat ID for per-user sharing (group chats)
* @param params.usePerUserSharing - Whether to use per-user sharing (requires beta API + Chat.Read.All)
*/
async function uploadAndShareSharePoint(params) {
	const uploaded = await uploadToSharePoint({
		buffer: params.buffer,
		filename: params.filename,
		contentType: params.contentType,
		tokenProvider: params.tokenProvider,
		siteId: params.siteId,
		fetchFn: params.fetchFn
	});
	let scope = "organization";
	let recipientObjectIds;
	if (params.usePerUserSharing && params.chatId) try {
		const members = await getChatMembers({
			chatId: params.chatId,
			tokenProvider: params.tokenProvider,
			fetchFn: params.fetchFn
		});
		if (members.length > 0) {
			scope = "users";
			recipientObjectIds = members.map((m) => m.aadObjectId);
		}
	} catch {}
	const shareLink = await createSharePointSharingLink({
		siteId: params.siteId,
		itemId: uploaded.id,
		tokenProvider: params.tokenProvider,
		scope,
		recipientObjectIds,
		fetchFn: params.fetchFn
	});
	return {
		itemId: uploaded.id,
		webUrl: uploaded.webUrl,
		shareUrl: shareLink.webUrl,
		name: uploaded.name
	};
}

//#endregion
//#region extensions/msteams/src/media-helpers.ts
/**
* MIME type detection and filename extraction for MSTeams media attachments.
*/
/**
* Detect MIME type from URL extension or data URL.
* Uses shared MIME detection for consistency with core handling.
*/
async function getMimeType(url) {
	if (url.startsWith("data:")) {
		const match = url.match(/^data:([^;,]+)/);
		if (match?.[1]) return match[1];
	}
	return await detectMime({ filePath: url }) ?? "application/octet-stream";
}
/**
* Extract filename from URL or local path.
* For local paths, extracts original filename if stored with embedded name pattern.
* Falls back to deriving the extension from MIME type when no extension present.
*/
async function extractFilename(url) {
	if (url.startsWith("data:")) {
		const mime = await getMimeType(url);
		const ext = extensionForMime(mime) ?? ".bin";
		return `${mime.startsWith("image/") ? "image" : "file"}${ext}`;
	}
	try {
		const pathname = new URL(url).pathname;
		const basename = path.basename(pathname);
		const existingExt = getFileExtension(pathname);
		if (basename && existingExt) return basename;
		const mime = await getMimeType(url);
		const ext = extensionForMime(mime) ?? ".bin";
		const prefix = mime.startsWith("image/") ? "image" : "file";
		return basename ? `${basename}${ext}` : `${prefix}${ext}`;
	} catch {
		return extractOriginalFilename(url);
	}
}
/**
* Check if a URL refers to a local file path.
*/
function isLocalPath(url) {
	if (url.startsWith("file://") || url.startsWith("/") || url.startsWith("~")) return true;
	if (/^[a-zA-Z]:[\\/]/.test(url)) return true;
	if (url.startsWith("\\\\")) return true;
	return false;
}
/**
* Extract the message ID from a Bot Framework response.
*/
function extractMessageId(response) {
	if (!response || typeof response !== "object") return null;
	if (!("id" in response)) return null;
	const { id } = response;
	if (typeof id !== "string" || !id) return null;
	return id;
}

//#endregion
//#region extensions/msteams/src/mentions.ts
/**
* Check whether an ID looks like a valid Teams user/bot identifier.
* Accepts:
* - Bot Framework IDs: "28:xxx..." / "29:xxx..." / "8:orgid:..."
* - AAD object IDs (UUIDs): "d5318c29-33ac-4e6b-bd42-57b8b793908f"
*
* Keep this permissive enough for real Teams IDs while still rejecting
* documentation placeholders like `@[表示名](ユーザーID)`.
*/
const TEAMS_BOT_ID_PATTERN = /^\d+:[a-z0-9._=-]+(?::[a-z0-9._=-]+)*$/i;
const AAD_OBJECT_ID_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
function isValidTeamsId(id) {
	return TEAMS_BOT_ID_PATTERN.test(id) || AAD_OBJECT_ID_PATTERN.test(id);
}
/**
* Parse mentions from text in the format @[Name](id).
* Example: "Hello @[John Doe](28:xxx-yyy-zzz)!"
*
* Only matches where the id looks like a real Teams user/bot ID are treated
* as mentions. This avoids false positives from documentation or code samples
* embedded in the message (e.g. `@[表示名](ユーザーID)` in backticks).
*
* Returns both the formatted text with <at> tags and the entities array.
*/
function parseMentions(text) {
	const mentionPattern = /@\[([^\]]+)\]\(([^)]+)\)/g;
	const entities = [];
	return {
		text: text.replace(mentionPattern, (match, name, id) => {
			const trimmedId = id.trim();
			if (!isValidTeamsId(trimmedId)) return match;
			const trimmedName = name.trim();
			const mentionTag = `<at>${trimmedName}</at>`;
			entities.push({
				type: "mention",
				text: mentionTag,
				mentioned: {
					id: trimmedId,
					name: trimmedName
				}
			});
			return mentionTag;
		}),
		entities
	};
}

//#endregion
//#region extensions/msteams/src/messenger.ts
/**
* MSTeams-specific media size limit (100MB).
* Higher than the default because OneDrive upload handles large files well.
*/
const MSTEAMS_MAX_MEDIA_BYTES$1 = 100 * 1024 * 1024;
/**
* Threshold for large files that require FileConsentCard flow in personal chats.
* Files >= 4MB use consent flow; smaller images can use inline base64.
*/
const FILE_CONSENT_THRESHOLD_BYTES$1 = 4 * 1024 * 1024;
function normalizeConversationId(rawId) {
	return rawId.split(";")[0] ?? rawId;
}
function buildConversationReference(ref) {
	const conversationId = ref.conversation?.id?.trim();
	if (!conversationId) throw new Error("Invalid stored reference: missing conversation.id");
	const agent = ref.agent ?? ref.bot ?? void 0;
	if (agent == null || !agent.id) throw new Error("Invalid stored reference: missing agent.id");
	const user = ref.user;
	if (!user?.id) throw new Error("Invalid stored reference: missing user.id");
	return {
		activityId: ref.activityId,
		user,
		agent,
		conversation: {
			id: normalizeConversationId(conversationId),
			conversationType: ref.conversation?.conversationType,
			tenantId: ref.conversation?.tenantId
		},
		channelId: ref.channelId ?? "msteams",
		serviceUrl: ref.serviceUrl,
		locale: ref.locale
	};
}
function pushTextMessages(out, text, opts) {
	if (!text) return;
	if (opts.chunkText) {
		for (const chunk of getMSTeamsRuntime().channel.text.chunkMarkdownTextWithMode(text, opts.chunkLimit, opts.chunkMode)) {
			const trimmed = chunk.trim();
			if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) continue;
			out.push({ text: trimmed });
		}
		return;
	}
	const trimmed = text.trim();
	if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) return;
	out.push({ text: trimmed });
}
function clampMs(value, maxMs) {
	if (!Number.isFinite(value) || value < 0) return 0;
	return Math.min(value, maxMs);
}
function resolveRetryOptions(retry) {
	if (!retry) return {
		enabled: false,
		maxAttempts: 1,
		baseDelayMs: 0,
		maxDelayMs: 0
	};
	return {
		enabled: true,
		maxAttempts: Math.max(1, retry?.maxAttempts ?? 3),
		baseDelayMs: Math.max(0, retry?.baseDelayMs ?? 250),
		maxDelayMs: Math.max(0, retry?.maxDelayMs ?? 1e4)
	};
}
function computeRetryDelayMs(attempt, classification, opts) {
	if (classification.retryAfterMs != null) return clampMs(classification.retryAfterMs, opts.maxDelayMs);
	return clampMs(opts.baseDelayMs * 2 ** Math.max(0, attempt - 1), opts.maxDelayMs);
}
function shouldRetry(classification) {
	return classification.kind === "throttled" || classification.kind === "transient";
}
function renderReplyPayloadsToMessages(replies, options) {
	const out = [];
	const chunkLimit = Math.min(options.textChunkLimit, 4e3);
	const chunkText = options.chunkText !== false;
	const chunkMode = options.chunkMode ?? "length";
	const mediaMode = options.mediaMode ?? "split";
	const tableMode = options.tableMode ?? getMSTeamsRuntime().channel.text.resolveMarkdownTableMode({
		cfg: getMSTeamsRuntime().config.loadConfig(),
		channel: "msteams"
	});
	for (const payload of replies) {
		const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
		const text = getMSTeamsRuntime().channel.text.convertMarkdownTables(payload.text ?? "", tableMode);
		if (!text && mediaList.length === 0) continue;
		if (mediaList.length === 0) {
			pushTextMessages(out, text, {
				chunkText,
				chunkLimit,
				chunkMode
			});
			continue;
		}
		if (mediaMode === "inline") {
			const firstMedia = mediaList[0];
			if (firstMedia) {
				out.push({
					text: text || void 0,
					mediaUrl: firstMedia
				});
				for (let i = 1; i < mediaList.length; i++) if (mediaList[i]) out.push({ mediaUrl: mediaList[i] });
			} else pushTextMessages(out, text, {
				chunkText,
				chunkLimit,
				chunkMode
			});
			continue;
		}
		pushTextMessages(out, text, {
			chunkText,
			chunkLimit,
			chunkMode
		});
		for (const mediaUrl of mediaList) {
			if (!mediaUrl) continue;
			out.push({ mediaUrl });
		}
	}
	return out;
}
async function buildActivity(msg, conversationRef, tokenProvider, sharePointSiteId, mediaMaxBytes) {
	const activity = { type: "message" };
	if (msg.text) {
		const { text: formattedText, entities } = parseMentions(msg.text);
		activity.text = formattedText;
		if (entities.length > 0) activity.entities = entities;
	}
	if (msg.mediaUrl) {
		let contentUrl = msg.mediaUrl;
		let contentType = await getMimeType(msg.mediaUrl);
		let fileName = await extractFilename(msg.mediaUrl);
		if (isLocalPath(msg.mediaUrl)) {
			const maxBytes = mediaMaxBytes ?? MSTEAMS_MAX_MEDIA_BYTES$1;
			const media = await loadWebMedia(msg.mediaUrl, maxBytes);
			contentType = media.contentType ?? contentType;
			fileName = media.fileName ?? fileName;
			const conversationType = conversationRef.conversation?.conversationType?.toLowerCase();
			const isPersonal = conversationType === "personal";
			const isImage = contentType?.startsWith("image/") ?? false;
			if (requiresFileConsent({
				conversationType,
				contentType,
				bufferSize: media.buffer.length,
				thresholdBytes: FILE_CONSENT_THRESHOLD_BYTES$1
			})) {
				const conversationId = conversationRef.conversation?.id ?? "unknown";
				const { activity: consentActivity } = prepareFileConsentActivity({
					media: {
						buffer: media.buffer,
						filename: fileName,
						contentType
					},
					conversationId,
					description: msg.text || void 0
				});
				return consentActivity;
			}
			if (!isPersonal && !isImage && tokenProvider && sharePointSiteId) {
				const chatId = conversationRef.conversation?.id;
				activity.attachments = [buildTeamsFileInfoCard(await getDriveItemProperties({
					siteId: sharePointSiteId,
					itemId: (await uploadAndShareSharePoint({
						buffer: media.buffer,
						filename: fileName,
						contentType,
						tokenProvider,
						siteId: sharePointSiteId,
						chatId: chatId ?? void 0,
						usePerUserSharing: conversationType === "groupchat"
					})).itemId,
					tokenProvider
				}))];
				return activity;
			}
			if (!isPersonal && !isImage && tokenProvider) {
				const uploaded = await uploadAndShareOneDrive({
					buffer: media.buffer,
					filename: fileName,
					contentType,
					tokenProvider
				});
				const fileLink = `📎 [${uploaded.name}](${uploaded.shareUrl})`;
				const existingText = typeof activity.text === "string" ? activity.text : void 0;
				activity.text = existingText ? `${existingText}\n\n${fileLink}` : fileLink;
				return activity;
			}
			const base64 = media.buffer.toString("base64");
			contentUrl = `data:${media.contentType};base64,${base64}`;
		}
		activity.attachments = [{
			name: fileName,
			contentType,
			contentUrl
		}];
	}
	return activity;
}
async function sendMSTeamsMessages(params) {
	const messages = params.messages.filter((m) => m.text && m.text.trim().length > 0 || m.mediaUrl);
	if (messages.length === 0) return [];
	const retryOptions = resolveRetryOptions(params.retry);
	const sendWithRetry = async (sendOnce, meta) => {
		if (!retryOptions.enabled) return await sendOnce();
		let attempt = 1;
		while (true) try {
			return await sendOnce();
		} catch (err) {
			const classification = classifyMSTeamsSendError(err);
			if (!(attempt < retryOptions.maxAttempts && shouldRetry(classification))) throw err;
			const delayMs = computeRetryDelayMs(attempt, classification, retryOptions);
			const nextAttempt = attempt + 1;
			params.onRetry?.({
				messageIndex: meta.messageIndex,
				messageCount: meta.messageCount,
				nextAttempt,
				maxAttempts: retryOptions.maxAttempts,
				delayMs,
				classification
			});
			await sleep(delayMs);
			attempt = nextAttempt;
		}
	};
	if (params.replyStyle === "thread") {
		const ctx = params.context;
		if (!ctx) throw new Error("Missing context for replyStyle=thread");
		const messageIds = [];
		for (const [idx, message] of messages.entries()) {
			const response = await sendWithRetry(async () => await ctx.sendActivity(await buildActivity(message, params.conversationRef, params.tokenProvider, params.sharePointSiteId, params.mediaMaxBytes)), {
				messageIndex: idx,
				messageCount: messages.length
			});
			messageIds.push(extractMessageId(response) ?? "unknown");
		}
		return messageIds;
	}
	const proactiveRef = {
		...buildConversationReference(params.conversationRef),
		activityId: void 0
	};
	const messageIds = [];
	await params.adapter.continueConversation(params.appId, proactiveRef, async (ctx) => {
		for (const [idx, message] of messages.entries()) {
			const response = await sendWithRetry(async () => await ctx.sendActivity(await buildActivity(message, params.conversationRef, params.tokenProvider, params.sharePointSiteId, params.mediaMaxBytes)), {
				messageIndex: idx,
				messageCount: messages.length
			});
			messageIds.push(extractMessageId(response) ?? "unknown");
		}
	});
	return messageIds;
}

//#endregion
//#region extensions/msteams/src/send-context.ts
/**
* Parse the target value into a conversation reference lookup key.
* Supported formats:
* - conversation:19:abc@thread.tacv2 → lookup by conversation ID
* - user:aad-object-id → lookup by user AAD object ID
* - 19:abc@thread.tacv2 → direct conversation ID
*/
function parseRecipient(to) {
	const trimmed = to.trim();
	const finalize = (type, id) => {
		const normalized = id.trim();
		if (!normalized) throw new Error(`Invalid target value: missing ${type} id`);
		return {
			type,
			id: normalized
		};
	};
	if (trimmed.startsWith("conversation:")) return finalize("conversation", trimmed.slice(13));
	if (trimmed.startsWith("user:")) return finalize("user", trimmed.slice(5));
	if (trimmed.startsWith("19:") || trimmed.includes("@thread")) return finalize("conversation", trimmed);
	return finalize("user", trimmed);
}
/**
* Find a stored conversation reference for the given recipient.
*/
async function findConversationReference(recipient) {
	if (recipient.type === "conversation") {
		const ref = await recipient.store.get(recipient.id);
		if (ref) return {
			conversationId: recipient.id,
			ref
		};
		return null;
	}
	const found = await recipient.store.findByUserId(recipient.id);
	if (!found) return null;
	return {
		conversationId: found.conversationId,
		ref: found.reference
	};
}
async function resolveMSTeamsSendContext(params) {
	const msteamsCfg = params.cfg.channels?.msteams;
	if (!msteamsCfg?.enabled) throw new Error("msteams provider is not enabled");
	const creds = resolveMSTeamsCredentials(msteamsCfg);
	if (!creds) throw new Error("msteams credentials not configured");
	const store = createMSTeamsConversationStoreFs();
	const recipient = parseRecipient(params.to);
	const found = await findConversationReference({
		...recipient,
		store
	});
	if (!found) throw new Error(`No conversation reference found for ${recipient.type}:${recipient.id}. The bot must receive a message from this conversation before it can send proactively.`);
	const { conversationId, ref } = found;
	const log = getMSTeamsRuntime().logging.getChildLogger({ name: "msteams:send" });
	const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds);
	const adapter = createMSTeamsAdapter(authConfig, sdk);
	const tokenProvider = new sdk.MsalTokenProvider(authConfig);
	const storedConversationType = ref.conversation?.conversationType?.toLowerCase() ?? "";
	let conversationType;
	if (storedConversationType === "personal") conversationType = "personal";
	else if (storedConversationType === "channel") conversationType = "channel";
	else conversationType = "groupChat";
	const sharePointSiteId = msteamsCfg.sharePointSiteId;
	const mediaMaxBytes = resolveChannelMediaMaxBytes({
		cfg: params.cfg,
		resolveChannelLimitMb: ({ cfg }) => cfg.channels?.msteams?.mediaMaxMb
	});
	return {
		appId: creds.appId,
		conversationId,
		ref,
		adapter,
		log,
		conversationType,
		tokenProvider,
		sharePointSiteId,
		mediaMaxBytes
	};
}

//#endregion
//#region extensions/msteams/src/send.ts
/** Threshold for large files that require FileConsentCard flow in personal chats */
const FILE_CONSENT_THRESHOLD_BYTES = 4 * 1024 * 1024;
/**
* MSTeams-specific media size limit (100MB).
* Higher than the default because OneDrive upload handles large files well.
*/
const MSTEAMS_MAX_MEDIA_BYTES = 100 * 1024 * 1024;
/**
* Send a message to a Teams conversation or user.
*
* Uses the stored ConversationReference from previous interactions.
* The bot must have received at least one message from the conversation
* before proactive messaging works.
*
* File handling by conversation type:
* - Personal (1:1) chats: small images (<4MB) use base64, large files and non-images use FileConsentCard
* - Group chats / channels: files are uploaded to OneDrive and shared via link
*/
async function sendMessageMSTeams(params) {
	const { cfg, to, mediaUrl } = params;
	const { enabled: disclosureEnabled, text: disclosureText } = resolveDisclosureConfig(cfg.gateway?.aiDisclosure);
	const text = maybeInjectDisclosure(`msteams:default:${to}`, params.text, disclosureText, disclosureEnabled);
	const tableMode = getMSTeamsRuntime().channel.text.resolveMarkdownTableMode({
		cfg,
		channel: "msteams"
	});
	const messageText = getMSTeamsRuntime().channel.text.convertMarkdownTables(text ?? "", tableMode);
	const ctx = await resolveMSTeamsSendContext({
		cfg,
		to
	});
	const { adapter, appId, conversationId, ref, log, conversationType, tokenProvider, sharePointSiteId } = ctx;
	log.debug?.("sending proactive message", {
		conversationId,
		conversationType,
		textLength: messageText.length,
		hasMedia: Boolean(mediaUrl)
	});
	if (mediaUrl) {
		const media = await loadWebMedia(mediaUrl, resolveChannelMediaMaxBytes({
			cfg,
			resolveChannelLimitMb: ({ cfg }) => cfg.channels?.msteams?.mediaMaxMb
		}) ?? MSTEAMS_MAX_MEDIA_BYTES);
		const isLargeFile = media.buffer.length >= FILE_CONSENT_THRESHOLD_BYTES;
		const isImage = media.contentType?.startsWith("image/") ?? false;
		const fallbackFileName = await extractFilename(mediaUrl);
		const fileName = media.fileName ?? fallbackFileName;
		log.debug?.("processing media", {
			fileName,
			contentType: media.contentType,
			size: media.buffer.length,
			isLargeFile,
			isImage,
			conversationType
		});
		if (requiresFileConsent({
			conversationType,
			contentType: media.contentType,
			bufferSize: media.buffer.length,
			thresholdBytes: FILE_CONSENT_THRESHOLD_BYTES
		})) {
			const { activity, uploadId } = prepareFileConsentActivity({
				media: {
					buffer: media.buffer,
					filename: fileName,
					contentType: media.contentType
				},
				conversationId,
				description: messageText || void 0
			});
			log.debug?.("sending file consent card", {
				uploadId,
				fileName,
				size: media.buffer.length
			});
			const proactiveRef = {
				...buildConversationReference(ref),
				activityId: void 0
			};
			let messageId = "unknown";
			try {
				await adapter.continueConversation(appId, proactiveRef, async (turnCtx) => {
					messageId = extractMessageId(await turnCtx.sendActivity(activity)) ?? "unknown";
				});
			} catch (err) {
				const classification = classifyMSTeamsSendError(err);
				const hint = formatMSTeamsSendErrorHint(classification);
				const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
				throw new Error(`msteams consent card send failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`, { cause: err });
			}
			log.info("sent file consent card", {
				conversationId,
				messageId,
				uploadId
			});
			return {
				messageId,
				conversationId,
				pendingUploadId: uploadId
			};
		}
		if (conversationType === "personal") {
			const base64 = media.buffer.toString("base64");
			return sendTextWithMedia(ctx, messageText, `data:${media.contentType};base64,${base64}`);
		}
		if (isImage && !sharePointSiteId) {
			const base64 = media.buffer.toString("base64");
			return sendTextWithMedia(ctx, messageText, `data:${media.contentType};base64,${base64}`);
		}
		try {
			if (sharePointSiteId) {
				log.debug?.("uploading to SharePoint for native file card", {
					fileName,
					conversationType,
					siteId: sharePointSiteId
				});
				const uploaded = await uploadAndShareSharePoint({
					buffer: media.buffer,
					filename: fileName,
					contentType: media.contentType,
					tokenProvider,
					siteId: sharePointSiteId,
					chatId: conversationId,
					usePerUserSharing: conversationType === "groupChat"
				});
				log.debug?.("SharePoint upload complete", {
					itemId: uploaded.itemId,
					shareUrl: uploaded.shareUrl
				});
				const driveItem = await getDriveItemProperties({
					siteId: sharePointSiteId,
					itemId: uploaded.itemId,
					tokenProvider
				});
				log.debug?.("driveItem properties retrieved", {
					eTag: driveItem.eTag,
					webDavUrl: driveItem.webDavUrl
				});
				const fileCardAttachment = buildTeamsFileInfoCard(driveItem);
				const activity = {
					type: "message",
					text: messageText || void 0,
					attachments: [fileCardAttachment]
				};
				const proactiveRef = {
					...buildConversationReference(ref),
					activityId: void 0
				};
				let messageId = "unknown";
				await adapter.continueConversation(appId, proactiveRef, async (turnCtx) => {
					messageId = extractMessageId(await turnCtx.sendActivity(activity)) ?? "unknown";
				});
				log.info("sent native file card", {
					conversationId,
					messageId,
					fileName: driveItem.name
				});
				return {
					messageId,
					conversationId
				};
			}
			log.debug?.("uploading to OneDrive (no SharePoint site configured)", {
				fileName,
				conversationType
			});
			const uploaded = await uploadAndShareOneDrive({
				buffer: media.buffer,
				filename: fileName,
				contentType: media.contentType,
				tokenProvider
			});
			log.debug?.("OneDrive upload complete", {
				itemId: uploaded.itemId,
				shareUrl: uploaded.shareUrl
			});
			const fileLink = `📎 [${uploaded.name}](${uploaded.shareUrl})`;
			const activity = {
				type: "message",
				text: messageText ? `${messageText}\n\n${fileLink}` : fileLink
			};
			const proactiveRef = {
				...buildConversationReference(ref),
				activityId: void 0
			};
			let messageId = "unknown";
			await adapter.continueConversation(appId, proactiveRef, async (turnCtx) => {
				messageId = extractMessageId(await turnCtx.sendActivity(activity)) ?? "unknown";
			});
			log.info("sent message with OneDrive file link", {
				conversationId,
				messageId,
				shareUrl: uploaded.shareUrl
			});
			return {
				messageId,
				conversationId
			};
		} catch (err) {
			const classification = classifyMSTeamsSendError(err);
			const hint = formatMSTeamsSendErrorHint(classification);
			const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
			throw new Error(`msteams file send failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`, { cause: err });
		}
	}
	return sendTextWithMedia(ctx, messageText, void 0);
}
/**
* Send a text message with optional base64 media URL.
*/
async function sendTextWithMedia(ctx, text, mediaUrl) {
	const { adapter, appId, conversationId, ref, log, tokenProvider, sharePointSiteId, mediaMaxBytes } = ctx;
	let messageIds;
	try {
		messageIds = await sendMSTeamsMessages({
			replyStyle: "top-level",
			adapter,
			appId,
			conversationRef: ref,
			messages: [{
				text: text || void 0,
				mediaUrl
			}],
			retry: {},
			onRetry: (event) => {
				log.debug?.("retrying send", {
					conversationId,
					...event
				});
			},
			tokenProvider,
			sharePointSiteId,
			mediaMaxBytes
		});
	} catch (err) {
		const classification = classifyMSTeamsSendError(err);
		const hint = formatMSTeamsSendErrorHint(classification);
		const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
		throw new Error(`msteams send failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`, { cause: err });
	}
	const messageId = messageIds[0] ?? "unknown";
	log.info("sent proactive message", {
		conversationId,
		messageId
	});
	return {
		messageId,
		conversationId
	};
}
async function sendProactiveActivity({ adapter, appId, ref, activity, errorPrefix }) {
	const proactiveRef = {
		...buildConversationReference(ref),
		activityId: void 0
	};
	let messageId = "unknown";
	try {
		await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
			messageId = extractMessageId(await ctx.sendActivity(activity)) ?? "unknown";
		});
		return messageId;
	} catch (err) {
		const classification = classifyMSTeamsSendError(err);
		const hint = formatMSTeamsSendErrorHint(classification);
		const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
		throw new Error(`${errorPrefix} failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`, { cause: err });
	}
}
/**
* Send a poll (Adaptive Card) to a Teams conversation or user.
*/
async function sendPollMSTeams(params) {
	const { cfg, to, question, options, maxSelections } = params;
	const { adapter, appId, conversationId, ref, log } = await resolveMSTeamsSendContext({
		cfg,
		to
	});
	const pollCard = buildMSTeamsPollCard({
		question,
		options,
		maxSelections
	});
	log.debug?.("sending poll", {
		conversationId,
		pollId: pollCard.pollId,
		optionCount: pollCard.options.length
	});
	const messageId = await sendProactiveActivity({
		adapter,
		appId,
		ref,
		activity: {
			type: "message",
			attachments: [{
				contentType: "application/vnd.microsoft.card.adaptive",
				content: pollCard.card
			}]
		},
		errorPrefix: "msteams poll send"
	});
	log.info("sent poll", {
		conversationId,
		pollId: pollCard.pollId,
		messageId
	});
	return {
		pollId: pollCard.pollId,
		messageId,
		conversationId
	};
}
/**
* Send an arbitrary Adaptive Card to a Teams conversation or user.
*/
async function sendAdaptiveCardMSTeams(params) {
	const { cfg, to, card } = params;
	const { adapter, appId, conversationId, ref, log } = await resolveMSTeamsSendContext({
		cfg,
		to
	});
	log.debug?.("sending adaptive card", {
		conversationId,
		cardType: card.type,
		cardVersion: card.version
	});
	const messageId = await sendProactiveActivity({
		adapter,
		appId,
		ref,
		activity: {
			type: "message",
			attachments: [{
				contentType: "application/vnd.microsoft.card.adaptive",
				content: card
			}]
		},
		errorPrefix: "msteams card send"
	});
	log.info("sent adaptive card", {
		conversationId,
		messageId
	});
	return {
		messageId,
		conversationId
	};
}

//#endregion
//#region extensions/msteams/src/policy.ts
function resolveMSTeamsRouteConfig(params) {
	const teamId = params.teamId?.trim();
	const teamName = params.teamName?.trim();
	const conversationId = params.conversationId?.trim();
	const channelName = params.channelName?.trim();
	const teams = params.cfg?.teams ?? {};
	const allowlistConfigured = Object.keys(teams).length > 0;
	const teamMatch = resolveChannelEntryMatchWithFallback({
		entries: teams,
		keys: buildChannelKeyCandidates(teamId, teamName, teamName ? normalizeChannelSlug(teamName) : void 0),
		wildcardKey: "*",
		normalizeKey: normalizeChannelSlug
	});
	const teamConfig = teamMatch.entry;
	const channels = teamConfig?.channels ?? {};
	const channelAllowlistConfigured = Object.keys(channels).length > 0;
	const channelMatch = resolveChannelEntryMatchWithFallback({
		entries: channels,
		keys: buildChannelKeyCandidates(conversationId, channelName, channelName ? normalizeChannelSlug(channelName) : void 0),
		wildcardKey: "*",
		normalizeKey: normalizeChannelSlug
	});
	const channelConfig = channelMatch.entry;
	return {
		teamConfig,
		channelConfig,
		allowlistConfigured,
		allowed: resolveNestedAllowlistDecision({
			outerConfigured: allowlistConfigured,
			outerMatched: Boolean(teamConfig),
			innerConfigured: channelAllowlistConfigured,
			innerMatched: Boolean(channelConfig)
		}),
		teamKey: teamMatch.matchKey ?? teamMatch.key,
		channelKey: channelMatch.matchKey ?? channelMatch.key,
		channelMatchKey: channelMatch.matchKey,
		channelMatchSource: channelMatch.matchSource === "direct" || channelMatch.matchSource === "wildcard" ? channelMatch.matchSource : void 0
	};
}
function resolveMSTeamsGroupToolPolicy(params) {
	const cfg = params.cfg.channels?.msteams;
	if (!cfg) return;
	const groupId = params.groupId?.trim();
	const groupChannel = params.groupChannel?.trim();
	const groupSpace = params.groupSpace?.trim();
	const resolved = resolveMSTeamsRouteConfig({
		cfg,
		teamId: groupSpace,
		teamName: groupSpace,
		conversationId: groupId,
		channelName: groupChannel
	});
	if (resolved.channelConfig) {
		const senderPolicy = resolveToolsBySender({
			toolsBySender: resolved.channelConfig.toolsBySender,
			senderId: params.senderId,
			senderName: params.senderName,
			senderUsername: params.senderUsername,
			senderE164: params.senderE164
		});
		if (senderPolicy) return senderPolicy;
		if (resolved.channelConfig.tools) return resolved.channelConfig.tools;
		const teamSenderPolicy = resolveToolsBySender({
			toolsBySender: resolved.teamConfig?.toolsBySender,
			senderId: params.senderId,
			senderName: params.senderName,
			senderUsername: params.senderUsername,
			senderE164: params.senderE164
		});
		if (teamSenderPolicy) return teamSenderPolicy;
		return resolved.teamConfig?.tools;
	}
	if (resolved.teamConfig) {
		const teamSenderPolicy = resolveToolsBySender({
			toolsBySender: resolved.teamConfig.toolsBySender,
			senderId: params.senderId,
			senderName: params.senderName,
			senderUsername: params.senderUsername,
			senderE164: params.senderE164
		});
		if (teamSenderPolicy) return teamSenderPolicy;
		if (resolved.teamConfig.tools) return resolved.teamConfig.tools;
	}
	if (!groupId) return;
	const channelCandidates = buildChannelKeyCandidates(groupId, groupChannel, groupChannel ? normalizeChannelSlug(groupChannel) : void 0);
	for (const teamConfig of Object.values(cfg.teams ?? {})) {
		const match = resolveChannelEntryMatchWithFallback({
			entries: teamConfig?.channels ?? {},
			keys: channelCandidates,
			wildcardKey: "*",
			normalizeKey: normalizeChannelSlug
		});
		if (match.entry) {
			const senderPolicy = resolveToolsBySender({
				toolsBySender: match.entry.toolsBySender,
				senderId: params.senderId,
				senderName: params.senderName,
				senderUsername: params.senderUsername,
				senderE164: params.senderE164
			});
			if (senderPolicy) return senderPolicy;
			if (match.entry.tools) return match.entry.tools;
			const teamSenderPolicy = resolveToolsBySender({
				toolsBySender: teamConfig?.toolsBySender,
				senderId: params.senderId,
				senderName: params.senderName,
				senderUsername: params.senderUsername,
				senderE164: params.senderE164
			});
			if (teamSenderPolicy) return teamSenderPolicy;
			return teamConfig?.tools;
		}
	}
}
function resolveMSTeamsAllowlistMatch(params) {
	return resolveAllowlistMatchSimple(params);
}
function resolveMSTeamsReplyPolicy(params) {
	if (params.isDirectMessage) return {
		requireMention: false,
		replyStyle: "thread"
	};
	const requireMention = params.channelConfig?.requireMention ?? params.teamConfig?.requireMention ?? params.globalConfig?.requireMention ?? true;
	return {
		requireMention,
		replyStyle: params.channelConfig?.replyStyle ?? params.teamConfig?.replyStyle ?? params.globalConfig?.replyStyle ?? (requireMention ? "thread" : "top-level")
	};
}
function isMSTeamsGroupAllowed(params) {
	const { groupPolicy } = params;
	if (groupPolicy === "disabled") return false;
	if (groupPolicy === "open") return true;
	return resolveMSTeamsAllowlistMatch(params).allowed;
}

//#endregion
//#region extensions/msteams/src/probe.ts
function readAccessToken(value) {
	if (typeof value === "string") return value;
	if (value && typeof value === "object") {
		const token = value.accessToken ?? value.token;
		return typeof token === "string" ? token : null;
	}
	return null;
}
function decodeJwtPayload(token) {
	const parts = token.split(".");
	if (parts.length < 2) return null;
	const payload = parts[1] ?? "";
	const normalized = payload.padEnd(payload.length + (4 - payload.length % 4) % 4, "=").replace(/-/g, "+").replace(/_/g, "/");
	try {
		const decoded = Buffer.from(normalized, "base64").toString("utf8");
		const parsed = JSON.parse(decoded);
		return parsed && typeof parsed === "object" ? parsed : null;
	} catch {
		return null;
	}
}
function readStringArray(value) {
	if (!Array.isArray(value)) return;
	const out = value.map((entry) => String(entry).trim()).filter(Boolean);
	return out.length > 0 ? out : void 0;
}
function readScopes(value) {
	if (typeof value !== "string") return;
	const out = value.split(/\s+/).map((entry) => entry.trim()).filter(Boolean);
	return out.length > 0 ? out : void 0;
}
async function probeMSTeams(cfg) {
	const creds = resolveMSTeamsCredentials(cfg);
	if (!creds) return {
		ok: false,
		error: "missing credentials (appId, appPassword, tenantId)"
	};
	try {
		const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds);
		const tokenProvider = new sdk.MsalTokenProvider(authConfig);
		await tokenProvider.getAccessToken("https://api.botframework.com");
		let graph;
		try {
			const accessToken = readAccessToken(await tokenProvider.getAccessToken("https://graph.microsoft.com"));
			const payload = accessToken ? decodeJwtPayload(accessToken) : null;
			graph = {
				ok: true,
				roles: readStringArray(payload?.roles),
				scopes: readScopes(payload?.scp)
			};
		} catch (err) {
			graph = {
				ok: false,
				error: formatUnknownError(err)
			};
		}
		return {
			ok: true,
			appId: creds.appId,
			...graph ? { graph } : {}
		};
	} catch (err) {
		return {
			ok: false,
			appId: creds.appId,
			error: formatUnknownError(err)
		};
	}
}

//#endregion
export { resolveAuthAllowedHosts as $, resolveMSTeamsChannelAllowlist as A, loadMSTeamsSdkWithAuth as B, getMSTeamsRuntime as C, parseMSTeamsConversationId as D, normalizeMSTeamsUserInput as E, listTeamsByName as F, extractInlineImageCandidates as G, GRAPH_ROOT$1 as H, normalizeQuery as I, isLikelyImageAttachment as J, inferPlaceholder as K, resolveGraphToken as L, escapeOData as M, fetchGraphJson as N, parseMSTeamsTeamChannelInput as O, listChannelsForTeam as P, resolveAllowedHosts as Q, resolveMSTeamsCredentials as R, extractMSTeamsPollVote as S, normalizeMSTeamsMessagingTarget as T, IMG_SRC_RE as U, ATTACHMENT_TAG_RE as V, extractHtmlFromAttachment as W, isUrlAllowed as X, isRecord$2 as Y, normalizeContentType as Z, classifyMSTeamsSendError as _, resolveMSTeamsReplyPolicy as a, createMSTeamsConversationStoreFs as b, sendMessageMSTeams as c, sendMSTeamsMessages as d, safeHostForUrl as et, getPendingUpload as f, uploadToConsentUrl as g, parseFileConsentInvoke as h, resolveMSTeamsGroupToolPolicy as i, resolveMSTeamsUserAllowlist as j, parseMSTeamsTeamEntry as k, sendPollMSTeams as l, buildFileInfoCard as m, isMSTeamsGroupAllowed as n, resolveMSTeamsRouteConfig as o, removePendingUpload as p, isDownloadableAttachment as q, resolveMSTeamsAllowlistMatch as r, sendAdaptiveCardMSTeams as s, probeMSTeams as t, renderReplyPayloadsToMessages as u, formatMSTeamsSendErrorHint as v, setMSTeamsRuntime as w, createMSTeamsPollStoreFs as x, formatUnknownError as y, createMSTeamsAdapter as z };