import * as Lark from "@larksuiteoapi/node-sdk";
import { DEFAULT_GROUP_HISTORY_LIMIT, buildAgentMediaPayload, buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, createReplyPrefixContext, createTypingCallbacks, installRequestBodyLimitGuard, logTypingFailure, recordPendingHistoryEntryIfEnabled, resolveAllowlistMatchSimple } from "openclaw/plugin-sdk";
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId } from "minion/plugin-sdk/account-id";
import fs from "fs";
import os from "os";
import path from "path";
import * as http from "http";
import fs$1 from "node:fs";
import os$1 from "node:os";
import path$1 from "node:path";

//#region \0rolldown/runtime.js
var __defProp = Object.defineProperty;
var __exportAll = (all, no_symbols) => {
	let target = {};
	for (var name in all) {
		__defProp(target, name, {
			get: all[name],
			enumerable: true
		});
	}
	if (!no_symbols) {
		__defProp(target, Symbol.toStringTag, { value: "Module" });
	}
	return target;
};

//#endregion
//#region extensions/feishu/src/client.ts
const clientCache = /* @__PURE__ */ new Map();
function resolveDomain(domain) {
	if (domain === "lark") return Lark.Domain.Lark;
	if (domain === "feishu" || !domain) return Lark.Domain.Feishu;
	return domain.replace(/\/+$/, "");
}
/**
* Create or get a cached Feishu client for an account.
* Accepts any object with appId, appSecret, and optional domain/accountId.
*/
function createFeishuClient(creds) {
	const { accountId = "default", appId, appSecret, domain } = creds;
	if (!appId || !appSecret) throw new Error(`Feishu credentials not configured for account "${accountId}"`);
	const cached = clientCache.get(accountId);
	if (cached && cached.config.appId === appId && cached.config.appSecret === appSecret && cached.config.domain === domain) return cached.client;
	const client = new Lark.Client({
		appId,
		appSecret,
		appType: Lark.AppType.SelfBuild,
		domain: resolveDomain(domain)
	});
	clientCache.set(accountId, {
		client,
		config: {
			appId,
			appSecret,
			domain
		}
	});
	return client;
}
/**
* Create a Feishu WebSocket client for an account.
* Note: WSClient is not cached since each call creates a new connection.
*/
function createFeishuWSClient(account) {
	const { accountId, appId, appSecret, domain } = account;
	if (!appId || !appSecret) throw new Error(`Feishu credentials not configured for account "${accountId}"`);
	return new Lark.WSClient({
		appId,
		appSecret,
		domain: resolveDomain(domain),
		loggerLevel: Lark.LoggerLevel.info
	});
}
/**
* Create an event dispatcher for an account.
*/
function createEventDispatcher(account) {
	return new Lark.EventDispatcher({
		encryptKey: account.encryptKey,
		verificationToken: account.verificationToken
	});
}

//#endregion
//#region extensions/feishu/src/accounts.ts
/**
* List all configured account IDs from the accounts field.
*/
function listConfiguredAccountIds(cfg) {
	const accounts = (cfg.channels?.feishu)?.accounts;
	if (!accounts || typeof accounts !== "object") return [];
	return Object.keys(accounts).filter(Boolean);
}
/**
* List all Feishu account IDs.
* If no accounts are configured, returns [DEFAULT_ACCOUNT_ID] for backward compatibility.
*/
function listFeishuAccountIds(cfg) {
	const ids = listConfiguredAccountIds(cfg);
	if (ids.length === 0) return [DEFAULT_ACCOUNT_ID$1];
	return [...ids].toSorted((a, b) => a.localeCompare(b));
}
/**
* Resolve the default account ID.
*/
function resolveDefaultFeishuAccountId(cfg) {
	const ids = listFeishuAccountIds(cfg);
	if (ids.includes(DEFAULT_ACCOUNT_ID$1)) return DEFAULT_ACCOUNT_ID$1;
	return ids[0] ?? DEFAULT_ACCOUNT_ID$1;
}
/**
* Get the raw account-specific config.
*/
function resolveAccountConfig(cfg, accountId) {
	const accounts = (cfg.channels?.feishu)?.accounts;
	if (!accounts || typeof accounts !== "object") return;
	return accounts[accountId];
}
/**
* Merge top-level config with account-specific config.
* Account-specific fields override top-level fields.
*/
function mergeFeishuAccountConfig(cfg, accountId) {
	const { accounts: _ignored, ...base } = cfg.channels?.feishu ?? {};
	const account = resolveAccountConfig(cfg, accountId) ?? {};
	return {
		...base,
		...account
	};
}
/**
* Resolve Feishu credentials from a config.
*/
function resolveFeishuCredentials(cfg) {
	const appId = cfg?.appId?.trim();
	const appSecret = cfg?.appSecret?.trim();
	if (!appId || !appSecret) return null;
	return {
		appId,
		appSecret,
		encryptKey: cfg?.encryptKey?.trim() || void 0,
		verificationToken: cfg?.verificationToken?.trim() || void 0,
		domain: cfg?.domain ?? "feishu"
	};
}
/**
* Resolve a complete Feishu account with merged config.
*/
function resolveFeishuAccount(params) {
	const accountId = normalizeAccountId(params.accountId);
	const baseEnabled = (params.cfg.channels?.feishu)?.enabled !== false;
	const merged = mergeFeishuAccountConfig(params.cfg, accountId);
	const accountEnabled = merged.enabled !== false;
	const enabled = baseEnabled && accountEnabled;
	const creds = resolveFeishuCredentials(merged);
	return {
		accountId,
		enabled,
		configured: Boolean(creds),
		name: merged.name?.trim() || void 0,
		appId: creds?.appId,
		appSecret: creds?.appSecret,
		encryptKey: creds?.encryptKey,
		verificationToken: creds?.verificationToken,
		domain: creds?.domain ?? "feishu",
		config: merged
	};
}
/**
* List all enabled and configured accounts.
*/
function listEnabledFeishuAccounts(cfg) {
	return listFeishuAccountIds(cfg).map((accountId) => resolveFeishuAccount({
		cfg,
		accountId
	})).filter((account) => account.enabled && account.configured);
}

//#endregion
//#region extensions/feishu/src/targets.ts
const CHAT_ID_PREFIX = "oc_";
const OPEN_ID_PREFIX = "ou_";
function normalizeFeishuTarget(raw) {
	const trimmed = raw.trim();
	if (!trimmed) return null;
	const lowered = trimmed.toLowerCase();
	if (lowered.startsWith("chat:")) return trimmed.slice(5).trim() || null;
	if (lowered.startsWith("user:")) return trimmed.slice(5).trim() || null;
	if (lowered.startsWith("open_id:")) return trimmed.slice(8).trim() || null;
	return trimmed;
}
function resolveReceiveIdType(id) {
	const trimmed = id.trim();
	if (trimmed.startsWith(CHAT_ID_PREFIX)) return "chat_id";
	if (trimmed.startsWith(OPEN_ID_PREFIX)) return "open_id";
	return "user_id";
}
function looksLikeFeishuId(raw) {
	const trimmed = raw.trim();
	if (!trimmed) return false;
	if (/^(chat|user|open_id):/i.test(trimmed)) return true;
	if (trimmed.startsWith(CHAT_ID_PREFIX)) return true;
	if (trimmed.startsWith(OPEN_ID_PREFIX)) return true;
	return false;
}

//#endregion
//#region extensions/feishu/src/probe.ts
async function probeFeishu(creds) {
	if (!creds?.appId || !creds?.appSecret) return {
		ok: false,
		error: "missing credentials (appId, appSecret)"
	};
	try {
		const response = await createFeishuClient(creds).request({
			method: "GET",
			url: "/open-apis/bot/v3/info",
			data: {}
		});
		if (response.code !== 0) return {
			ok: false,
			appId: creds.appId,
			error: `API error: ${response.msg || `code ${response.code}`}`
		};
		const bot = response.bot || response.data?.bot;
		return {
			ok: true,
			appId: creds.appId,
			botName: bot?.bot_name,
			botOpenId: bot?.open_id
		};
	} catch (err) {
		return {
			ok: false,
			appId: creds.appId,
			error: err instanceof Error ? err.message : String(err)
		};
	}
}

//#endregion
//#region extensions/feishu/src/runtime.ts
let runtime = null;
function setFeishuRuntime(next) {
	runtime = next;
}
function getFeishuRuntime() {
	if (!runtime) throw new Error("Feishu runtime not initialized");
	return runtime;
}

//#endregion
//#region extensions/feishu/src/send-result.ts
function assertFeishuMessageApiSuccess(response, errorPrefix) {
	if (response.code !== 0) throw new Error(`${errorPrefix}: ${response.msg || `code ${response.code}`}`);
}
function toFeishuSendResult(response, chatId) {
	return {
		messageId: response.data?.message_id ?? "unknown",
		chatId
	};
}

//#endregion
//#region extensions/feishu/src/media.ts
async function readFeishuResponseBuffer(params) {
	const { response } = params;
	const responseAny = response;
	if (responseAny.code !== void 0 && responseAny.code !== 0) throw new Error(`${params.errorPrefix}: ${responseAny.msg || `code ${responseAny.code}`}`);
	if (Buffer.isBuffer(response)) return response;
	if (response instanceof ArrayBuffer) return Buffer.from(response);
	if (responseAny.data && Buffer.isBuffer(responseAny.data)) return responseAny.data;
	if (responseAny.data instanceof ArrayBuffer) return Buffer.from(responseAny.data);
	if (typeof responseAny.getReadableStream === "function") {
		const stream = responseAny.getReadableStream();
		const chunks = [];
		for await (const chunk of stream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
		return Buffer.concat(chunks);
	}
	if (typeof responseAny.writeFile === "function") {
		await responseAny.writeFile(params.tmpPath);
		const buffer = await fs.promises.readFile(params.tmpPath);
		await fs.promises.unlink(params.tmpPath).catch(() => {});
		return buffer;
	}
	if (typeof responseAny[Symbol.asyncIterator] === "function") {
		const chunks = [];
		for await (const chunk of responseAny) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
		return Buffer.concat(chunks);
	}
	if (typeof responseAny.read === "function") {
		const chunks = [];
		for await (const chunk of responseAny) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
		return Buffer.concat(chunks);
	}
	const types = Object.keys(responseAny).map((k) => `${k}: ${typeof responseAny[k]}`).join(", ");
	throw new Error(`${params.errorPrefix}: unexpected response format. Keys: [${types}]`);
}
/**
* Download a message resource (file/image/audio/video) from Feishu.
* Used for downloading files, audio, and video from messages.
*/
async function downloadMessageResourceFeishu(params) {
	const { cfg, messageId, fileKey, type, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	return { buffer: await readFeishuResponseBuffer({
		response: await createFeishuClient(account).im.messageResource.get({
			path: {
				message_id: messageId,
				file_key: fileKey
			},
			params: { type }
		}),
		tmpPath: path.join(os.tmpdir(), `feishu_${Date.now()}_${fileKey}`),
		errorPrefix: "Feishu message resource download failed"
	}) };
}
/**
* Upload an image to Feishu and get an image_key for sending.
* Supports: JPEG, PNG, WEBP, GIF, TIFF, BMP, ICO
*/
async function uploadImageFeishu(params) {
	const { cfg, image, imageType = "message", accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const client = createFeishuClient(account);
	const imageData = typeof image === "string" ? fs.createReadStream(image) : image;
	const responseAny = await client.im.image.create({ data: {
		image_type: imageType,
		image: imageData
	} });
	if (responseAny.code !== void 0 && responseAny.code !== 0) throw new Error(`Feishu image upload failed: ${responseAny.msg || `code ${responseAny.code}`}`);
	const imageKey = responseAny.image_key ?? responseAny.data?.image_key;
	if (!imageKey) throw new Error("Feishu image upload failed: no image_key returned");
	return { imageKey };
}
/**
* Upload a file to Feishu and get a file_key for sending.
* Max file size: 30MB
*/
async function uploadFileFeishu(params) {
	const { cfg, file, fileName, fileType, duration, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const client = createFeishuClient(account);
	const fileData = typeof file === "string" ? fs.createReadStream(file) : file;
	const responseAny = await client.im.file.create({ data: {
		file_type: fileType,
		file_name: fileName,
		file: fileData,
		...duration !== void 0 && { duration }
	} });
	if (responseAny.code !== void 0 && responseAny.code !== 0) throw new Error(`Feishu file upload failed: ${responseAny.msg || `code ${responseAny.code}`}`);
	const fileKey = responseAny.file_key ?? responseAny.data?.file_key;
	if (!fileKey) throw new Error("Feishu file upload failed: no file_key returned");
	return { fileKey };
}
/**
* Send an image message using an image_key
*/
async function sendImageFeishu(params) {
	const { cfg, to, imageKey, replyToMessageId, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const client = createFeishuClient(account);
	const receiveId = normalizeFeishuTarget(to);
	if (!receiveId) throw new Error(`Invalid Feishu target: ${to}`);
	const receiveIdType = resolveReceiveIdType(receiveId);
	const content = JSON.stringify({ image_key: imageKey });
	if (replyToMessageId) {
		const response = await client.im.message.reply({
			path: { message_id: replyToMessageId },
			data: {
				content,
				msg_type: "image"
			}
		});
		assertFeishuMessageApiSuccess(response, "Feishu image reply failed");
		return toFeishuSendResult(response, receiveId);
	}
	const response = await client.im.message.create({
		params: { receive_id_type: receiveIdType },
		data: {
			receive_id: receiveId,
			content,
			msg_type: "image"
		}
	});
	assertFeishuMessageApiSuccess(response, "Feishu image send failed");
	return toFeishuSendResult(response, receiveId);
}
/**
* Send a file message using a file_key
*/
async function sendFileFeishu(params) {
	const { cfg, to, fileKey, replyToMessageId, accountId } = params;
	const msgType = params.msgType ?? "file";
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const client = createFeishuClient(account);
	const receiveId = normalizeFeishuTarget(to);
	if (!receiveId) throw new Error(`Invalid Feishu target: ${to}`);
	const receiveIdType = resolveReceiveIdType(receiveId);
	const content = JSON.stringify({ file_key: fileKey });
	if (replyToMessageId) {
		const response = await client.im.message.reply({
			path: { message_id: replyToMessageId },
			data: {
				content,
				msg_type: msgType
			}
		});
		assertFeishuMessageApiSuccess(response, "Feishu file reply failed");
		return toFeishuSendResult(response, receiveId);
	}
	const response = await client.im.message.create({
		params: { receive_id_type: receiveIdType },
		data: {
			receive_id: receiveId,
			content,
			msg_type: msgType
		}
	});
	assertFeishuMessageApiSuccess(response, "Feishu file send failed");
	return toFeishuSendResult(response, receiveId);
}
/**
* Helper to detect file type from extension
*/
function detectFileType(fileName) {
	switch (path.extname(fileName).toLowerCase()) {
		case ".opus":
		case ".ogg": return "opus";
		case ".mp4":
		case ".mov":
		case ".avi": return "mp4";
		case ".pdf": return "pdf";
		case ".doc":
		case ".docx": return "doc";
		case ".xls":
		case ".xlsx": return "xls";
		case ".ppt":
		case ".pptx": return "ppt";
		default: return "stream";
	}
}
/**
* Upload and send media (image or file) from URL, local path, or buffer
*/
async function sendMediaFeishu(params) {
	const { cfg, to, mediaUrl, mediaBuffer, fileName, replyToMessageId, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const mediaMaxBytes = (account.config?.mediaMaxMb ?? 30) * 1024 * 1024;
	let buffer;
	let name;
	if (mediaBuffer) {
		buffer = mediaBuffer;
		name = fileName ?? "file";
	} else if (mediaUrl) {
		const loaded = await getFeishuRuntime().media.loadWebMedia(mediaUrl, {
			maxBytes: mediaMaxBytes,
			optimizeImages: false
		});
		buffer = loaded.buffer;
		name = fileName ?? loaded.fileName ?? "file";
	} else throw new Error("Either mediaUrl or mediaBuffer must be provided");
	const ext = path.extname(name).toLowerCase();
	if ([
		".jpg",
		".jpeg",
		".png",
		".gif",
		".webp",
		".bmp",
		".ico",
		".tiff"
	].includes(ext)) {
		const { imageKey } = await uploadImageFeishu({
			cfg,
			image: buffer,
			accountId
		});
		return sendImageFeishu({
			cfg,
			to,
			imageKey,
			replyToMessageId,
			accountId
		});
	} else {
		const fileType = detectFileType(name);
		const { fileKey } = await uploadFileFeishu({
			cfg,
			file: buffer,
			fileName: name,
			fileType,
			accountId
		});
		return sendFileFeishu({
			cfg,
			to,
			fileKey,
			msgType: fileType === "mp4" || fileType === "opus" ? "media" : "file",
			replyToMessageId,
			accountId
		});
	}
}

//#endregion
//#region extensions/feishu/src/mention.ts
/**
* Extract mention targets from message event (excluding the bot itself)
*/
function extractMentionTargets(event, botOpenId) {
	return (event.message.mentions ?? []).filter((m) => {
		if (botOpenId && m.id.open_id === botOpenId) return false;
		return !!m.id.open_id;
	}).map((m) => ({
		openId: m.id.open_id,
		name: m.name,
		key: m.key
	}));
}
/**
* Check if message is a mention forward request
* Rules:
* - Group: message mentions bot + at least one other user
* - DM: message mentions any user (no need to mention bot)
*/
function isMentionForwardRequest(event, botOpenId) {
	const mentions = event.message.mentions ?? [];
	if (mentions.length === 0) return false;
	const isDirectMessage = event.message.chat_type === "p2p";
	const hasOtherMention = mentions.some((m) => m.id.open_id !== botOpenId);
	if (isDirectMessage) return hasOtherMention;
	else return mentions.some((m) => m.id.open_id === botOpenId) && hasOtherMention;
}
/**
* Extract message body from text (remove @ placeholders)
*/
function extractMessageBody(text, allMentionKeys) {
	let result = text;
	for (const key of allMentionKeys) result = result.replace(new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "");
	return result.replace(/\s+/g, " ").trim();
}
/**
* Format @mention for text message
*/
function formatMentionForText(target) {
	return `<at user_id="${target.openId}">${target.name}</at>`;
}
/**
* Format @everyone for text message
*/
function formatMentionAllForText() {
	return `<at user_id="all">Everyone</at>`;
}
/**
* Format @mention for card message (lark_md)
*/
function formatMentionForCard(target) {
	return `<at id=${target.openId}></at>`;
}
/**
* Format @everyone for card message
*/
function formatMentionAllForCard() {
	return `<at id=all></at>`;
}
/**
* Build complete message with @mentions (text format)
*/
function buildMentionedMessage(targets, message) {
	if (targets.length === 0) return message;
	return `${targets.map((t) => formatMentionForText(t)).join(" ")} ${message}`;
}
/**
* Build card content with @mentions (Markdown format)
*/
function buildMentionedCardContent(targets, message) {
	if (targets.length === 0) return message;
	return `${targets.map((t) => formatMentionForCard(t)).join(" ")} ${message}`;
}

//#endregion
//#region extensions/feishu/src/send.ts
/**
* Get a message by its ID.
* Useful for fetching quoted/replied message content.
*/
async function getMessageFeishu(params) {
	const { cfg, messageId, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const client = createFeishuClient(account);
	try {
		const response = await client.im.message.get({ path: { message_id: messageId } });
		if (response.code !== 0) return null;
		const item = response.data?.items?.[0];
		if (!item) return null;
		let content = item.body?.content ?? "";
		try {
			const parsed = JSON.parse(content);
			if (item.msg_type === "text" && parsed.text) content = parsed.text;
		} catch {}
		return {
			messageId: item.message_id ?? messageId,
			chatId: item.chat_id ?? "",
			senderId: item.sender?.id,
			senderOpenId: item.sender?.id_type === "open_id" ? item.sender?.id : void 0,
			content,
			contentType: item.msg_type ?? "text",
			createTime: item.create_time ? parseInt(item.create_time, 10) : void 0
		};
	} catch {
		return null;
	}
}
function buildFeishuPostMessagePayload(params) {
	const { messageText } = params;
	return {
		content: JSON.stringify({ zh_cn: { content: [[{
			tag: "md",
			text: messageText
		}]] } }),
		msgType: "post"
	};
}
async function sendMessageFeishu(params) {
	const { cfg, to, text, replyToMessageId, mentions, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const client = createFeishuClient(account);
	const receiveId = normalizeFeishuTarget(to);
	if (!receiveId) throw new Error(`Invalid Feishu target: ${to}`);
	const receiveIdType = resolveReceiveIdType(receiveId);
	const tableMode = getFeishuRuntime().channel.text.resolveMarkdownTableMode({
		cfg,
		channel: "feishu"
	});
	let rawText = text ?? "";
	if (mentions && mentions.length > 0) rawText = buildMentionedMessage(mentions, rawText);
	const { content, msgType } = buildFeishuPostMessagePayload({ messageText: getFeishuRuntime().channel.text.convertMarkdownTables(rawText, tableMode) });
	if (replyToMessageId) {
		const response = await client.im.message.reply({
			path: { message_id: replyToMessageId },
			data: {
				content,
				msg_type: msgType
			}
		});
		assertFeishuMessageApiSuccess(response, "Feishu reply failed");
		return toFeishuSendResult(response, receiveId);
	}
	const response = await client.im.message.create({
		params: { receive_id_type: receiveIdType },
		data: {
			receive_id: receiveId,
			content,
			msg_type: msgType
		}
	});
	assertFeishuMessageApiSuccess(response, "Feishu send failed");
	return toFeishuSendResult(response, receiveId);
}
async function sendCardFeishu(params) {
	const { cfg, to, card, replyToMessageId, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const client = createFeishuClient(account);
	const receiveId = normalizeFeishuTarget(to);
	if (!receiveId) throw new Error(`Invalid Feishu target: ${to}`);
	const receiveIdType = resolveReceiveIdType(receiveId);
	const content = JSON.stringify(card);
	if (replyToMessageId) {
		const response = await client.im.message.reply({
			path: { message_id: replyToMessageId },
			data: {
				content,
				msg_type: "interactive"
			}
		});
		assertFeishuMessageApiSuccess(response, "Feishu card reply failed");
		return toFeishuSendResult(response, receiveId);
	}
	const response = await client.im.message.create({
		params: { receive_id_type: receiveIdType },
		data: {
			receive_id: receiveId,
			content,
			msg_type: "interactive"
		}
	});
	assertFeishuMessageApiSuccess(response, "Feishu card send failed");
	return toFeishuSendResult(response, receiveId);
}
async function updateCardFeishu(params) {
	const { cfg, messageId, card, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const client = createFeishuClient(account);
	const content = JSON.stringify(card);
	const response = await client.im.message.patch({
		path: { message_id: messageId },
		data: { content }
	});
	if (response.code !== 0) throw new Error(`Feishu card update failed: ${response.msg || `code ${response.code}`}`);
}
/**
* Build a Feishu interactive card with markdown content.
* Cards render markdown properly (code blocks, tables, links, etc.)
* Uses schema 2.0 format for proper markdown rendering.
*/
function buildMarkdownCard(text) {
	return {
		schema: "2.0",
		config: { wide_screen_mode: true },
		body: { elements: [{
			tag: "markdown",
			content: text
		}] }
	};
}
/**
* Send a message as a markdown card (interactive message).
* This renders markdown properly in Feishu (code blocks, tables, bold/italic, etc.)
*/
async function sendMarkdownCardFeishu(params) {
	const { cfg, to, text, replyToMessageId, mentions, accountId } = params;
	let cardText = text;
	if (mentions && mentions.length > 0) cardText = buildMentionedCardContent(mentions, text);
	return sendCardFeishu({
		cfg,
		to,
		card: buildMarkdownCard(cardText),
		replyToMessageId,
		accountId
	});
}
/**
* Edit an existing text message.
* Note: Feishu only allows editing messages within 24 hours.
*/
async function editMessageFeishu(params) {
	const { cfg, messageId, text, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
	const client = createFeishuClient(account);
	const tableMode = getFeishuRuntime().channel.text.resolveMarkdownTableMode({
		cfg,
		channel: "feishu"
	});
	const { content, msgType } = buildFeishuPostMessagePayload({ messageText: getFeishuRuntime().channel.text.convertMarkdownTables(text ?? "", tableMode) });
	const response = await client.im.message.update({
		path: { message_id: messageId },
		data: {
			msg_type: msgType,
			content
		}
	});
	if (response.code !== 0) throw new Error(`Feishu message edit failed: ${response.msg || `code ${response.code}`}`);
}

//#endregion
//#region extensions/feishu/src/policy.ts
function resolveFeishuAllowlistMatch(params) {
	return resolveAllowlistMatchSimple(params);
}
function resolveFeishuGroupConfig(params) {
	const groups = params.cfg?.groups ?? {};
	const groupId = params.groupId?.trim();
	if (!groupId) return;
	const direct = groups[groupId];
	if (direct) return direct;
	const lowered = groupId.toLowerCase();
	const matchKey = Object.keys(groups).find((key) => key.toLowerCase() === lowered);
	return matchKey ? groups[matchKey] : void 0;
}
function resolveFeishuGroupToolPolicy(params) {
	const cfg = params.cfg.channels?.feishu;
	if (!cfg) return;
	return resolveFeishuGroupConfig({
		cfg,
		groupId: params.groupId
	})?.tools;
}
function isFeishuGroupAllowed(params) {
	const { groupPolicy } = params;
	if (groupPolicy === "disabled") return false;
	if (groupPolicy === "open") return true;
	return resolveFeishuAllowlistMatch(params).allowed;
}
function resolveFeishuReplyPolicy(params) {
	if (params.isDirectMessage) return { requireMention: false };
	return { requireMention: params.groupConfig?.requireMention ?? params.globalConfig?.requireMention ?? true };
}

//#endregion
//#region extensions/feishu/src/dedup.ts
const DEDUP_TTL_MS = 1800 * 1e3;
const DEDUP_MAX_SIZE = 1e3;
const DEDUP_CLEANUP_INTERVAL_MS = 300 * 1e3;
const processedMessageIds = /* @__PURE__ */ new Map();
let lastCleanupTime = Date.now();
function tryRecordMessage(messageId) {
	const now = Date.now();
	if (now - lastCleanupTime > DEDUP_CLEANUP_INTERVAL_MS) {
		for (const [id, ts] of processedMessageIds) if (now - ts > DEDUP_TTL_MS) processedMessageIds.delete(id);
		lastCleanupTime = now;
	}
	if (processedMessageIds.has(messageId)) return false;
	if (processedMessageIds.size >= DEDUP_MAX_SIZE) {
		const first = processedMessageIds.keys().next().value;
		processedMessageIds.delete(first);
	}
	processedMessageIds.set(messageId, now);
	return true;
}

//#endregion
//#region extensions/feishu/src/dynamic-agent.ts
/**
* Check if a dynamic agent should be created for a DM user and create it if needed.
* This creates a unique agent instance with its own workspace for each DM user.
*/
async function maybeCreateDynamicAgent(params) {
	const { cfg, runtime, senderOpenId, dynamicCfg, log } = params;
	const existingBindings = cfg.bindings ?? [];
	if (existingBindings.some((b) => b.match?.channel === "feishu" && b.match?.peer?.kind === "direct" && b.match?.peer?.id === senderOpenId)) return {
		created: false,
		updatedCfg: cfg
	};
	if (dynamicCfg.maxAgents !== void 0) {
		if ((cfg.agents?.list ?? []).filter((a) => a.id.startsWith("feishu-")).length >= dynamicCfg.maxAgents) {
			log(`feishu: maxAgents limit (${dynamicCfg.maxAgents}) reached, not creating agent for ${senderOpenId}`);
			return {
				created: false,
				updatedCfg: cfg
			};
		}
	}
	const agentId = `feishu-${senderOpenId}`;
	if ((cfg.agents?.list ?? []).find((a) => a.id === agentId)) {
		log(`feishu: agent "${agentId}" exists, adding missing binding for ${senderOpenId}`);
		const updatedCfg = {
			...cfg,
			bindings: [...existingBindings, {
				agentId,
				match: {
					channel: "feishu",
					peer: {
						kind: "direct",
						id: senderOpenId
					}
				}
			}]
		};
		await runtime.config.writeConfigFile(updatedCfg);
		return {
			created: true,
			updatedCfg,
			agentId
		};
	}
	const workspaceTemplate = dynamicCfg.workspaceTemplate ?? "~/.openclaw/workspace-{agentId}";
	const agentDirTemplate = dynamicCfg.agentDirTemplate ?? "~/.openclaw/agents/{agentId}/agent";
	const workspace = resolveUserPath(workspaceTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId));
	const agentDir = resolveUserPath(agentDirTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId));
	log(`feishu: creating dynamic agent "${agentId}" for user ${senderOpenId}`);
	log(`  workspace: ${workspace}`);
	log(`  agentDir: ${agentDir}`);
	await fs$1.promises.mkdir(workspace, { recursive: true });
	await fs$1.promises.mkdir(agentDir, { recursive: true });
	const updatedCfg = {
		...cfg,
		agents: {
			...cfg.agents,
			list: [...cfg.agents?.list ?? [], {
				id: agentId,
				workspace,
				agentDir
			}]
		},
		bindings: [...existingBindings, {
			agentId,
			match: {
				channel: "feishu",
				peer: {
					kind: "direct",
					id: senderOpenId
				}
			}
		}]
	};
	await runtime.config.writeConfigFile(updatedCfg);
	return {
		created: true,
		updatedCfg,
		agentId
	};
}
/**
* Resolve a path that may start with ~ to the user's home directory.
*/
function resolveUserPath(p) {
	if (p.startsWith("~/")) return path$1.join(os$1.homedir(), p.slice(2));
	return p;
}

//#endregion
//#region extensions/feishu/src/streaming-card.ts
const tokenCache = /* @__PURE__ */ new Map();
function resolveApiBase(domain) {
	if (domain === "lark") return "https://open.larksuite.com/open-apis";
	if (domain && domain !== "feishu" && domain.startsWith("http")) return `${domain.replace(/\/+$/, "")}/open-apis`;
	return "https://open.feishu.cn/open-apis";
}
async function getToken(creds) {
	const key = `${creds.domain ?? "feishu"}|${creds.appId}`;
	const cached = tokenCache.get(key);
	if (cached && cached.expiresAt > Date.now() + 6e4) return cached.token;
	const data = await (await fetch(`${resolveApiBase(creds.domain)}/auth/v3/tenant_access_token/internal`, {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({
			app_id: creds.appId,
			app_secret: creds.appSecret
		})
	})).json();
	if (data.code !== 0 || !data.tenant_access_token) throw new Error(`Token error: ${data.msg}`);
	tokenCache.set(key, {
		token: data.tenant_access_token,
		expiresAt: Date.now() + (data.expire ?? 7200) * 1e3
	});
	return data.tenant_access_token;
}
function truncateSummary(text, max = 50) {
	if (!text) return "";
	const clean = text.replace(/\n/g, " ").trim();
	return clean.length <= max ? clean : clean.slice(0, max - 3) + "...";
}
/** Build the display content for the streaming card element.
*
* Tool hints are appended below the main text as italic lines so they appear
* inline without interrupting the response flow. The same hint is never
* appended twice (dedup enforced by the caller via seenHints).
*/
function buildDisplayContent(mainText, hints) {
	if (!hints.length) return mainText;
	return `${mainText}\n\n${hints.map((h) => `_${h}_`).join("\n")}`;
}
/** Maximum number of streaming resume attempts per session. */
const MAX_RESUME_ATTEMPTS = 2;
/** Streaming card session manager */
var FeishuStreamingSession = class {
	constructor(client, creds, optsOrLog) {
		this.state = null;
		this.queue = Promise.resolve();
		this.closed = false;
		this.lastUpdateTime = 0;
		this.pendingText = null;
		this.updateThrottleMs = 100;
		this.receiveId = null;
		this.receiveIdType = "chat_id";
		this.resumeAttempts = 0;
		this.resuming = false;
		this.activeHints = [];
		this.seenHints = /* @__PURE__ */ new Set();
		this.client = client;
		this.creds = creds;
		if (typeof optsOrLog === "function") {
			this.log = optsOrLog;
			this.toolHintPrefix = "🔧 ";
		} else {
			this.log = optsOrLog?.log;
			this.toolHintPrefix = optsOrLog?.toolHintPrefix ?? "🔧 ";
		}
	}
	async start(receiveId, receiveIdType = "chat_id") {
		if (this.state) return;
		this.receiveId = receiveId;
		this.receiveIdType = receiveIdType;
		await this._createCard(receiveId, receiveIdType);
	}
	async _createCard(receiveId, receiveIdType) {
		const apiBase = resolveApiBase(this.creds.domain);
		const cardJson = {
			schema: "2.0",
			config: {
				streaming_mode: true,
				summary: { content: "[Generating...]" },
				streaming_config: {
					print_frequency_ms: { default: 50 },
					print_step: { default: 2 }
				}
			},
			body: { elements: [{
				tag: "markdown",
				content: "⏳ Thinking...",
				element_id: "content"
			}] }
		};
		const createData = await (await fetch(`${apiBase}/cardkit/v1/cards`, {
			method: "POST",
			headers: {
				Authorization: `Bearer ${await getToken(this.creds)}`,
				"Content-Type": "application/json"
			},
			body: JSON.stringify({
				type: "card_json",
				data: JSON.stringify(cardJson)
			})
		})).json();
		if (createData.code !== 0 || !createData.data?.card_id) throw new Error(`Create card failed: ${createData.msg}`);
		const cardId = createData.data.card_id;
		const sendRes = await this.client.im.message.create({
			params: { receive_id_type: receiveIdType },
			data: {
				receive_id: receiveId,
				msg_type: "interactive",
				content: JSON.stringify({
					type: "card",
					data: { card_id: cardId }
				})
			}
		});
		if (sendRes.code !== 0 || !sendRes.data?.message_id) throw new Error(`Send card failed: ${sendRes.msg}`);
		this.state = {
			cardId,
			messageId: sendRes.data.message_id,
			sequence: 1,
			currentText: ""
		};
		this.log?.(`Started streaming: cardId=${cardId}, messageId=${sendRes.data.message_id}`);
	}
	/**
	* Resume streaming after the card session becomes invalid.
	*
	* Creates a new streaming card in the same chat and picks up from the last
	* known text, so the user sees a continuation rather than a silent failure.
	*/
	async _tryResume(fromText) {
		if (this.resuming || this.resumeAttempts >= MAX_RESUME_ATTEMPTS || !this.receiveId) return false;
		this.resuming = true;
		this.resumeAttempts += 1;
		this.log?.(`feishu: streaming resume attempt ${this.resumeAttempts}/${MAX_RESUME_ATTEMPTS}`);
		try {
			this.state = null;
			await this._createCard(this.receiveId, this.receiveIdType);
			const resumedState = this.state;
			if (resumedState && fromText) {
				resumedState.currentText = fromText;
				resumedState.sequence += 1;
				const apiBase = resolveApiBase(this.creds.domain);
				await fetch(`${apiBase}/cardkit/v1/cards/${resumedState.cardId}/elements/content/content`, {
					method: "PUT",
					headers: {
						Authorization: `Bearer ${await getToken(this.creds)}`,
						"Content-Type": "application/json"
					},
					body: JSON.stringify({
						content: buildDisplayContent(fromText, this.activeHints),
						sequence: resumedState.sequence,
						uuid: `s_${resumedState.cardId}_${resumedState.sequence}`
					})
				}).catch((e) => this.log?.(`Resume seed update failed: ${String(e)}`));
			}
			this.log?.(`feishu: streaming resumed on new card ${resumedState?.cardId}`);
			return true;
		} catch (err) {
			this.log?.(`feishu: streaming resume failed: ${String(err)}`);
			return false;
		} finally {
			this.resuming = false;
		}
	}
	async update(text) {
		if (!this.state || this.closed) return;
		const now = Date.now();
		if (now - this.lastUpdateTime < this.updateThrottleMs) {
			this.pendingText = text;
			return;
		}
		this.pendingText = null;
		this.lastUpdateTime = now;
		this.queue = this.queue.then(async () => {
			if (!this.state || this.closed) return;
			this.state.currentText = text;
			this.state.sequence += 1;
			const apiBase = resolveApiBase(this.creds.domain);
			const res = await fetch(`${apiBase}/cardkit/v1/cards/${this.state.cardId}/elements/content/content`, {
				method: "PUT",
				headers: {
					Authorization: `Bearer ${await getToken(this.creds)}`,
					"Content-Type": "application/json"
				},
				body: JSON.stringify({
					content: buildDisplayContent(text, this.activeHints),
					sequence: this.state.sequence,
					uuid: `s_${this.state.cardId}_${this.state.sequence}`
				})
			}).catch((e) => {
				this.log?.(`Update failed: ${String(e)}`);
				return null;
			});
			if (res !== null && !res.ok && this.state) {
				const statusCode = res.status;
				if (statusCode === 404 || statusCode === 400) await this._tryResume(text);
			}
		});
		await this.queue;
	}
	/**
	* Set the active tool hints shown inline during streaming.
	*
	* Each hint name is formatted with the configured prefix and appended as its
	* own italic line below the response text. Hints already seen in this session
	* are silently skipped (deduplication), so the same tool name never stacks.
	*
	* Calling with an empty array clears the currently displayed hints.
	*/
	setToolHints(names) {
		if (!names.length) {
			this.activeHints = [];
			return;
		}
		const newHints = [];
		for (const name of names) {
			const formatted = `${this.toolHintPrefix}${name}`;
			if (!this.seenHints.has(formatted)) {
				this.seenHints.add(formatted);
				newHints.push(formatted);
			}
		}
		const requested = new Set(names.map((n) => `${this.toolHintPrefix}${n}`));
		this.activeHints = [...this.seenHints].filter((h) => requested.has(h));
	}
	async close(finalText) {
		if (!this.state || this.closed) return;
		this.closed = true;
		await this.queue;
		this.activeHints = [];
		const text = finalText ?? this.pendingText ?? this.state.currentText;
		const apiBase = resolveApiBase(this.creds.domain);
		if (text && text !== this.state.currentText) {
			this.state.sequence += 1;
			await fetch(`${apiBase}/cardkit/v1/cards/${this.state.cardId}/elements/content/content`, {
				method: "PUT",
				headers: {
					Authorization: `Bearer ${await getToken(this.creds)}`,
					"Content-Type": "application/json"
				},
				body: JSON.stringify({
					content: text,
					sequence: this.state.sequence,
					uuid: `s_${this.state.cardId}_${this.state.sequence}`
				})
			}).catch(() => {});
			this.state.currentText = text;
		}
		this.state.sequence += 1;
		await fetch(`${apiBase}/cardkit/v1/cards/${this.state.cardId}/settings`, {
			method: "PATCH",
			headers: {
				Authorization: `Bearer ${await getToken(this.creds)}`,
				"Content-Type": "application/json; charset=utf-8"
			},
			body: JSON.stringify({
				settings: JSON.stringify({ config: {
					streaming_mode: false,
					summary: { content: truncateSummary(text) }
				} }),
				sequence: this.state.sequence,
				uuid: `c_${this.state.cardId}_${this.state.sequence}`
			})
		}).catch((e) => this.log?.(`Close failed: ${String(e)}`));
		this.log?.(`Closed streaming: cardId=${this.state.cardId}`);
	}
	isActive() {
		return this.state !== null && !this.closed;
	}
};

//#endregion
//#region extensions/feishu/src/typing.ts
const TYPING_EMOJI = "Typing";
/**
* Add a typing indicator (reaction) to a message
*/
async function addTypingIndicator(params) {
	const { cfg, messageId, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) return {
		messageId,
		reactionId: null
	};
	const client = createFeishuClient(account);
	try {
		return {
			messageId,
			reactionId: (await client.im.messageReaction.create({
				path: { message_id: messageId },
				data: { reaction_type: { emoji_type: TYPING_EMOJI } }
			}))?.data?.reaction_id ?? null
		};
	} catch (err) {
		console.log(`[feishu] failed to add typing indicator: ${err}`);
		return {
			messageId,
			reactionId: null
		};
	}
}
/**
* Remove a typing indicator (reaction) from a message
*/
async function removeTypingIndicator(params) {
	const { cfg, state, accountId } = params;
	if (!state.reactionId) return;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	if (!account.configured) return;
	const client = createFeishuClient(account);
	try {
		await client.im.messageReaction.delete({ path: {
			message_id: state.messageId,
			reaction_id: state.reactionId
		} });
	} catch (err) {
		console.log(`[feishu] failed to remove typing indicator: ${err}`);
	}
}

//#endregion
//#region extensions/feishu/src/reply-dispatcher.ts
/** Detect if text contains markdown elements that benefit from card rendering */
function shouldUseCard(text) {
	return /```[\s\S]*?```/.test(text) || /\|.+\|[\r\n]+\|[-:| ]+\|/.test(text);
}
function createFeishuReplyDispatcher(params) {
	const core = getFeishuRuntime();
	const { cfg, agentId, chatId, replyToMessageId, mentionTargets, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	const prefixContext = createReplyPrefixContext({
		cfg,
		agentId
	});
	let typingState = null;
	const typingCallbacks = createTypingCallbacks({
		start: async () => {
			if (!replyToMessageId) return;
			typingState = await addTypingIndicator({
				cfg,
				messageId: replyToMessageId,
				accountId
			});
		},
		stop: async () => {
			if (!typingState) return;
			await removeTypingIndicator({
				cfg,
				state: typingState,
				accountId
			});
			typingState = null;
		},
		onStartError: (err) => logTypingFailure({
			log: (message) => params.runtime.log?.(message),
			channel: "feishu",
			action: "start",
			error: err
		}),
		onStopError: (err) => logTypingFailure({
			log: (message) => params.runtime.log?.(message),
			channel: "feishu",
			action: "stop",
			error: err
		})
	});
	const textChunkLimit = core.channel.text.resolveTextChunkLimit(cfg, "feishu", accountId, { fallbackLimit: 4e3 });
	const chunkMode = core.channel.text.resolveChunkMode(cfg, "feishu");
	const tableMode = core.channel.text.resolveMarkdownTableMode({
		cfg,
		channel: "feishu"
	});
	const renderMode = account.config?.renderMode ?? "auto";
	const streamingEnabled = account.config?.streaming !== false && renderMode !== "raw";
	const toolHintPrefix = account.config?.toolHintPrefix;
	let streaming = null;
	let streamText = "";
	let lastPartial = "";
	let partialUpdateQueue = Promise.resolve();
	let streamingStartPromise = null;
	const startStreaming = () => {
		if (!streamingEnabled || streamingStartPromise || streaming) return;
		streamingStartPromise = (async () => {
			const creds = account.appId && account.appSecret ? {
				appId: account.appId,
				appSecret: account.appSecret,
				domain: account.domain
			} : null;
			if (!creds) return;
			const sessionOpts = {
				log: (message) => params.runtime.log?.(`feishu[${account.accountId}] ${message}`),
				...toolHintPrefix !== void 0 && { toolHintPrefix }
			};
			streaming = new FeishuStreamingSession(createFeishuClient(account), creds, sessionOpts);
			try {
				await streaming.start(chatId, resolveReceiveIdType(chatId));
			} catch (error) {
				params.runtime.error?.(`feishu: streaming start failed: ${String(error)}`);
				streaming = null;
			}
		})();
	};
	const closeStreaming = async () => {
		if (streamingStartPromise) await streamingStartPromise;
		await partialUpdateQueue;
		if (streaming?.isActive()) {
			let text = streamText;
			if (mentionTargets?.length) text = buildMentionedCardContent(mentionTargets, text);
			await streaming.close(text);
		}
		streaming = null;
		streamingStartPromise = null;
		streamText = "";
		lastPartial = "";
	};
	const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
		responsePrefix: prefixContext.responsePrefix,
		responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
		humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, agentId),
		onReplyStart: () => {
			if (streamingEnabled && renderMode === "card") startStreaming();
			typingCallbacks.onReplyStart?.();
		},
		deliver: async (payload, info) => {
			const text = payload.text ?? "";
			if (!text.trim()) return;
			const useCard = renderMode === "card" || renderMode === "auto" && shouldUseCard(text);
			if ((info?.kind === "block" || info?.kind === "final") && streamingEnabled && useCard) {
				startStreaming();
				if (streamingStartPromise) await streamingStartPromise;
			}
			if (streaming?.isActive()) {
				if (info?.kind === "final") {
					streamText = text;
					await closeStreaming();
				}
				return;
			}
			let first = true;
			if (useCard) for (const chunk of core.channel.text.chunkTextWithMode(text, textChunkLimit, chunkMode)) {
				await sendMarkdownCardFeishu({
					cfg,
					to: chatId,
					text: chunk,
					replyToMessageId,
					mentions: first ? mentionTargets : void 0,
					accountId
				});
				first = false;
			}
			else {
				const converted = core.channel.text.convertMarkdownTables(text, tableMode);
				for (const chunk of core.channel.text.chunkTextWithMode(converted, textChunkLimit, chunkMode)) {
					await sendMessageFeishu({
						cfg,
						to: chatId,
						text: chunk,
						replyToMessageId,
						mentions: first ? mentionTargets : void 0,
						accountId
					});
					first = false;
				}
			}
		},
		onError: async (error, info) => {
			params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
			await closeStreaming();
			typingCallbacks.onIdle?.();
		},
		onIdle: async () => {
			await closeStreaming();
			typingCallbacks.onIdle?.();
		},
		onCleanup: () => {
			typingCallbacks.onCleanup?.();
		}
	});
	return {
		dispatcher,
		replyOptions: {
			...replyOptions,
			onModelSelected: prefixContext.onModelSelected,
			onPartialReply: streamingEnabled ? (payload) => {
				if (!payload.text || payload.text === lastPartial) return;
				lastPartial = payload.text;
				streamText = payload.text;
				partialUpdateQueue = partialUpdateQueue.then(async () => {
					if (streamingStartPromise) await streamingStartPromise;
					if (streaming?.isActive()) await streaming.update(streamText);
				});
			} : void 0,
			onToolStart: streamingEnabled ? (payload) => {
				const activeSession = streaming;
				if (!payload.name || !activeSession?.isActive()) return;
				activeSession.setToolHints([payload.name]);
				partialUpdateQueue = partialUpdateQueue.then(async () => {
					if (streamingStartPromise) await streamingStartPromise;
					if (streaming?.isActive()) await streaming.update(streamText);
				});
			} : void 0
		},
		markDispatchIdle
	};
}

//#endregion
//#region extensions/feishu/src/bot.ts
function extractPermissionError(err) {
	if (!err || typeof err !== "object") return null;
	const data = err.response?.data;
	if (!data || typeof data !== "object") return null;
	const feishuErr = data;
	if (feishuErr.code !== 99991672) return null;
	const msg = feishuErr.msg ?? "";
	const grantUrl = msg.match(/https:\/\/[^\s,]+\/app\/[^\s,]+/)?.[0];
	return {
		code: feishuErr.code,
		message: msg,
		grantUrl
	};
}
const SENDER_NAME_TTL_MS = 600 * 1e3;
const senderNameCache = /* @__PURE__ */ new Map();
const permissionErrorNotifiedAt = /* @__PURE__ */ new Map();
const PERMISSION_ERROR_COOLDOWN_MS = 300 * 1e3;
async function resolveFeishuSenderName(params) {
	const { account, senderOpenId, log } = params;
	if (!account.configured) return {};
	if (!senderOpenId) return {};
	const cached = senderNameCache.get(senderOpenId);
	const now = Date.now();
	if (cached && cached.expireAt > now) return { name: cached.name };
	try {
		const res = await createFeishuClient(account).contact.user.get({
			path: { user_id: senderOpenId },
			params: { user_id_type: "open_id" }
		});
		const name = res?.data?.user?.name || res?.data?.user?.display_name || res?.data?.user?.nickname || res?.data?.user?.en_name;
		if (name && typeof name === "string") {
			senderNameCache.set(senderOpenId, {
				name,
				expireAt: now + SENDER_NAME_TTL_MS
			});
			return { name };
		}
		return {};
	} catch (err) {
		const permErr = extractPermissionError(err);
		if (permErr) {
			log(`feishu: permission error resolving sender name: code=${permErr.code}`);
			return { permissionError: permErr };
		}
		log(`feishu: failed to resolve sender name for ${senderOpenId}: ${String(err)}`);
		return {};
	}
}
function parseMessageContent(content, messageType) {
	try {
		const parsed = JSON.parse(content);
		if (messageType === "text") return parsed.text || "";
		if (messageType === "post") {
			const { textContent } = parsePostContent(content);
			return textContent;
		}
		return content;
	} catch {
		return content;
	}
}
function checkBotMentioned(event, botOpenId) {
	if (!botOpenId) return false;
	const mentions = event.message.mentions ?? [];
	if (mentions.length > 0) return mentions.some((m) => m.id.open_id === botOpenId);
	if (event.message.message_type === "post") {
		const { mentionedOpenIds } = parsePostContent(event.message.content);
		return mentionedOpenIds.some((id) => id === botOpenId);
	}
	return false;
}
function stripBotMention(text, mentions) {
	if (!mentions || mentions.length === 0) return text;
	let result = text;
	for (const mention of mentions) {
		result = result.replace(new RegExp(`@${mention.name}\\s*`, "g"), "").trim();
		result = result.replace(new RegExp(mention.key, "g"), "").trim();
	}
	return result;
}
/**
* Parse media keys from message content based on message type.
*/
function parseMediaKeys(content, messageType) {
	try {
		const parsed = JSON.parse(content);
		switch (messageType) {
			case "image": return { imageKey: parsed.image_key };
			case "file": return {
				fileKey: parsed.file_key,
				fileName: parsed.file_name
			};
			case "audio": return { fileKey: parsed.file_key };
			case "video": return {
				fileKey: parsed.file_key,
				imageKey: parsed.image_key
			};
			case "sticker": return { fileKey: parsed.file_key };
			default: return {};
		}
	} catch {
		return {};
	}
}
/**
* Parse post (rich text) content and extract embedded image keys.
* Post structure: { title?: string, content: [[{ tag, text?, image_key?, ... }]] }
*/
function parsePostContent(content) {
	try {
		const parsed = JSON.parse(content);
		const title = parsed.title || "";
		const contentBlocks = parsed.content || [];
		let textContent = title ? `${title}\n\n` : "";
		const imageKeys = [];
		const mentionedOpenIds = [];
		for (const paragraph of contentBlocks) if (Array.isArray(paragraph)) {
			for (const element of paragraph) if (element.tag === "text") textContent += element.text || "";
			else if (element.tag === "a") textContent += element.text || element.href || "";
			else if (element.tag === "at") {
				textContent += `@${element.user_name || element.user_id || ""}`;
				if (element.user_id) mentionedOpenIds.push(element.user_id);
			} else if (element.tag === "img" && element.image_key) imageKeys.push(element.image_key);
			textContent += "\n";
		}
		return {
			textContent: textContent.trim() || "[Rich text message]",
			imageKeys,
			mentionedOpenIds
		};
	} catch {
		return {
			textContent: "[Rich text message]",
			imageKeys: [],
			mentionedOpenIds: []
		};
	}
}
/**
* Infer placeholder text based on message type.
*/
function inferPlaceholder(messageType) {
	switch (messageType) {
		case "image": return "<media:image>";
		case "file": return "<media:document>";
		case "audio": return "<media:audio>";
		case "video": return "<media:video>";
		case "sticker": return "<media:sticker>";
		default: return "<media:document>";
	}
}
/**
* Resolve media from a Feishu message, downloading and saving to disk.
* Similar to Discord's resolveMediaList().
*/
async function resolveFeishuMediaList(params) {
	const { cfg, messageId, messageType, content, maxBytes, log, accountId } = params;
	if (![
		"image",
		"file",
		"audio",
		"video",
		"sticker",
		"post"
	].includes(messageType)) return [];
	const out = [];
	const core = getFeishuRuntime();
	if (messageType === "post") {
		const { imageKeys } = parsePostContent(content);
		if (imageKeys.length === 0) return [];
		log?.(`feishu: post message contains ${imageKeys.length} embedded image(s)`);
		for (const imageKey of imageKeys) try {
			const result = await downloadMessageResourceFeishu({
				cfg,
				messageId,
				fileKey: imageKey,
				type: "image",
				accountId
			});
			let contentType = result.contentType;
			if (!contentType) contentType = await core.media.detectMime({ buffer: result.buffer });
			const saved = await core.channel.media.saveMediaBuffer(result.buffer, contentType, "inbound", maxBytes);
			out.push({
				path: saved.path,
				contentType: saved.contentType,
				placeholder: "<media:image>"
			});
			log?.(`feishu: downloaded embedded image ${imageKey}, saved to ${saved.path}`);
		} catch (err) {
			log?.(`feishu: failed to download embedded image ${imageKey}: ${String(err)}`);
		}
		return out;
	}
	const mediaKeys = parseMediaKeys(content, messageType);
	if (!mediaKeys.imageKey && !mediaKeys.fileKey) return [];
	try {
		let buffer;
		let contentType;
		let fileName;
		const fileKey = mediaKeys.imageKey || mediaKeys.fileKey;
		if (!fileKey) return [];
		const result = await downloadMessageResourceFeishu({
			cfg,
			messageId,
			fileKey,
			type: messageType === "image" ? "image" : "file",
			accountId
		});
		buffer = result.buffer;
		contentType = result.contentType;
		fileName = result.fileName || mediaKeys.fileName;
		if (!contentType) contentType = await core.media.detectMime({ buffer });
		const saved = await core.channel.media.saveMediaBuffer(buffer, contentType, "inbound", maxBytes, fileName);
		out.push({
			path: saved.path,
			contentType: saved.contentType,
			placeholder: inferPlaceholder(messageType)
		});
		log?.(`feishu: downloaded ${messageType} media, saved to ${saved.path}`);
	} catch (err) {
		log?.(`feishu: failed to download ${messageType} media: ${String(err)}`);
	}
	return out;
}
/**
* Build media payload for inbound context.
* Similar to Discord's buildDiscordMediaPayload().
*/
function parseFeishuMessageEvent(event, botOpenId) {
	const rawContent = parseMessageContent(event.message.content, event.message.message_type);
	const mentionedBot = checkBotMentioned(event, botOpenId);
	const content = stripBotMention(rawContent, event.message.mentions);
	const ctx = {
		chatId: event.message.chat_id,
		messageId: event.message.message_id,
		senderId: event.sender.sender_id.user_id || event.sender.sender_id.open_id || "",
		senderOpenId: event.sender.sender_id.open_id || "",
		chatType: event.message.chat_type,
		mentionedBot,
		rootId: event.message.root_id || void 0,
		parentId: event.message.parent_id || void 0,
		content,
		contentType: event.message.message_type
	};
	if (isMentionForwardRequest(event, botOpenId)) {
		const mentionTargets = extractMentionTargets(event, botOpenId);
		if (mentionTargets.length > 0) {
			ctx.mentionTargets = mentionTargets;
			ctx.mentionMessageBody = extractMessageBody(content, (event.message.mentions ?? []).map((m) => m.key));
		}
	}
	return ctx;
}
async function handleFeishuMessage(params) {
	const { cfg, event, botOpenId, runtime, chatHistories, accountId } = params;
	const account = resolveFeishuAccount({
		cfg,
		accountId
	});
	const feishuCfg = account.config;
	const log = runtime?.log ?? console.log;
	const error = runtime?.error ?? console.error;
	const messageId = event.message.message_id;
	if (!tryRecordMessage(messageId)) {
		log(`feishu: skipping duplicate message ${messageId}`);
		return;
	}
	let ctx = parseFeishuMessageEvent(event, botOpenId);
	const isGroup = ctx.chatType === "group";
	const senderResult = await resolveFeishuSenderName({
		account,
		senderOpenId: ctx.senderOpenId,
		log
	});
	if (senderResult.name) ctx = {
		...ctx,
		senderName: senderResult.name
	};
	let permissionErrorForAgent;
	if (senderResult.permissionError) {
		const appKey = account.appId ?? "default";
		const now = Date.now();
		if (now - (permissionErrorNotifiedAt.get(appKey) ?? 0) > PERMISSION_ERROR_COOLDOWN_MS) {
			permissionErrorNotifiedAt.set(appKey, now);
			permissionErrorForAgent = senderResult.permissionError;
		}
	}
	log(`feishu[${account.accountId}]: received message from ${ctx.senderOpenId} in ${ctx.chatId} (${ctx.chatType})`);
	if (ctx.mentionTargets && ctx.mentionTargets.length > 0) {
		const names = ctx.mentionTargets.map((t) => t.name).join(", ");
		log(`feishu[${account.accountId}]: detected @ forward request, targets: [${names}]`);
	}
	const historyLimit = Math.max(0, feishuCfg?.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT);
	const groupConfig = isGroup ? resolveFeishuGroupConfig({
		cfg: feishuCfg,
		groupId: ctx.chatId
	}) : void 0;
	const dmPolicy = feishuCfg?.dmPolicy ?? "pairing";
	const configAllowFrom = feishuCfg?.allowFrom ?? [];
	const useAccessGroups = cfg.commands?.useAccessGroups !== false;
	if (isGroup) {
		if (!isFeishuGroupAllowed({
			groupPolicy: feishuCfg?.groupPolicy ?? "open",
			allowFrom: feishuCfg?.groupAllowFrom ?? [],
			senderId: ctx.chatId,
			senderName: void 0
		})) {
			log(`feishu[${account.accountId}]: sender ${ctx.senderOpenId} not in group allowlist`);
			return;
		}
		const senderAllowFrom = groupConfig?.allowFrom ?? [];
		if (senderAllowFrom.length > 0) {
			if (!isFeishuGroupAllowed({
				groupPolicy: "allowlist",
				allowFrom: senderAllowFrom,
				senderId: ctx.senderOpenId,
				senderName: ctx.senderName
			})) {
				log(`feishu: sender ${ctx.senderOpenId} not in group ${ctx.chatId} sender allowlist`);
				return;
			}
		}
		const { requireMention } = resolveFeishuReplyPolicy({
			isDirectMessage: false,
			globalConfig: feishuCfg,
			groupConfig
		});
		if (requireMention && !ctx.mentionedBot) {
			log(`feishu[${account.accountId}]: message in group ${ctx.chatId} did not mention bot, recording to history`);
			if (chatHistories) recordPendingHistoryEntryIfEnabled({
				historyMap: chatHistories,
				historyKey: ctx.chatId,
				limit: historyLimit,
				entry: {
					sender: ctx.senderOpenId,
					body: `${ctx.senderName ?? ctx.senderOpenId}: ${ctx.content}`,
					timestamp: Date.now(),
					messageId: ctx.messageId
				}
			});
			return;
		}
	}
	try {
		const core = getFeishuRuntime();
		const shouldComputeCommandAuthorized = core.channel.commands.shouldComputeCommandAuthorized(ctx.content, cfg);
		const storeAllowFrom = !isGroup && (dmPolicy !== "open" || shouldComputeCommandAuthorized) ? await core.channel.pairing.readAllowFromStore("feishu").catch(() => []) : [];
		const effectiveDmAllowFrom = [...configAllowFrom, ...storeAllowFrom];
		const dmAllowed = resolveFeishuAllowlistMatch({
			allowFrom: effectiveDmAllowFrom,
			senderId: ctx.senderOpenId,
			senderName: ctx.senderName
		}).allowed;
		if (!isGroup && dmPolicy !== "open" && !dmAllowed) {
			if (dmPolicy === "pairing") {
				const { code, created } = await core.channel.pairing.upsertPairingRequest({
					channel: "feishu",
					id: ctx.senderOpenId,
					meta: { name: ctx.senderName }
				});
				if (created) {
					log(`feishu[${account.accountId}]: pairing request sender=${ctx.senderOpenId}`);
					try {
						await sendMessageFeishu({
							cfg,
							to: `user:${ctx.senderOpenId}`,
							text: core.channel.pairing.buildPairingReply({
								channel: "feishu",
								idLine: `Your Feishu user id: ${ctx.senderOpenId}`,
								code
							}),
							accountId: account.accountId
						});
					} catch (err) {
						log(`feishu[${account.accountId}]: pairing reply failed for ${ctx.senderOpenId}: ${String(err)}`);
					}
				}
			} else log(`feishu[${account.accountId}]: blocked unauthorized sender ${ctx.senderOpenId} (dmPolicy=${dmPolicy})`);
			return;
		}
		const commandAllowFrom = isGroup ? groupConfig?.allowFrom ?? [] : effectiveDmAllowFrom;
		const senderAllowedForCommands = resolveFeishuAllowlistMatch({
			allowFrom: commandAllowFrom,
			senderId: ctx.senderOpenId,
			senderName: ctx.senderName
		}).allowed;
		const commandAuthorized = shouldComputeCommandAuthorized ? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
			useAccessGroups,
			authorizers: [{
				configured: commandAllowFrom.length > 0,
				allowed: senderAllowedForCommands
			}]
		}) : void 0;
		const feishuFrom = `feishu:${ctx.senderOpenId}`;
		const feishuTo = isGroup ? `chat:${ctx.chatId}` : `user:${ctx.senderOpenId}`;
		let peerId = isGroup ? ctx.chatId : ctx.senderOpenId;
		if (isGroup && ctx.rootId) {
			if ((resolveFeishuGroupConfig({
				cfg: feishuCfg,
				groupId: ctx.chatId
			})?.topicSessionMode ?? feishuCfg?.topicSessionMode ?? "disabled") === "enabled") {
				peerId = `${ctx.chatId}:topic:${ctx.rootId}`;
				log(`feishu[${account.accountId}]: topic session isolation enabled, peer=${peerId}`);
			}
		}
		let route = core.channel.routing.resolveAgentRoute({
			cfg,
			channel: "feishu",
			accountId: account.accountId,
			peer: {
				kind: isGroup ? "group" : "direct",
				id: peerId
			}
		});
		if (!isGroup && route.matchedBy === "default") {
			const dynamicCfg = feishuCfg?.dynamicAgentCreation;
			if (dynamicCfg?.enabled) {
				const result = await maybeCreateDynamicAgent({
					cfg,
					runtime: getFeishuRuntime(),
					senderOpenId: ctx.senderOpenId,
					dynamicCfg,
					log: (msg) => log(msg)
				});
				if (result.created) {
					result.updatedCfg;
					route = core.channel.routing.resolveAgentRoute({
						cfg: result.updatedCfg,
						channel: "feishu",
						accountId: account.accountId,
						peer: {
							kind: "direct",
							id: ctx.senderOpenId
						}
					});
					log(`feishu[${account.accountId}]: dynamic agent created, new route: ${route.sessionKey}`);
				}
			}
		}
		const preview = ctx.content.replace(/\s+/g, " ").slice(0, 160);
		const inboundLabel = isGroup ? `Feishu[${account.accountId}] message in group ${ctx.chatId}` : `Feishu[${account.accountId}] DM from ${ctx.senderOpenId}`;
		core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
			sessionKey: route.sessionKey,
			contextKey: `feishu:message:${ctx.chatId}:${ctx.messageId}`
		});
		const mediaMaxBytes = (feishuCfg?.mediaMaxMb ?? 30) * 1024 * 1024;
		const mediaPayload = buildAgentMediaPayload(await resolveFeishuMediaList({
			cfg,
			messageId: ctx.messageId,
			messageType: event.message.message_type,
			content: event.message.content,
			maxBytes: mediaMaxBytes,
			log,
			accountId: account.accountId
		}));
		let quotedContent;
		if (ctx.parentId) try {
			const quotedMsg = await getMessageFeishu({
				cfg,
				messageId: ctx.parentId,
				accountId: account.accountId
			});
			if (quotedMsg) {
				quotedContent = quotedMsg.content;
				log(`feishu[${account.accountId}]: fetched quoted message: ${quotedContent?.slice(0, 100)}`);
			}
		} catch (err) {
			log(`feishu[${account.accountId}]: failed to fetch quoted message: ${String(err)}`);
		}
		const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
		let messageBody = ctx.content;
		if (quotedContent) messageBody = `[Replying to: "${quotedContent}"]\n\n${ctx.content}`;
		messageBody = `${ctx.senderName ?? ctx.senderOpenId}: ${messageBody}`;
		if (ctx.mentionTargets && ctx.mentionTargets.length > 0) {
			const targetNames = ctx.mentionTargets.map((t) => t.name).join(", ");
			messageBody += `\n\n[System: Your reply will automatically @mention: ${targetNames}. Do not write @xxx yourself.]`;
		}
		const envelopeFrom = isGroup ? `${ctx.chatId}:${ctx.senderOpenId}` : ctx.senderOpenId;
		if (permissionErrorForAgent) {
			const permissionNotifyBody = `[System: The bot encountered a Feishu API permission error. Please inform the user about this issue and provide the permission grant URL for the admin to authorize. Permission grant URL: ${permissionErrorForAgent.grantUrl ?? ""}]`;
			const permissionBody = core.channel.reply.formatAgentEnvelope({
				channel: "Feishu",
				from: envelopeFrom,
				timestamp: /* @__PURE__ */ new Date(),
				envelope: envelopeOptions,
				body: permissionNotifyBody
			});
			const permissionCtx = core.channel.reply.finalizeInboundContext({
				Body: permissionBody,
				BodyForAgent: permissionNotifyBody,
				RawBody: permissionNotifyBody,
				CommandBody: permissionNotifyBody,
				From: feishuFrom,
				To: feishuTo,
				SessionKey: route.sessionKey,
				AccountId: route.accountId,
				ChatType: isGroup ? "group" : "direct",
				GroupSubject: isGroup ? ctx.chatId : void 0,
				SenderName: "system",
				SenderId: "system",
				Provider: "feishu",
				Surface: "feishu",
				MessageSid: `${ctx.messageId}:permission-error`,
				Timestamp: Date.now(),
				WasMentioned: false,
				CommandAuthorized: commandAuthorized,
				OriginatingChannel: "feishu",
				OriginatingTo: feishuTo
			});
			const { dispatcher: permDispatcher, replyOptions: permReplyOptions, markDispatchIdle: markPermIdle } = createFeishuReplyDispatcher({
				cfg,
				agentId: route.agentId,
				runtime,
				chatId: ctx.chatId,
				replyToMessageId: ctx.messageId,
				accountId: account.accountId
			});
			log(`feishu[${account.accountId}]: dispatching permission error notification to agent`);
			await core.channel.reply.dispatchReplyFromConfig({
				ctx: permissionCtx,
				cfg,
				dispatcher: permDispatcher,
				replyOptions: permReplyOptions
			});
			markPermIdle();
		}
		let combinedBody = core.channel.reply.formatAgentEnvelope({
			channel: "Feishu",
			from: envelopeFrom,
			timestamp: /* @__PURE__ */ new Date(),
			envelope: envelopeOptions,
			body: messageBody
		});
		const historyKey = isGroup ? ctx.chatId : void 0;
		if (isGroup && historyKey && chatHistories) combinedBody = buildPendingHistoryContextFromMap({
			historyMap: chatHistories,
			historyKey,
			limit: historyLimit,
			currentMessage: combinedBody,
			formatEntry: (entry) => core.channel.reply.formatAgentEnvelope({
				channel: "Feishu",
				from: `${ctx.chatId}:${entry.sender}`,
				timestamp: entry.timestamp,
				body: entry.body,
				envelope: envelopeOptions
			})
		});
		const inboundHistory = isGroup && historyKey && historyLimit > 0 && chatHistories ? (chatHistories.get(historyKey) ?? []).map((entry) => ({
			sender: entry.sender,
			body: entry.body,
			timestamp: entry.timestamp
		})) : void 0;
		const ctxPayload = core.channel.reply.finalizeInboundContext({
			Body: combinedBody,
			BodyForAgent: ctx.content,
			InboundHistory: inboundHistory,
			RawBody: ctx.content,
			CommandBody: ctx.content,
			From: feishuFrom,
			To: feishuTo,
			SessionKey: route.sessionKey,
			AccountId: route.accountId,
			ChatType: isGroup ? "group" : "direct",
			GroupSubject: isGroup ? ctx.chatId : void 0,
			SenderName: ctx.senderName ?? ctx.senderOpenId,
			SenderId: ctx.senderOpenId,
			Provider: "feishu",
			Surface: "feishu",
			MessageSid: ctx.messageId,
			ReplyToBody: quotedContent ?? void 0,
			Timestamp: Date.now(),
			WasMentioned: ctx.mentionedBot,
			CommandAuthorized: commandAuthorized,
			OriginatingChannel: "feishu",
			OriginatingTo: feishuTo,
			...mediaPayload
		});
		const { dispatcher, replyOptions, markDispatchIdle } = createFeishuReplyDispatcher({
			cfg,
			agentId: route.agentId,
			runtime,
			chatId: ctx.chatId,
			replyToMessageId: ctx.messageId,
			mentionTargets: ctx.mentionTargets,
			accountId: account.accountId
		});
		log(`feishu[${account.accountId}]: dispatching to agent (session=${route.sessionKey})`);
		const { queuedFinal, counts } = await core.channel.reply.dispatchReplyFromConfig({
			ctx: ctxPayload,
			cfg,
			dispatcher,
			replyOptions
		});
		markDispatchIdle();
		if (isGroup && historyKey && chatHistories) clearHistoryEntriesIfEnabled({
			historyMap: chatHistories,
			historyKey,
			limit: historyLimit
		});
		log(`feishu[${account.accountId}]: dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final})`);
	} catch (err) {
		error(`feishu[${account.accountId}]: failed to dispatch message: ${String(err)}`);
	}
}

//#endregion
//#region extensions/feishu/src/monitor.ts
var monitor_exports = /* @__PURE__ */ __exportAll({ monitorFeishuProvider: () => monitorFeishuProvider });
const wsClients = /* @__PURE__ */ new Map();
const httpServers = /* @__PURE__ */ new Map();
const botOpenIds = /* @__PURE__ */ new Map();
const FEISHU_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
const FEISHU_WEBHOOK_BODY_TIMEOUT_MS = 3e4;
async function fetchBotOpenId(account) {
	try {
		const result = await probeFeishu(account);
		return result.ok ? result.botOpenId : void 0;
	} catch {
		return;
	}
}
/**
* Register common event handlers on an EventDispatcher.
* When fireAndForget is true (webhook mode), message handling is not awaited
* to avoid blocking the HTTP response (Lark requires <3s response).
*/
function registerEventHandlers(eventDispatcher, context) {
	const { cfg, accountId, runtime, chatHistories, fireAndForget } = context;
	const log = runtime?.log ?? console.log;
	const error = runtime?.error ?? console.error;
	eventDispatcher.register({
		"im.message.receive_v1": async (data) => {
			try {
				const promise = handleFeishuMessage({
					cfg,
					event: data,
					botOpenId: botOpenIds.get(accountId),
					runtime,
					chatHistories,
					accountId
				});
				if (fireAndForget) promise.catch((err) => {
					error(`feishu[${accountId}]: error handling message: ${String(err)}`);
				});
				else await promise;
			} catch (err) {
				error(`feishu[${accountId}]: error handling message: ${String(err)}`);
			}
		},
		"im.message.message_read_v1": async () => {},
		"im.chat.member.bot.added_v1": async (data) => {
			try {
				log(`feishu[${accountId}]: bot added to chat ${data.chat_id}`);
			} catch (err) {
				error(`feishu[${accountId}]: error handling bot added event: ${String(err)}`);
			}
		},
		"im.chat.member.bot.deleted_v1": async (data) => {
			try {
				log(`feishu[${accountId}]: bot removed from chat ${data.chat_id}`);
			} catch (err) {
				error(`feishu[${accountId}]: error handling bot removed event: ${String(err)}`);
			}
		}
	});
}
/**
* Monitor a single Feishu account.
*/
async function monitorSingleAccount(params) {
	const { cfg, account, runtime, abortSignal } = params;
	const { accountId } = account;
	const log = runtime?.log ?? console.log;
	const botOpenId = await fetchBotOpenId(account);
	botOpenIds.set(accountId, botOpenId ?? "");
	log(`feishu[${accountId}]: bot open_id resolved: ${botOpenId ?? "unknown"}`);
	const connectionMode = account.config.connectionMode ?? "websocket";
	const eventDispatcher = createEventDispatcher(account);
	registerEventHandlers(eventDispatcher, {
		cfg,
		accountId,
		runtime,
		chatHistories: /* @__PURE__ */ new Map(),
		fireAndForget: connectionMode === "webhook"
	});
	if (connectionMode === "webhook") return monitorWebhook({
		params,
		accountId,
		eventDispatcher
	});
	return monitorWebSocket({
		params,
		accountId,
		eventDispatcher
	});
}
async function monitorWebSocket({ params, accountId, eventDispatcher }) {
	const { account, runtime, abortSignal } = params;
	const log = runtime?.log ?? console.log;
	runtime?.error ?? console.error;
	log(`feishu[${accountId}]: starting WebSocket connection...`);
	const wsClient = createFeishuWSClient(account);
	wsClients.set(accountId, wsClient);
	return new Promise((resolve, reject) => {
		const cleanup = () => {
			wsClients.delete(accountId);
			botOpenIds.delete(accountId);
		};
		const handleAbort = () => {
			log(`feishu[${accountId}]: abort signal received, stopping`);
			cleanup();
			resolve();
		};
		if (abortSignal?.aborted) {
			cleanup();
			resolve();
			return;
		}
		abortSignal?.addEventListener("abort", handleAbort, { once: true });
		try {
			wsClient.start({ eventDispatcher });
			log(`feishu[${accountId}]: WebSocket client started`);
		} catch (err) {
			cleanup();
			abortSignal?.removeEventListener("abort", handleAbort);
			reject(err);
		}
	});
}
async function monitorWebhook({ params, accountId, eventDispatcher }) {
	const { account, runtime, abortSignal } = params;
	const log = runtime?.log ?? console.log;
	const error = runtime?.error ?? console.error;
	const port = account.config.webhookPort ?? 3e3;
	const path = account.config.webhookPath ?? "/feishu/events";
	log(`feishu[${accountId}]: starting Webhook server on port ${port}, path ${path}...`);
	const server = http.createServer();
	const webhookHandler = Lark.adaptDefault(path, eventDispatcher, { autoChallenge: true });
	server.on("request", (req, res) => {
		const guard = installRequestBodyLimitGuard(req, res, {
			maxBytes: FEISHU_WEBHOOK_MAX_BODY_BYTES,
			timeoutMs: FEISHU_WEBHOOK_BODY_TIMEOUT_MS,
			responseFormat: "text"
		});
		if (guard.isTripped()) return;
		Promise.resolve(webhookHandler(req, res)).catch((err) => {
			if (!guard.isTripped()) error(`feishu[${accountId}]: webhook handler error: ${String(err)}`);
		}).finally(() => {
			guard.dispose();
		});
	});
	httpServers.set(accountId, server);
	return new Promise((resolve, reject) => {
		const cleanup = () => {
			server.close();
			httpServers.delete(accountId);
			botOpenIds.delete(accountId);
		};
		const handleAbort = () => {
			log(`feishu[${accountId}]: abort signal received, stopping Webhook server`);
			cleanup();
			resolve();
		};
		if (abortSignal?.aborted) {
			cleanup();
			resolve();
			return;
		}
		abortSignal?.addEventListener("abort", handleAbort, { once: true });
		server.listen(port, () => {
			log(`feishu[${accountId}]: Webhook server listening on port ${port}`);
		});
		server.on("error", (err) => {
			error(`feishu[${accountId}]: Webhook server error: ${err}`);
			abortSignal?.removeEventListener("abort", handleAbort);
			reject(err);
		});
	});
}
/**
* Main entry: start monitoring for all enabled accounts.
*/
async function monitorFeishuProvider(opts = {}) {
	const cfg = opts.config;
	if (!cfg) throw new Error("Config is required for Feishu monitor");
	const log = opts.runtime?.log ?? console.log;
	if (opts.accountId) {
		const account = resolveFeishuAccount({
			cfg,
			accountId: opts.accountId
		});
		if (!account.enabled || !account.configured) throw new Error(`Feishu account "${opts.accountId}" not configured or disabled`);
		return monitorSingleAccount({
			cfg,
			account,
			runtime: opts.runtime,
			abortSignal: opts.abortSignal
		});
	}
	const accounts = listEnabledFeishuAccounts(cfg);
	if (accounts.length === 0) throw new Error("No enabled Feishu accounts configured");
	log(`feishu: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(", ")}`);
	await Promise.all(accounts.map((account) => monitorSingleAccount({
		cfg,
		account,
		runtime: opts.runtime,
		abortSignal: opts.abortSignal
	})));
}

//#endregion
export { resolveDefaultFeishuAccountId as A, getFeishuRuntime as C, normalizeFeishuTarget as D, looksLikeFeishuId as E, resolveFeishuCredentials as M, createFeishuClient as N, listEnabledFeishuAccounts as O, uploadImageFeishu as S, probeFeishu as T, isMentionForwardRequest as _, getMessageFeishu as a, sendMediaFeishu as b, updateCardFeishu as c, extractMentionTargets as d, extractMessageBody as f, formatMentionForText as g, formatMentionForCard as h, editMessageFeishu as i, resolveFeishuAccount as j, listFeishuAccountIds as k, buildMentionedCardContent as l, formatMentionAllForText as m, monitor_exports as n, sendCardFeishu as o, formatMentionAllForCard as p, resolveFeishuGroupToolPolicy as r, sendMessageFeishu as s, monitorFeishuProvider as t, buildMentionedMessage as u, sendFileFeishu as v, setFeishuRuntime as w, uploadFileFeishu as x, sendImageFeishu as y };