import { BlockStreamingCoalesceSchema, DEFAULT_ACCOUNT_ID, DEFAULT_GROUP_HISTORY_LIMIT, DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, applyAccountNameToChannelSection, buildAgentMediaPayload, buildChannelConfigSchema, buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, createReplyPrefixOptions, createTypingCallbacks, deleteAccountFromConfigSection, emptyPluginConfigSchema, formatPairingApproveHint, logInboundDrop, logTypingFailure, migrateBaseNameToDefaultAccount, normalizeAccountId, promptAccountId, recordPendingHistoryEntryIfEnabled, requireOpenAllowFrom, resolveChannelMediaMaxBytes, resolveControlCommandGate, setAccountEnabledInConfigSection } from "minion/plugin-sdk";
import { z } from "zod";
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId as normalizeAccountId$1 } from "minion/plugin-sdk/account-id";
import { createDedupeCache, rawDataToString } from "openclaw/plugin-sdk";
import WebSocket from "ws";

//#region extensions/mattermost/src/config-schema.ts
const MattermostAccountSchemaBase = z.object({
	name: z.string().optional(),
	capabilities: z.array(z.string()).optional(),
	markdown: MarkdownConfigSchema,
	enabled: z.boolean().optional(),
	configWrites: z.boolean().optional(),
	botToken: z.string().optional(),
	baseUrl: z.string().optional(),
	chatmode: z.enum([
		"oncall",
		"onmessage",
		"onchar"
	]).optional(),
	oncharPrefixes: z.array(z.string()).optional(),
	requireMention: z.boolean().optional(),
	dmPolicy: DmPolicySchema.optional().default("pairing"),
	allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
	groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
	groupPolicy: GroupPolicySchema.optional().default("allowlist"),
	textChunkLimit: z.number().int().positive().optional(),
	chunkMode: z.enum(["length", "newline"]).optional(),
	blockStreaming: z.boolean().optional(),
	blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
	responsePrefix: z.string().optional(),
	actions: z.object({ reactions: z.boolean().optional() }).optional()
}).strict();
const MattermostAccountSchema = MattermostAccountSchemaBase.superRefine((value, ctx) => {
	requireOpenAllowFrom({
		policy: value.dmPolicy,
		allowFrom: value.allowFrom,
		ctx,
		path: ["allowFrom"],
		message: "channels.mattermost.dmPolicy=\"open\" requires channels.mattermost.allowFrom to include \"*\""
	});
});
const MattermostConfigSchema = MattermostAccountSchemaBase.extend({ accounts: z.record(z.string(), MattermostAccountSchema.optional()).optional() }).superRefine((value, ctx) => {
	requireOpenAllowFrom({
		policy: value.dmPolicy,
		allowFrom: value.allowFrom,
		ctx,
		path: ["allowFrom"],
		message: "channels.mattermost.dmPolicy=\"open\" requires channels.mattermost.allowFrom to include \"*\""
	});
});

//#endregion
//#region extensions/mattermost/src/mattermost/client.ts
function normalizeMattermostBaseUrl(raw) {
	const trimmed = raw?.trim();
	if (!trimmed) return;
	return trimmed.replace(/\/+$/, "").replace(/\/api\/v4$/i, "");
}
function buildMattermostApiUrl(baseUrl, path) {
	const normalized = normalizeMattermostBaseUrl(baseUrl);
	if (!normalized) throw new Error("Mattermost baseUrl is required");
	return `${normalized}/api/v4${path.startsWith("/") ? path : `/${path}`}`;
}
async function readMattermostError$1(res) {
	if ((res.headers.get("content-type") ?? "").includes("application/json")) {
		const data = await res.json();
		if (data?.message) return data.message;
		return JSON.stringify(data);
	}
	return await res.text();
}
function createMattermostClient(params) {
	const baseUrl = normalizeMattermostBaseUrl(params.baseUrl);
	if (!baseUrl) throw new Error("Mattermost baseUrl is required");
	const apiBaseUrl = `${baseUrl}/api/v4`;
	const token = params.botToken.trim();
	const fetchImpl = params.fetchImpl ?? fetch;
	const request = async (path, init) => {
		const url = buildMattermostApiUrl(baseUrl, path);
		const headers = new Headers(init?.headers);
		headers.set("Authorization", `Bearer ${token}`);
		if (typeof init?.body === "string" && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
		const res = await fetchImpl(url, {
			...init,
			headers
		});
		if (!res.ok) {
			const detail = await readMattermostError$1(res);
			throw new Error(`Mattermost API ${res.status} ${res.statusText}: ${detail || "unknown error"}`);
		}
		if (res.status === 204) return;
		if ((res.headers.get("content-type") ?? "").includes("application/json")) return await res.json();
		return await res.text();
	};
	return {
		baseUrl,
		apiBaseUrl,
		token,
		request
	};
}
async function fetchMattermostMe(client) {
	return await client.request("/users/me");
}
async function fetchMattermostUser(client, userId) {
	return await client.request(`/users/${userId}`);
}
async function fetchMattermostUserByUsername(client, username) {
	return await client.request(`/users/username/${encodeURIComponent(username)}`);
}
async function fetchMattermostChannel(client, channelId) {
	return await client.request(`/channels/${channelId}`);
}
async function sendMattermostTyping(client, params) {
	const payload = { channel_id: params.channelId };
	const parentId = params.parentId?.trim();
	if (parentId) payload.parent_id = parentId;
	await client.request("/users/me/typing", {
		method: "POST",
		body: JSON.stringify(payload)
	});
}
async function createMattermostDirectChannel(client, userIds) {
	return await client.request("/channels/direct", {
		method: "POST",
		body: JSON.stringify(userIds)
	});
}
async function createMattermostPost(client, params) {
	const payload = {
		channel_id: params.channelId,
		message: params.message
	};
	if (params.rootId) payload.root_id = params.rootId;
	if (params.fileIds?.length) payload.file_ids = params.fileIds;
	return await client.request("/posts", {
		method: "POST",
		body: JSON.stringify(payload)
	});
}
async function uploadMattermostFile(client, params) {
	const form = new FormData();
	const fileName = params.fileName?.trim() || "upload";
	const bytes = Uint8Array.from(params.buffer);
	const blob = params.contentType ? new Blob([bytes], { type: params.contentType }) : new Blob([bytes]);
	form.append("files", blob, fileName);
	form.append("channel_id", params.channelId);
	const res = await fetch(`${client.apiBaseUrl}/files`, {
		method: "POST",
		headers: { Authorization: `Bearer ${client.token}` },
		body: form
	});
	if (!res.ok) {
		const detail = await readMattermostError$1(res);
		throw new Error(`Mattermost API ${res.status} ${res.statusText}: ${detail || "unknown error"}`);
	}
	const info = (await res.json()).file_infos?.[0];
	if (!info?.id) throw new Error("Mattermost file upload failed");
	return info;
}

//#endregion
//#region extensions/mattermost/src/mattermost/accounts.ts
function listConfiguredAccountIds(cfg) {
	const accounts = cfg.channels?.mattermost?.accounts;
	if (!accounts || typeof accounts !== "object") return [];
	return Object.keys(accounts).filter(Boolean);
}
function listMattermostAccountIds(cfg) {
	const ids = listConfiguredAccountIds(cfg);
	if (ids.length === 0) return [DEFAULT_ACCOUNT_ID$1];
	return ids.toSorted((a, b) => a.localeCompare(b));
}
function resolveDefaultMattermostAccountId(cfg) {
	const ids = listMattermostAccountIds(cfg);
	if (ids.includes(DEFAULT_ACCOUNT_ID$1)) return DEFAULT_ACCOUNT_ID$1;
	return ids[0] ?? DEFAULT_ACCOUNT_ID$1;
}
function resolveAccountConfig(cfg, accountId) {
	const accounts = cfg.channels?.mattermost?.accounts;
	if (!accounts || typeof accounts !== "object") return;
	return accounts[accountId];
}
function mergeMattermostAccountConfig(cfg, accountId) {
	const { accounts: _ignored, ...base } = cfg.channels?.mattermost ?? {};
	const account = resolveAccountConfig(cfg, accountId) ?? {};
	return {
		...base,
		...account
	};
}
function resolveMattermostRequireMention(config) {
	if (config.chatmode === "oncall") return true;
	if (config.chatmode === "onmessage") return false;
	if (config.chatmode === "onchar") return true;
	return config.requireMention;
}
function resolveMattermostAccount(params) {
	const accountId = normalizeAccountId$1(params.accountId);
	const baseEnabled = params.cfg.channels?.mattermost?.enabled !== false;
	const merged = mergeMattermostAccountConfig(params.cfg, accountId);
	const accountEnabled = merged.enabled !== false;
	const enabled = baseEnabled && accountEnabled;
	const allowEnv = accountId === DEFAULT_ACCOUNT_ID$1;
	const envToken = allowEnv ? process.env.MATTERMOST_BOT_TOKEN?.trim() : void 0;
	const envUrl = allowEnv ? process.env.MATTERMOST_URL?.trim() : void 0;
	const configToken = merged.botToken?.trim();
	const configUrl = merged.baseUrl?.trim();
	const botToken = configToken || envToken;
	const baseUrl = normalizeMattermostBaseUrl(configUrl || envUrl);
	const requireMention = resolveMattermostRequireMention(merged);
	const botTokenSource = configToken ? "config" : envToken ? "env" : "none";
	const baseUrlSource = configUrl ? "config" : envUrl ? "env" : "none";
	return {
		accountId,
		enabled,
		name: merged.name?.trim() || void 0,
		botToken,
		baseUrl,
		botTokenSource,
		baseUrlSource,
		config: merged,
		chatmode: merged.chatmode,
		oncharPrefixes: merged.oncharPrefixes,
		requireMention,
		textChunkLimit: merged.textChunkLimit,
		blockStreaming: merged.blockStreaming,
		blockStreamingCoalesce: merged.blockStreamingCoalesce
	};
}

//#endregion
//#region extensions/mattermost/src/group-mentions.ts
function resolveMattermostGroupRequireMention(params) {
	const account = resolveMattermostAccount({
		cfg: params.cfg,
		accountId: params.accountId
	});
	if (typeof account.requireMention === "boolean") return account.requireMention;
	return true;
}

//#endregion
//#region extensions/mattermost/src/runtime.ts
let runtime = null;
function setMattermostRuntime(next) {
	runtime = next;
}
function getMattermostRuntime() {
	if (!runtime) throw new Error("Mattermost runtime not initialized");
	return runtime;
}

//#endregion
//#region extensions/mattermost/src/mattermost/monitor-helpers.ts
function formatInboundFromLabel(params) {
	if (params.isGroup) {
		const label = params.groupLabel?.trim() || params.groupFallback || "Group";
		const id = params.groupId?.trim();
		return id ? `${label} id:${id}` : label;
	}
	const directLabel = params.directLabel.trim();
	const directId = params.directId?.trim();
	if (!directId || directId === directLabel) return directLabel;
	return `${directLabel} id:${directId}`;
}
function resolveThreadSessionKeys(params) {
	const threadId = (params.threadId ?? "").trim();
	if (!threadId) return {
		sessionKey: params.baseSessionKey,
		parentSessionKey: void 0
	};
	return {
		sessionKey: params.useSuffix ?? true ? `${params.baseSessionKey}:thread:${threadId}` : params.baseSessionKey,
		parentSessionKey: params.parentSessionKey
	};
}

//#endregion
//#region extensions/mattermost/src/mattermost/monitor-onchar.ts
const DEFAULT_ONCHAR_PREFIXES = [">", "!"];
function resolveOncharPrefixes(prefixes) {
	const cleaned = prefixes?.map((entry) => entry.trim()).filter(Boolean) ?? DEFAULT_ONCHAR_PREFIXES;
	return cleaned.length > 0 ? cleaned : DEFAULT_ONCHAR_PREFIXES;
}
function stripOncharPrefix(text, prefixes) {
	const trimmed = text.trimStart();
	for (const prefix of prefixes) {
		if (!prefix) continue;
		if (trimmed.startsWith(prefix)) return {
			triggered: true,
			stripped: trimmed.slice(prefix.length).trimStart()
		};
	}
	return {
		triggered: false,
		stripped: text
	};
}

//#endregion
//#region extensions/mattermost/src/mattermost/monitor-websocket.ts
var WebSocketClosedBeforeOpenError = class extends Error {
	constructor(code, reason) {
		super(`websocket closed before open (code ${code})`);
		this.code = code;
		this.reason = reason;
		this.name = "WebSocketClosedBeforeOpenError";
	}
};
const defaultMattermostWebSocketFactory = (url) => new WebSocket(url);
function parsePostedPayload(payload) {
	if (payload.event !== "posted") return null;
	const postData = payload.data?.post;
	if (!postData) return null;
	let post = null;
	if (typeof postData === "string") try {
		post = JSON.parse(postData);
	} catch {
		return null;
	}
	else if (typeof postData === "object") post = postData;
	if (!post) return null;
	return {
		payload,
		post
	};
}
function createMattermostConnectOnce(opts) {
	const webSocketFactory = opts.webSocketFactory ?? defaultMattermostWebSocketFactory;
	return async () => {
		const ws = webSocketFactory(opts.wsUrl);
		const onAbort = () => ws.terminate();
		opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
		try {
			return await new Promise((resolve, reject) => {
				let opened = false;
				let settled = false;
				const resolveOnce = () => {
					if (settled) return;
					settled = true;
					resolve();
				};
				const rejectOnce = (error) => {
					if (settled) return;
					settled = true;
					reject(error);
				};
				ws.on("open", () => {
					opened = true;
					opts.statusSink?.({
						connected: true,
						lastConnectedAt: Date.now(),
						lastError: null
					});
					ws.send(JSON.stringify({
						seq: opts.nextSeq(),
						action: "authentication_challenge",
						data: { token: opts.botToken }
					}));
				});
				ws.on("message", async (data) => {
					const raw = rawDataToString(data);
					let payload;
					try {
						payload = JSON.parse(raw);
					} catch {
						return;
					}
					if (payload.event === "reaction_added" || payload.event === "reaction_removed") {
						if (!opts.onReaction) return;
						try {
							await opts.onReaction(payload);
						} catch (err) {
							opts.runtime.error?.(`mattermost reaction handler failed: ${String(err)}`);
						}
						return;
					}
					if (payload.event !== "posted") return;
					const parsed = parsePostedPayload(payload);
					if (!parsed) return;
					try {
						await opts.onPosted(parsed.post, parsed.payload);
					} catch (err) {
						opts.runtime.error?.(`mattermost handler failed: ${String(err)}`);
					}
				});
				ws.on("close", (code, reason) => {
					const message = reasonToString(reason);
					opts.statusSink?.({
						connected: false,
						lastDisconnect: {
							at: Date.now(),
							status: code,
							error: message || void 0
						}
					});
					if (opened) {
						resolveOnce();
						return;
					}
					rejectOnce(new WebSocketClosedBeforeOpenError(code, message || void 0));
				});
				ws.on("error", (err) => {
					opts.runtime.error?.(`mattermost websocket error: ${String(err)}`);
					opts.statusSink?.({ lastError: String(err) });
					try {
						ws.close();
					} catch {}
				});
			});
		} finally {
			opts.abortSignal?.removeEventListener("abort", onAbort);
		}
	};
}
function reasonToString(reason) {
	if (!reason) return "";
	if (typeof reason === "string") return reason;
	return reason.length > 0 ? reason.toString("utf8") : "";
}

//#endregion
//#region extensions/mattermost/src/mattermost/reconnect.ts
/**
* Reconnection loop with exponential backoff.
*
* Calls `connectFn` in a while loop. On normal resolve (connection closed),
* the backoff resets. On thrown error (connection failed), the current delay is
* used, then doubled for the next retry.
* The loop exits when `abortSignal` fires.
*/
async function runWithReconnect(connectFn, opts = {}) {
	const { initialDelayMs = 2e3, maxDelayMs = 6e4 } = opts;
	const jitterRatio = Math.max(0, opts.jitterRatio ?? 0);
	const random = opts.random ?? Math.random;
	let retryDelay = initialDelayMs;
	let attempt = 0;
	while (!opts.abortSignal?.aborted) {
		let shouldIncreaseDelay = false;
		let outcome = "resolved";
		let error;
		try {
			await connectFn();
			retryDelay = initialDelayMs;
		} catch (err) {
			if (opts.abortSignal?.aborted) return;
			outcome = "rejected";
			error = err;
			opts.onError?.(err);
			shouldIncreaseDelay = true;
		}
		if (opts.abortSignal?.aborted) return;
		const delayMs = withJitter(retryDelay, jitterRatio, random);
		if (!(opts.shouldReconnect?.({
			attempt,
			delayMs,
			outcome,
			error
		}) ?? true)) return;
		opts.onReconnect?.(delayMs);
		await sleepAbortable(delayMs, opts.abortSignal);
		if (shouldIncreaseDelay) retryDelay = Math.min(retryDelay * 2, maxDelayMs);
		attempt++;
	}
}
function withJitter(baseMs, jitterRatio, random) {
	if (jitterRatio <= 0) return baseMs;
	const normalized = Math.max(0, Math.min(1, random()));
	const spread = baseMs * jitterRatio;
	return Math.max(1, Math.round(baseMs - spread + normalized * spread * 2));
}
function sleepAbortable(ms, signal) {
	return new Promise((resolve) => {
		if (signal?.aborted) {
			resolve();
			return;
		}
		const onAbort = () => {
			clearTimeout(timer);
			resolve();
		};
		const timer = setTimeout(() => {
			signal?.removeEventListener("abort", onAbort);
			resolve();
		}, ms);
		signal?.addEventListener("abort", onAbort, { once: true });
	});
}

//#endregion
//#region extensions/mattermost/src/mattermost/send.ts
const botUserCache = /* @__PURE__ */ new Map();
const userByNameCache = /* @__PURE__ */ new Map();
const getCore = () => getMattermostRuntime();
function cacheKey(baseUrl, token) {
	return `${baseUrl}::${token}`;
}
function normalizeMessage(text, mediaUrl) {
	return [text.trim(), mediaUrl?.trim()].filter(Boolean).join("\n");
}
function isHttpUrl(value) {
	return /^https?:\/\//i.test(value);
}
function parseMattermostTarget(raw) {
	const trimmed = raw.trim();
	if (!trimmed) throw new Error("Recipient is required for Mattermost sends");
	const lower = trimmed.toLowerCase();
	if (lower.startsWith("channel:")) {
		const id = trimmed.slice(8).trim();
		if (!id) throw new Error("Channel id is required for Mattermost sends");
		return {
			kind: "channel",
			id
		};
	}
	if (lower.startsWith("user:")) {
		const id = trimmed.slice(5).trim();
		if (!id) throw new Error("User id is required for Mattermost sends");
		return {
			kind: "user",
			id
		};
	}
	if (lower.startsWith("mattermost:")) {
		const id = trimmed.slice(11).trim();
		if (!id) throw new Error("User id is required for Mattermost sends");
		return {
			kind: "user",
			id
		};
	}
	if (trimmed.startsWith("@")) {
		const username = trimmed.slice(1).trim();
		if (!username) throw new Error("Username is required for Mattermost sends");
		return {
			kind: "user",
			username
		};
	}
	return {
		kind: "channel",
		id: trimmed
	};
}
async function resolveBotUser(baseUrl, token) {
	const key = cacheKey(baseUrl, token);
	const cached = botUserCache.get(key);
	if (cached) return cached;
	const user = await fetchMattermostMe(createMattermostClient({
		baseUrl,
		botToken: token
	}));
	botUserCache.set(key, user);
	return user;
}
async function resolveUserIdByUsername(params) {
	const { baseUrl, token, username } = params;
	const key = `${cacheKey(baseUrl, token)}::${username.toLowerCase()}`;
	const cached = userByNameCache.get(key);
	if (cached?.id) return cached.id;
	const user = await fetchMattermostUserByUsername(createMattermostClient({
		baseUrl,
		botToken: token
	}), username);
	userByNameCache.set(key, user);
	return user.id;
}
async function resolveTargetChannelId(params) {
	if (params.target.kind === "channel") return params.target.id;
	const userId = params.target.id ? params.target.id : await resolveUserIdByUsername({
		baseUrl: params.baseUrl,
		token: params.token,
		username: params.target.username ?? ""
	});
	const botUser = await resolveBotUser(params.baseUrl, params.token);
	return (await createMattermostDirectChannel(createMattermostClient({
		baseUrl: params.baseUrl,
		botToken: params.token
	}), [botUser.id, userId])).id;
}
async function sendMessageMattermost(to, text, opts = {}) {
	const core = getCore();
	const logger = core.logging.getChildLogger({ module: "mattermost" });
	const cfg = core.config.loadConfig();
	const account = resolveMattermostAccount({
		cfg,
		accountId: opts.accountId
	});
	const token = opts.botToken?.trim() || account.botToken?.trim();
	if (!token) throw new Error(`Mattermost bot token missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.botToken or MATTERMOST_BOT_TOKEN for default).`);
	const baseUrl = normalizeMattermostBaseUrl(opts.baseUrl ?? account.baseUrl);
	if (!baseUrl) throw new Error(`Mattermost baseUrl missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.baseUrl or MATTERMOST_URL for default).`);
	const channelId = await resolveTargetChannelId({
		target: parseMattermostTarget(to),
		baseUrl,
		token
	});
	const client = createMattermostClient({
		baseUrl,
		botToken: token
	});
	let message = text?.trim() ?? "";
	let fileIds;
	let uploadError;
	const mediaUrl = opts.mediaUrl?.trim();
	if (mediaUrl) try {
		const media = await core.media.loadWebMedia(mediaUrl);
		fileIds = [(await uploadMattermostFile(client, {
			channelId,
			buffer: media.buffer,
			fileName: media.fileName ?? "upload",
			contentType: media.contentType ?? void 0
		})).id];
	} catch (err) {
		uploadError = err instanceof Error ? err : new Error(String(err));
		if (core.logging.shouldLogVerbose()) logger.debug?.(`mattermost send: media upload failed, falling back to URL text: ${String(err)}`);
		message = normalizeMessage(message, isHttpUrl(mediaUrl) ? mediaUrl : "");
	}
	if (message) {
		const tableMode = core.channel.text.resolveMarkdownTableMode({
			cfg,
			channel: "mattermost",
			accountId: account.accountId
		});
		message = core.channel.text.convertMarkdownTables(message, tableMode);
	}
	if (!message && (!fileIds || fileIds.length === 0)) {
		if (uploadError) throw new Error(`Mattermost media upload failed: ${uploadError.message}`);
		throw new Error("Mattermost message is empty");
	}
	const post = await createMattermostPost(client, {
		channelId,
		message,
		rootId: opts.replyToId,
		fileIds
	});
	core.channel.activity.record({
		channel: "mattermost",
		accountId: account.accountId,
		direction: "outbound"
	});
	return {
		messageId: post.id ?? "unknown",
		channelId
	};
}

//#endregion
//#region extensions/mattermost/src/mattermost/monitor.ts
const RECENT_MATTERMOST_MESSAGE_TTL_MS = 5 * 6e4;
const RECENT_MATTERMOST_MESSAGE_MAX = 2e3;
const CHANNEL_CACHE_TTL_MS = 5 * 6e4;
const USER_CACHE_TTL_MS = 10 * 6e4;
const recentInboundMessages = createDedupeCache({
	ttlMs: RECENT_MATTERMOST_MESSAGE_TTL_MS,
	maxSize: RECENT_MATTERMOST_MESSAGE_MAX
});
function resolveRuntime(opts) {
	return opts.runtime ?? {
		log: console.log,
		error: console.error,
		exit: (code) => {
			throw new Error(`exit ${code}`);
		}
	};
}
function normalizeMention(text, mention) {
	if (!mention) return text.trim();
	const escaped = mention.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
	const re = new RegExp(`@${escaped}\\b`, "gi");
	return text.replace(re, " ").replace(/\s+/g, " ").trim();
}
function isSystemPost(post) {
	const type = post.type?.trim();
	return Boolean(type);
}
function channelKind(channelType) {
	if (!channelType) return "channel";
	const normalized = channelType.trim().toUpperCase();
	if (normalized === "D") return "direct";
	if (normalized === "G") return "group";
	return "channel";
}
function channelChatType(kind) {
	if (kind === "direct") return "direct";
	if (kind === "group") return "group";
	return "channel";
}
function normalizeAllowEntry$1(entry) {
	const trimmed = entry.trim();
	if (!trimmed) return "";
	if (trimmed === "*") return "*";
	return trimmed.replace(/^(mattermost|user):/i, "").replace(/^@/, "").toLowerCase();
}
function normalizeAllowList(entries) {
	const normalized = entries.map((entry) => normalizeAllowEntry$1(String(entry))).filter(Boolean);
	return Array.from(new Set(normalized));
}
function isSenderAllowed(params) {
	const allowFrom = params.allowFrom;
	if (allowFrom.length === 0) return false;
	if (allowFrom.includes("*")) return true;
	const normalizedSenderId = normalizeAllowEntry$1(params.senderId);
	const normalizedSenderName = params.senderName ? normalizeAllowEntry$1(params.senderName) : "";
	return allowFrom.some((entry) => entry === normalizedSenderId || normalizedSenderName && entry === normalizedSenderName);
}
function buildMattermostAttachmentPlaceholder(mediaList) {
	if (mediaList.length === 0) return "";
	if (mediaList.length === 1) return `<media:${mediaList[0].kind === "unknown" ? "document" : mediaList[0].kind}>`;
	const allImages = mediaList.every((media) => media.kind === "image");
	const label = allImages ? "image" : "file";
	const suffix = mediaList.length === 1 ? label : `${label}s`;
	return `${allImages ? "<media:image>" : "<media:document>"} (${mediaList.length} ${suffix})`;
}
function buildMattermostWsUrl(baseUrl) {
	const normalized = normalizeMattermostBaseUrl(baseUrl);
	if (!normalized) throw new Error("Mattermost baseUrl is required");
	return `${normalized.replace(/^http/i, "ws")}/api/v4/websocket`;
}
async function monitorMattermostProvider(opts = {}) {
	const core = getMattermostRuntime();
	const runtime = resolveRuntime(opts);
	const cfg = opts.config ?? core.config.loadConfig();
	const account = resolveMattermostAccount({
		cfg,
		accountId: opts.accountId
	});
	const botToken = opts.botToken?.trim() || account.botToken?.trim();
	if (!botToken) throw new Error(`Mattermost bot token missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.botToken or MATTERMOST_BOT_TOKEN for default).`);
	const baseUrl = normalizeMattermostBaseUrl(opts.baseUrl ?? account.baseUrl);
	if (!baseUrl) throw new Error(`Mattermost baseUrl missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.baseUrl or MATTERMOST_URL for default).`);
	const client = createMattermostClient({
		baseUrl,
		botToken
	});
	const botUser = await fetchMattermostMe(client);
	const botUserId = botUser.id;
	const botUsername = botUser.username?.trim() || void 0;
	runtime.log?.(`mattermost connected as ${botUsername ? `@${botUsername}` : botUserId}`);
	const channelCache = /* @__PURE__ */ new Map();
	const userCache = /* @__PURE__ */ new Map();
	const logger = core.logging.getChildLogger({ module: "mattermost" });
	const logVerboseMessage = (message) => {
		if (!core.logging.shouldLogVerbose()) return;
		logger.debug?.(message);
	};
	const mediaMaxBytes = resolveChannelMediaMaxBytes({
		cfg,
		resolveChannelLimitMb: () => void 0,
		accountId: account.accountId
	}) ?? 8 * 1024 * 1024;
	const historyLimit = Math.max(0, cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT);
	const channelHistories = /* @__PURE__ */ new Map();
	const fetchWithAuth = (input, init) => {
		const headers = new Headers(init?.headers);
		headers.set("Authorization", `Bearer ${client.token}`);
		return fetch(input, {
			...init,
			headers
		});
	};
	const resolveMattermostMedia = async (fileIds) => {
		const ids = (fileIds ?? []).map((id) => id?.trim()).filter(Boolean);
		if (ids.length === 0) return [];
		const out = [];
		for (const fileId of ids) try {
			const fetched = await core.channel.media.fetchRemoteMedia({
				url: `${client.apiBaseUrl}/files/${fileId}`,
				fetchImpl: fetchWithAuth,
				filePathHint: fileId,
				maxBytes: mediaMaxBytes
			});
			const saved = await core.channel.media.saveMediaBuffer(fetched.buffer, fetched.contentType ?? void 0, "inbound", mediaMaxBytes);
			const contentType = saved.contentType ?? fetched.contentType ?? void 0;
			out.push({
				path: saved.path,
				contentType,
				kind: core.media.mediaKindFromMime(contentType)
			});
		} catch (err) {
			logger.debug?.(`mattermost: failed to download file ${fileId}: ${String(err)}`);
		}
		return out;
	};
	const sendTypingIndicator = async (channelId, parentId) => {
		await sendMattermostTyping(client, {
			channelId,
			parentId
		});
	};
	const resolveChannelInfo = async (channelId) => {
		const cached = channelCache.get(channelId);
		if (cached && cached.expiresAt > Date.now()) return cached.value;
		try {
			const info = await fetchMattermostChannel(client, channelId);
			channelCache.set(channelId, {
				value: info,
				expiresAt: Date.now() + CHANNEL_CACHE_TTL_MS
			});
			return info;
		} catch (err) {
			logger.debug?.(`mattermost: channel lookup failed: ${String(err)}`);
			channelCache.set(channelId, {
				value: null,
				expiresAt: Date.now() + CHANNEL_CACHE_TTL_MS
			});
			return null;
		}
	};
	const resolveUserInfo = async (userId) => {
		const cached = userCache.get(userId);
		if (cached && cached.expiresAt > Date.now()) return cached.value;
		try {
			const info = await fetchMattermostUser(client, userId);
			userCache.set(userId, {
				value: info,
				expiresAt: Date.now() + USER_CACHE_TTL_MS
			});
			return info;
		} catch (err) {
			logger.debug?.(`mattermost: user lookup failed: ${String(err)}`);
			userCache.set(userId, {
				value: null,
				expiresAt: Date.now() + USER_CACHE_TTL_MS
			});
			return null;
		}
	};
	const handlePost = async (post, payload, messageIds) => {
		const channelId = post.channel_id ?? payload.data?.channel_id ?? payload.broadcast?.channel_id;
		if (!channelId) return;
		const allMessageIds = messageIds?.length ? messageIds : post.id ? [post.id] : [];
		if (allMessageIds.length === 0) return;
		const dedupeEntries = allMessageIds.map((id) => recentInboundMessages.check(`${account.accountId}:${id}`));
		if (dedupeEntries.length > 0 && dedupeEntries.every(Boolean)) return;
		const senderId = post.user_id ?? payload.broadcast?.user_id;
		if (!senderId) return;
		if (senderId === botUserId) return;
		if (isSystemPost(post)) return;
		const channelInfo = await resolveChannelInfo(channelId);
		const kind = channelKind(payload.data?.channel_type ?? channelInfo?.type ?? void 0);
		const chatType = channelChatType(kind);
		const senderName = payload.data?.sender_name?.trim() || (await resolveUserInfo(senderId))?.username?.trim() || senderId;
		const rawText = post.message?.trim() || "";
		const dmPolicy = account.config.dmPolicy ?? "pairing";
		const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
		const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
		const configAllowFrom = normalizeAllowList(account.config.allowFrom ?? []);
		const configGroupAllowFrom = normalizeAllowList(account.config.groupAllowFrom ?? []);
		const storeAllowFrom = normalizeAllowList(await core.channel.pairing.readAllowFromStore("mattermost").catch(() => []));
		const effectiveAllowFrom = Array.from(new Set([...configAllowFrom, ...storeAllowFrom]));
		const effectiveGroupAllowFrom = Array.from(new Set([...configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom, ...storeAllowFrom]));
		const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
			cfg,
			surface: "mattermost"
		});
		const hasControlCommand = core.channel.text.hasControlCommand(rawText, cfg);
		const isControlCommand = allowTextCommands && hasControlCommand;
		const useAccessGroups = cfg.commands?.useAccessGroups !== false;
		const senderAllowedForCommands = isSenderAllowed({
			senderId,
			senderName,
			allowFrom: effectiveAllowFrom
		});
		const groupAllowedForCommands = isSenderAllowed({
			senderId,
			senderName,
			allowFrom: effectiveGroupAllowFrom
		});
		const commandGate = resolveControlCommandGate({
			useAccessGroups,
			authorizers: [{
				configured: effectiveAllowFrom.length > 0,
				allowed: senderAllowedForCommands
			}, {
				configured: effectiveGroupAllowFrom.length > 0,
				allowed: groupAllowedForCommands
			}],
			allowTextCommands,
			hasControlCommand
		});
		const commandAuthorized = kind === "direct" ? dmPolicy === "open" || senderAllowedForCommands : commandGate.commandAuthorized;
		if (kind === "direct") {
			if (dmPolicy === "disabled") {
				logVerboseMessage(`mattermost: drop dm (dmPolicy=disabled sender=${senderId})`);
				return;
			}
			if (dmPolicy !== "open" && !senderAllowedForCommands) {
				if (dmPolicy === "pairing") {
					const { code, created } = await core.channel.pairing.upsertPairingRequest({
						channel: "mattermost",
						id: senderId,
						meta: { name: senderName }
					});
					logVerboseMessage(`mattermost: pairing request sender=${senderId} created=${created}`);
					if (created) try {
						await sendMessageMattermost(`user:${senderId}`, core.channel.pairing.buildPairingReply({
							channel: "mattermost",
							idLine: `Your Mattermost user id: ${senderId}`,
							code
						}), { accountId: account.accountId });
						opts.statusSink?.({ lastOutboundAt: Date.now() });
					} catch (err) {
						logVerboseMessage(`mattermost: pairing reply failed for ${senderId}: ${String(err)}`);
					}
				} else logVerboseMessage(`mattermost: drop dm sender=${senderId} (dmPolicy=${dmPolicy})`);
				return;
			}
		} else {
			if (groupPolicy === "disabled") {
				logVerboseMessage("mattermost: drop group message (groupPolicy=disabled)");
				return;
			}
			if (groupPolicy === "allowlist") {
				if (effectiveGroupAllowFrom.length === 0) {
					logVerboseMessage("mattermost: drop group message (no group allowlist)");
					return;
				}
				if (!groupAllowedForCommands) {
					logVerboseMessage(`mattermost: drop group sender=${senderId} (not in groupAllowFrom)`);
					return;
				}
			}
		}
		if (kind !== "direct" && commandGate.shouldBlock) {
			logInboundDrop({
				log: logVerboseMessage,
				channel: "mattermost",
				reason: "control command (unauthorized)",
				target: senderId
			});
			return;
		}
		const teamId = payload.data?.team_id ?? channelInfo?.team_id ?? void 0;
		const channelName = payload.data?.channel_name ?? channelInfo?.name ?? "";
		const channelDisplay = payload.data?.channel_display_name ?? channelInfo?.display_name ?? channelName;
		const roomLabel = channelName ? `#${channelName}` : channelDisplay || `#${channelId}`;
		const route = core.channel.routing.resolveAgentRoute({
			cfg,
			channel: "mattermost",
			accountId: account.accountId,
			teamId,
			peer: {
				kind,
				id: kind === "direct" ? senderId : channelId
			}
		});
		const baseSessionKey = route.sessionKey;
		const threadRootId = post.root_id?.trim() || void 0;
		const threadKeys = resolveThreadSessionKeys({
			baseSessionKey,
			threadId: threadRootId,
			parentSessionKey: threadRootId ? baseSessionKey : void 0
		});
		const sessionKey = threadKeys.sessionKey;
		const historyKey = kind === "direct" ? null : sessionKey;
		const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, route.agentId);
		const wasMentioned = kind !== "direct" && ((botUsername ? rawText.toLowerCase().includes(`@${botUsername.toLowerCase()}`) : false) || core.channel.mentions.matchesMentionPatterns(rawText, mentionRegexes));
		const pendingBody = rawText || (post.file_ids?.length ? `[Mattermost ${post.file_ids.length === 1 ? "file" : "files"}]` : "");
		const pendingSender = senderName;
		const recordPendingHistory = () => {
			const trimmed = pendingBody.trim();
			recordPendingHistoryEntryIfEnabled({
				historyMap: channelHistories,
				limit: historyLimit,
				historyKey: historyKey ?? "",
				entry: historyKey && trimmed ? {
					sender: pendingSender,
					body: trimmed,
					timestamp: typeof post.create_at === "number" ? post.create_at : void 0,
					messageId: post.id ?? void 0
				} : null
			});
		};
		const oncharEnabled = account.chatmode === "onchar" && kind !== "direct";
		const oncharPrefixes = oncharEnabled ? resolveOncharPrefixes(account.oncharPrefixes) : [];
		const oncharResult = oncharEnabled ? stripOncharPrefix(rawText, oncharPrefixes) : {
			triggered: false,
			stripped: rawText
		};
		const oncharTriggered = oncharResult.triggered;
		const shouldRequireMention = kind !== "direct" && core.channel.groups.resolveRequireMention({
			cfg,
			channel: "mattermost",
			accountId: account.accountId,
			groupId: channelId
		});
		const effectiveWasMentioned = wasMentioned || isControlCommand && shouldRequireMention && !wasMentioned && commandAuthorized || oncharTriggered;
		const canDetectMention = Boolean(botUsername) || mentionRegexes.length > 0;
		if (oncharEnabled && !oncharTriggered && !wasMentioned && !isControlCommand) {
			recordPendingHistory();
			return;
		}
		if (kind !== "direct" && shouldRequireMention && canDetectMention) {
			if (!effectiveWasMentioned) {
				recordPendingHistory();
				return;
			}
		}
		const mediaList = await resolveMattermostMedia(post.file_ids);
		const mediaPlaceholder = buildMattermostAttachmentPlaceholder(mediaList);
		const bodyText = normalizeMention([oncharTriggered ? oncharResult.stripped : rawText, mediaPlaceholder].filter(Boolean).join("\n").trim(), botUsername);
		if (!bodyText) return;
		core.channel.activity.record({
			channel: "mattermost",
			accountId: account.accountId,
			direction: "inbound"
		});
		const fromLabel = formatInboundFromLabel({
			isGroup: kind !== "direct",
			groupLabel: channelDisplay || roomLabel,
			groupId: channelId,
			groupFallback: roomLabel || "Channel",
			directLabel: senderName,
			directId: senderId
		});
		const preview = bodyText.replace(/\s+/g, " ").slice(0, 160);
		const inboundLabel = kind === "direct" ? `Mattermost DM from ${senderName}` : `Mattermost message in ${roomLabel} from ${senderName}`;
		core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
			sessionKey,
			contextKey: `mattermost:message:${channelId}:${post.id ?? "unknown"}`
		});
		const textWithId = `${bodyText}\n[mattermost message id: ${post.id ?? "unknown"} channel: ${channelId}]`;
		let combinedBody = core.channel.reply.formatInboundEnvelope({
			channel: "Mattermost",
			from: fromLabel,
			timestamp: typeof post.create_at === "number" ? post.create_at : void 0,
			body: textWithId,
			chatType,
			sender: {
				name: senderName,
				id: senderId
			}
		});
		if (historyKey) combinedBody = buildPendingHistoryContextFromMap({
			historyMap: channelHistories,
			historyKey,
			limit: historyLimit,
			currentMessage: combinedBody,
			formatEntry: (entry) => core.channel.reply.formatInboundEnvelope({
				channel: "Mattermost",
				from: fromLabel,
				timestamp: entry.timestamp,
				body: `${entry.body}${entry.messageId ? ` [id:${entry.messageId} channel:${channelId}]` : ""}`,
				chatType,
				senderLabel: entry.sender
			})
		});
		const to = kind === "direct" ? `user:${senderId}` : `channel:${channelId}`;
		const mediaPayload = buildAgentMediaPayload(mediaList);
		const inboundHistory = historyKey && historyLimit > 0 ? (channelHistories.get(historyKey) ?? []).map((entry) => ({
			sender: entry.sender,
			body: entry.body,
			timestamp: entry.timestamp
		})) : void 0;
		const ctxPayload = core.channel.reply.finalizeInboundContext({
			Body: combinedBody,
			BodyForAgent: bodyText,
			InboundHistory: inboundHistory,
			RawBody: bodyText,
			CommandBody: bodyText,
			From: kind === "direct" ? `mattermost:${senderId}` : kind === "group" ? `mattermost:group:${channelId}` : `mattermost:channel:${channelId}`,
			To: to,
			SessionKey: sessionKey,
			ParentSessionKey: threadKeys.parentSessionKey,
			AccountId: route.accountId,
			ChatType: chatType,
			ConversationLabel: fromLabel,
			GroupSubject: kind !== "direct" ? channelDisplay || roomLabel : void 0,
			GroupChannel: channelName ? `#${channelName}` : void 0,
			GroupSpace: teamId,
			SenderName: senderName,
			SenderId: senderId,
			Provider: "mattermost",
			Surface: "mattermost",
			MessageSid: post.id ?? void 0,
			MessageSids: allMessageIds.length > 1 ? allMessageIds : void 0,
			MessageSidFirst: allMessageIds.length > 1 ? allMessageIds[0] : void 0,
			MessageSidLast: allMessageIds.length > 1 ? allMessageIds[allMessageIds.length - 1] : void 0,
			ReplyToId: threadRootId,
			MessageThreadId: threadRootId,
			Timestamp: typeof post.create_at === "number" ? post.create_at : void 0,
			WasMentioned: kind !== "direct" ? effectiveWasMentioned : void 0,
			CommandAuthorized: commandAuthorized,
			OriginatingChannel: "mattermost",
			OriginatingTo: to,
			...mediaPayload
		});
		if (kind === "direct") {
			const sessionCfg = cfg.session;
			const storePath = core.channel.session.resolveStorePath(sessionCfg?.store, { agentId: route.agentId });
			await core.channel.session.updateLastRoute({
				storePath,
				sessionKey: route.mainSessionKey,
				deliveryContext: {
					channel: "mattermost",
					to,
					accountId: route.accountId
				}
			});
		}
		const previewLine = bodyText.slice(0, 200).replace(/\n/g, "\\n");
		logVerboseMessage(`mattermost inbound: from=${ctxPayload.From} len=${bodyText.length} preview="${previewLine}"`);
		const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "mattermost", account.accountId, { fallbackLimit: account.textChunkLimit ?? 4e3 });
		const tableMode = core.channel.text.resolveMarkdownTableMode({
			cfg,
			channel: "mattermost",
			accountId: account.accountId
		});
		const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
			cfg,
			agentId: route.agentId,
			channel: "mattermost",
			accountId: account.accountId
		});
		const typingCallbacks = createTypingCallbacks({
			start: () => sendTypingIndicator(channelId, threadRootId),
			onStartError: (err) => {
				logTypingFailure({
					log: (message) => logger.debug?.(message),
					channel: "mattermost",
					target: channelId,
					error: err
				});
			}
		});
		const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
			...prefixOptions,
			humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId),
			deliver: async (payload) => {
				const mediaUrls = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
				const text = core.channel.text.convertMarkdownTables(payload.text ?? "", tableMode);
				if (mediaUrls.length === 0) {
					const chunkMode = core.channel.text.resolveChunkMode(cfg, "mattermost", account.accountId);
					const chunks = core.channel.text.chunkMarkdownTextWithMode(text, textLimit, chunkMode);
					for (const chunk of chunks.length > 0 ? chunks : [text]) {
						if (!chunk) continue;
						await sendMessageMattermost(to, chunk, {
							accountId: account.accountId,
							replyToId: threadRootId
						});
					}
				} else {
					let first = true;
					for (const mediaUrl of mediaUrls) {
						const caption = first ? text : "";
						first = false;
						await sendMessageMattermost(to, caption, {
							accountId: account.accountId,
							mediaUrl,
							replyToId: threadRootId
						});
					}
				}
				runtime.log?.(`delivered reply to ${to}`);
			},
			onError: (err, info) => {
				runtime.error?.(`mattermost ${info.kind} reply failed: ${String(err)}`);
			},
			onReplyStart: typingCallbacks.onReplyStart
		});
		await core.channel.reply.dispatchReplyFromConfig({
			ctx: ctxPayload,
			cfg,
			dispatcher,
			replyOptions: {
				...replyOptions,
				disableBlockStreaming: typeof account.blockStreaming === "boolean" ? !account.blockStreaming : void 0,
				onModelSelected
			}
		});
		markDispatchIdle();
		if (historyKey) clearHistoryEntriesIfEnabled({
			historyMap: channelHistories,
			historyKey,
			limit: historyLimit
		});
	};
	const handleReactionEvent = async (payload) => {
		const reactionData = payload.data?.reaction;
		if (!reactionData) return;
		let reaction = null;
		if (typeof reactionData === "string") try {
			reaction = JSON.parse(reactionData);
		} catch {
			return;
		}
		else if (typeof reactionData === "object") reaction = reactionData;
		if (!reaction) return;
		const userId = reaction.user_id?.trim();
		const postId = reaction.post_id?.trim();
		const emojiName = reaction.emoji_name?.trim();
		if (!userId || !postId || !emojiName) return;
		if (userId === botUserId) return;
		const action = payload.event === "reaction_removed" ? "removed" : "added";
		const senderName = (await resolveUserInfo(userId))?.username?.trim() || userId;
		const channelId = payload.broadcast?.channel_id;
		if (!channelId) {
			logVerboseMessage(`mattermost: drop reaction (no channel_id in broadcast, cannot enforce policy)`);
			return;
		}
		const channelInfo = await resolveChannelInfo(channelId);
		if (!channelInfo?.type) {
			logVerboseMessage(`mattermost: drop reaction (cannot resolve channel type for ${channelId})`);
			return;
		}
		const kind = channelKind(channelInfo.type);
		if (kind === "direct") {
			const dmPolicy = account.config.dmPolicy ?? "pairing";
			if (dmPolicy === "disabled") {
				logVerboseMessage(`mattermost: drop reaction (dmPolicy=disabled sender=${userId})`);
				return;
			}
			if (dmPolicy !== "open") {
				const configAllowFrom = normalizeAllowList(account.config.allowFrom ?? []);
				const storeAllowFrom = normalizeAllowList(await core.channel.pairing.readAllowFromStore("mattermost").catch(() => []));
				if (!isSenderAllowed({
					senderId: userId,
					senderName,
					allowFrom: Array.from(new Set([...configAllowFrom, ...storeAllowFrom]))
				})) {
					logVerboseMessage(`mattermost: drop reaction (dmPolicy=${dmPolicy} sender=${userId} not allowed)`);
					return;
				}
			}
		} else if (kind) {
			const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
			const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
			if (groupPolicy === "disabled") {
				logVerboseMessage(`mattermost: drop reaction (groupPolicy=disabled channel=${channelId})`);
				return;
			}
			if (groupPolicy === "allowlist") {
				const configAllowFrom = normalizeAllowList(account.config.allowFrom ?? []);
				const configGroupAllowFrom = normalizeAllowList(account.config.groupAllowFrom ?? []);
				const storeAllowFrom = normalizeAllowList(await core.channel.pairing.readAllowFromStore("mattermost").catch(() => []));
				const effectiveGroupAllowFrom = Array.from(new Set([...configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom, ...storeAllowFrom]));
				if (!(effectiveGroupAllowFrom.length > 0 && isSenderAllowed({
					senderId: userId,
					senderName,
					allowFrom: effectiveGroupAllowFrom
				}))) {
					logVerboseMessage(`mattermost: drop reaction (groupPolicy=allowlist sender=${userId})`);
					return;
				}
			}
		}
		const teamId = channelInfo?.team_id ?? void 0;
		const sessionKey = core.channel.routing.resolveAgentRoute({
			cfg,
			channel: "mattermost",
			accountId: account.accountId,
			teamId,
			peer: {
				kind,
				id: kind === "direct" ? userId : channelId
			}
		}).sessionKey;
		const eventText = `Mattermost reaction ${action}: :${emojiName}: by @${senderName} on post ${postId} in channel ${channelId}`;
		core.system.enqueueSystemEvent(eventText, {
			sessionKey,
			contextKey: `mattermost:reaction:${postId}:${emojiName}:${userId}:${action}`
		});
		logVerboseMessage(`mattermost reaction: ${action} :${emojiName}: by ${senderName} on ${postId}`);
	};
	const inboundDebounceMs = core.channel.debounce.resolveInboundDebounceMs({
		cfg,
		channel: "mattermost"
	});
	const debouncer = core.channel.debounce.createInboundDebouncer({
		debounceMs: inboundDebounceMs,
		buildKey: (entry) => {
			const channelId = entry.post.channel_id ?? entry.payload.data?.channel_id ?? entry.payload.broadcast?.channel_id;
			if (!channelId) return null;
			const threadId = entry.post.root_id?.trim();
			const threadKey = threadId ? `thread:${threadId}` : "channel";
			return `mattermost:${account.accountId}:${channelId}:${threadKey}`;
		},
		shouldDebounce: (entry) => {
			if (entry.post.file_ids && entry.post.file_ids.length > 0) return false;
			const text = entry.post.message?.trim() ?? "";
			if (!text) return false;
			return !core.channel.text.hasControlCommand(text, cfg);
		},
		onFlush: async (entries) => {
			const last = entries.at(-1);
			if (!last) return;
			if (entries.length === 1) {
				await handlePost(last.post, last.payload);
				return;
			}
			const combinedText = entries.map((entry) => entry.post.message?.trim() ?? "").filter(Boolean).join("\n");
			const mergedPost = {
				...last.post,
				message: combinedText,
				file_ids: []
			};
			const ids = entries.map((entry) => entry.post.id).filter(Boolean);
			await handlePost(mergedPost, last.payload, ids.length > 0 ? ids : void 0);
		},
		onError: (err) => {
			runtime.error?.(`mattermost debounce flush failed: ${String(err)}`);
		}
	});
	const wsUrl = buildMattermostWsUrl(baseUrl);
	let seq = 1;
	await runWithReconnect(createMattermostConnectOnce({
		wsUrl,
		botToken,
		abortSignal: opts.abortSignal,
		statusSink: opts.statusSink,
		runtime,
		webSocketFactory: opts.webSocketFactory,
		nextSeq: () => seq++,
		onPosted: async (post, payload) => {
			await debouncer.enqueue({
				post,
				payload
			});
		},
		onReaction: async (payload) => {
			await handleReactionEvent(payload);
		}
	}), {
		abortSignal: opts.abortSignal,
		jitterRatio: .2,
		onError: (err) => {
			runtime.error?.(`mattermost connection failed: ${String(err)}`);
			opts.statusSink?.({
				lastError: String(err),
				connected: false
			});
		},
		onReconnect: (delayMs) => {
			runtime.log?.(`mattermost reconnecting in ${Math.round(delayMs / 1e3)}s`);
		}
	});
}

//#endregion
//#region extensions/mattermost/src/mattermost/probe.ts
async function readMattermostError(res) {
	if ((res.headers.get("content-type") ?? "").includes("application/json")) {
		const data = await res.json();
		if (data?.message) return data.message;
		return JSON.stringify(data);
	}
	return await res.text();
}
async function probeMattermost(baseUrl, botToken, timeoutMs = 2500) {
	const normalized = normalizeMattermostBaseUrl(baseUrl);
	if (!normalized) return {
		ok: false,
		error: "baseUrl missing"
	};
	const url = `${normalized}/api/v4/users/me`;
	const start = Date.now();
	const controller = timeoutMs > 0 ? new AbortController() : void 0;
	let timer = null;
	if (controller) timer = setTimeout(() => controller.abort(), timeoutMs);
	try {
		const res = await fetch(url, {
			headers: { Authorization: `Bearer ${botToken}` },
			signal: controller?.signal
		});
		const elapsedMs = Date.now() - start;
		if (!res.ok) {
			const detail = await readMattermostError(res);
			return {
				ok: false,
				status: res.status,
				error: detail || res.statusText,
				elapsedMs
			};
		}
		const bot = await res.json();
		return {
			ok: true,
			status: res.status,
			elapsedMs,
			bot
		};
	} catch (err) {
		return {
			ok: false,
			status: null,
			error: err instanceof Error ? err.message : String(err),
			elapsedMs: Date.now() - start
		};
	} finally {
		if (timer) clearTimeout(timer);
	}
}

//#endregion
//#region extensions/mattermost/src/mattermost/reactions.ts
const BOT_USER_CACHE_TTL_MS = 10 * 6e4;
const botUserIdCache = /* @__PURE__ */ new Map();
async function resolveBotUserId(client, cacheKey) {
	const cached = botUserIdCache.get(cacheKey);
	if (cached && cached.expiresAt > Date.now()) return cached.userId;
	const userId = (await fetchMattermostMe(client))?.id?.trim();
	if (!userId) return null;
	botUserIdCache.set(cacheKey, {
		userId,
		expiresAt: Date.now() + BOT_USER_CACHE_TTL_MS
	});
	return userId;
}
async function addMattermostReaction(params) {
	return runMattermostReaction(params, {
		action: "add",
		mutation: createReaction
	});
}
async function removeMattermostReaction(params) {
	return runMattermostReaction(params, {
		action: "remove",
		mutation: deleteReaction
	});
}
async function runMattermostReaction(params, options) {
	const resolved = resolveMattermostAccount({
		cfg: params.cfg,
		accountId: params.accountId
	});
	const baseUrl = resolved.baseUrl?.trim();
	const botToken = resolved.botToken?.trim();
	if (!baseUrl || !botToken) return {
		ok: false,
		error: "Mattermost botToken/baseUrl missing."
	};
	const client = createMattermostClient({
		baseUrl,
		botToken,
		fetchImpl: params.fetchImpl
	});
	const userId = await resolveBotUserId(client, `${baseUrl}:${botToken}`);
	if (!userId) return {
		ok: false,
		error: "Mattermost reactions failed: could not resolve bot user id."
	};
	try {
		await options.mutation(client, {
			userId,
			postId: params.postId,
			emojiName: params.emojiName
		});
	} catch (err) {
		return {
			ok: false,
			error: `Mattermost ${options.action} reaction failed: ${String(err)}`
		};
	}
	return { ok: true };
}
async function createReaction(client, params) {
	await client.request("/reactions", {
		method: "POST",
		body: JSON.stringify({
			user_id: params.userId,
			post_id: params.postId,
			emoji_name: params.emojiName
		})
	});
}
async function deleteReaction(client, params) {
	const emoji = encodeURIComponent(params.emojiName);
	await client.request(`/users/${params.userId}/posts/${params.postId}/reactions/${emoji}`, { method: "DELETE" });
}

//#endregion
//#region extensions/mattermost/src/normalize.ts
function normalizeMattermostMessagingTarget(raw) {
	const trimmed = raw.trim();
	if (!trimmed) return;
	const lower = trimmed.toLowerCase();
	if (lower.startsWith("channel:")) {
		const id = trimmed.slice(8).trim();
		return id ? `channel:${id}` : void 0;
	}
	if (lower.startsWith("group:")) {
		const id = trimmed.slice(6).trim();
		return id ? `channel:${id}` : void 0;
	}
	if (lower.startsWith("user:")) {
		const id = trimmed.slice(5).trim();
		return id ? `user:${id}` : void 0;
	}
	if (lower.startsWith("mattermost:")) {
		const id = trimmed.slice(11).trim();
		return id ? `user:${id}` : void 0;
	}
	if (trimmed.startsWith("@")) {
		const id = trimmed.slice(1).trim();
		return id ? `@${id}` : void 0;
	}
	if (trimmed.startsWith("#")) {
		const id = trimmed.slice(1).trim();
		return id ? `channel:${id}` : void 0;
	}
	return `channel:${trimmed}`;
}
function looksLikeMattermostTargetId(raw) {
	const trimmed = raw.trim();
	if (!trimmed) return false;
	if (/^(user|channel|group|mattermost):/i.test(trimmed)) return true;
	if (/^[@#]/.test(trimmed)) return true;
	return /^[a-z0-9]{8,}$/i.test(trimmed);
}

//#endregion
//#region extensions/mattermost/src/onboarding.ts
const channel = "mattermost";
async function noteMattermostSetup(prompter) {
	await prompter.note([
		"1) Mattermost System Console -> Integrations -> Bot Accounts",
		"2) Create a bot + copy its token",
		"3) Use your server base URL (e.g., https://chat.example.com)",
		"Tip: the bot must be a member of any channel you want it to monitor.",
		"Docs: https://docs.minion.ai/channels/mattermost"
	].join("\n"), "Mattermost bot token");
}
const mattermostOnboardingAdapter = {
	channel,
	getStatus: async ({ cfg }) => {
		const configured = listMattermostAccountIds(cfg).some((accountId) => {
			const account = resolveMattermostAccount({
				cfg,
				accountId
			});
			return Boolean(account.botToken && account.baseUrl);
		});
		return {
			channel,
			configured,
			statusLines: [`Mattermost: ${configured ? "configured" : "needs token + url"}`],
			selectionHint: configured ? "configured" : "needs setup",
			quickstartScore: configured ? 2 : 1
		};
	},
	configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => {
		const override = accountOverrides.mattermost?.trim();
		const defaultAccountId = resolveDefaultMattermostAccountId(cfg);
		let accountId = override ? normalizeAccountId$1(override) : defaultAccountId;
		if (shouldPromptAccountIds && !override) accountId = await promptAccountId({
			cfg,
			prompter,
			label: "Mattermost",
			currentId: accountId,
			listAccountIds: listMattermostAccountIds,
			defaultAccountId
		});
		let next = cfg;
		const resolvedAccount = resolveMattermostAccount({
			cfg: next,
			accountId
		});
		const accountConfigured = Boolean(resolvedAccount.botToken && resolvedAccount.baseUrl);
		const canUseEnv = accountId === DEFAULT_ACCOUNT_ID$1 && Boolean(process.env.MATTERMOST_BOT_TOKEN?.trim()) && Boolean(process.env.MATTERMOST_URL?.trim());
		const hasConfigValues = Boolean(resolvedAccount.config.botToken) || Boolean(resolvedAccount.config.baseUrl);
		let botToken = null;
		let baseUrl = null;
		if (!accountConfigured) await noteMattermostSetup(prompter);
		if (canUseEnv && !hasConfigValues) if (await prompter.confirm({
			message: "MATTERMOST_BOT_TOKEN + MATTERMOST_URL detected. Use env vars?",
			initialValue: true
		})) next = {
			...next,
			channels: {
				...next.channels,
				mattermost: {
					...next.channels?.mattermost,
					enabled: true
				}
			}
		};
		else {
			botToken = String(await prompter.text({
				message: "Enter Mattermost bot token",
				validate: (value) => value?.trim() ? void 0 : "Required"
			})).trim();
			baseUrl = String(await prompter.text({
				message: "Enter Mattermost base URL",
				validate: (value) => value?.trim() ? void 0 : "Required"
			})).trim();
		}
		else if (accountConfigured) {
			if (!await prompter.confirm({
				message: "Mattermost credentials already configured. Keep them?",
				initialValue: true
			})) {
				botToken = String(await prompter.text({
					message: "Enter Mattermost bot token",
					validate: (value) => value?.trim() ? void 0 : "Required"
				})).trim();
				baseUrl = String(await prompter.text({
					message: "Enter Mattermost base URL",
					validate: (value) => value?.trim() ? void 0 : "Required"
				})).trim();
			}
		} else {
			botToken = String(await prompter.text({
				message: "Enter Mattermost bot token",
				validate: (value) => value?.trim() ? void 0 : "Required"
			})).trim();
			baseUrl = String(await prompter.text({
				message: "Enter Mattermost base URL",
				validate: (value) => value?.trim() ? void 0 : "Required"
			})).trim();
		}
		if (botToken || baseUrl) if (accountId === DEFAULT_ACCOUNT_ID$1) next = {
			...next,
			channels: {
				...next.channels,
				mattermost: {
					...next.channels?.mattermost,
					enabled: true,
					...botToken ? { botToken } : {},
					...baseUrl ? { baseUrl } : {}
				}
			}
		};
		else next = {
			...next,
			channels: {
				...next.channels,
				mattermost: {
					...next.channels?.mattermost,
					enabled: true,
					accounts: {
						...next.channels?.mattermost?.accounts,
						[accountId]: {
							...next.channels?.mattermost?.accounts?.[accountId],
							enabled: next.channels?.mattermost?.accounts?.[accountId]?.enabled ?? true,
							...botToken ? { botToken } : {},
							...baseUrl ? { baseUrl } : {}
						}
					}
				}
			}
		};
		return {
			cfg: next,
			accountId
		};
	},
	disable: (cfg) => ({
		...cfg,
		channels: {
			...cfg.channels,
			mattermost: {
				...cfg.channels?.mattermost,
				enabled: false
			}
		}
	})
};

//#endregion
//#region extensions/mattermost/src/channel.ts
const mattermostMessageActions = {
	listActions: ({ cfg }) => {
		const baseReactions = (cfg.channels?.mattermost?.actions)?.reactions;
		if (!listMattermostAccountIds(cfg).map((accountId) => resolveMattermostAccount({
			cfg,
			accountId
		})).filter((account) => account.enabled).filter((account) => Boolean(account.botToken?.trim() && account.baseUrl?.trim())).some((account) => {
			return (account.config.actions?.reactions ?? baseReactions ?? true) !== false;
		})) return [];
		return ["react"];
	},
	supportsAction: ({ action }) => {
		return action === "react";
	},
	handleAction: async ({ action, params, cfg, accountId }) => {
		if (action !== "react") throw new Error(`Mattermost action ${action} not supported`);
		const mmBase = cfg?.channels?.mattermost;
		const accounts = mmBase?.accounts;
		const resolvedAccountId = accountId ?? resolveDefaultMattermostAccountId(cfg);
		const acctActions = (accounts?.[resolvedAccountId])?.actions;
		const baseActions = mmBase?.actions;
		if (!(acctActions?.reactions ?? baseActions?.reactions ?? true)) throw new Error("Mattermost reactions are disabled in config");
		const postId = (typeof params?.messageId === "string" ? params.messageId : typeof params?.postId === "string" ? params.postId : "").trim();
		if (!postId) throw new Error("Mattermost react requires messageId (post id)");
		const emojiName = (typeof params?.emoji === "string" ? params.emoji : "").trim().replace(/^:+|:+$/g, "");
		if (!emojiName) throw new Error("Mattermost react requires emoji");
		if (params?.remove === true) {
			const result = await removeMattermostReaction({
				cfg,
				postId,
				emojiName,
				accountId: resolvedAccountId
			});
			if (!result.ok) throw new Error(result.error);
			return {
				content: [{
					type: "text",
					text: `Removed reaction :${emojiName}: from ${postId}`
				}],
				details: {}
			};
		}
		const result = await addMattermostReaction({
			cfg,
			postId,
			emojiName,
			accountId: resolvedAccountId
		});
		if (!result.ok) throw new Error(result.error);
		return {
			content: [{
				type: "text",
				text: `Reacted with :${emojiName}: on ${postId}`
			}],
			details: {}
		};
	}
};
const meta = {
	id: "mattermost",
	label: "Mattermost",
	selectionLabel: "Mattermost (plugin)",
	detailLabel: "Mattermost Bot",
	docsPath: "/channels/mattermost",
	docsLabel: "mattermost",
	blurb: "self-hosted Slack-style chat; install the plugin to enable.",
	systemImage: "bubble.left.and.bubble.right",
	order: 65,
	quickstartAllowFrom: true
};
function normalizeAllowEntry(entry) {
	return entry.trim().replace(/^(mattermost|user):/i, "").replace(/^@/, "").toLowerCase();
}
function formatAllowEntry(entry) {
	const trimmed = entry.trim();
	if (!trimmed) return "";
	if (trimmed.startsWith("@")) {
		const username = trimmed.slice(1).trim();
		return username ? `@${username.toLowerCase()}` : "";
	}
	return trimmed.replace(/^(mattermost|user):/i, "").toLowerCase();
}
const mattermostPlugin = {
	id: "mattermost",
	meta: { ...meta },
	onboarding: mattermostOnboardingAdapter,
	pairing: {
		idLabel: "mattermostUserId",
		normalizeAllowEntry: (entry) => normalizeAllowEntry(entry),
		notifyApproval: async ({ id }) => {
			console.log(`[mattermost] User ${id} approved for pairing`);
		}
	},
	capabilities: {
		chatTypes: [
			"direct",
			"channel",
			"group",
			"thread"
		],
		reactions: true,
		threads: true,
		media: true
	},
	streaming: { blockStreamingCoalesceDefaults: {
		minChars: 1500,
		idleMs: 1e3
	} },
	reload: { configPrefixes: ["channels.mattermost"] },
	configSchema: buildChannelConfigSchema(MattermostConfigSchema),
	config: {
		listAccountIds: (cfg) => listMattermostAccountIds(cfg),
		resolveAccount: (cfg, accountId) => resolveMattermostAccount({
			cfg,
			accountId
		}),
		defaultAccountId: (cfg) => resolveDefaultMattermostAccountId(cfg),
		setAccountEnabled: ({ cfg, accountId, enabled }) => setAccountEnabledInConfigSection({
			cfg,
			sectionKey: "mattermost",
			accountId,
			enabled,
			allowTopLevel: true
		}),
		deleteAccount: ({ cfg, accountId }) => deleteAccountFromConfigSection({
			cfg,
			sectionKey: "mattermost",
			accountId,
			clearBaseFields: [
				"botToken",
				"baseUrl",
				"name"
			]
		}),
		isConfigured: (account) => Boolean(account.botToken && account.baseUrl),
		describeAccount: (account) => ({
			accountId: account.accountId,
			name: account.name,
			enabled: account.enabled,
			configured: Boolean(account.botToken && account.baseUrl),
			botTokenSource: account.botTokenSource,
			baseUrl: account.baseUrl
		}),
		resolveAllowFrom: ({ cfg, accountId }) => (resolveMattermostAccount({
			cfg,
			accountId
		}).config.allowFrom ?? []).map((entry) => String(entry)),
		formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => formatAllowEntry(String(entry))).filter(Boolean)
	},
	security: {
		resolveDmPolicy: ({ cfg, accountId, account }) => {
			const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
			const basePath = Boolean(cfg.channels?.mattermost?.accounts?.[resolvedAccountId]) ? `channels.mattermost.accounts.${resolvedAccountId}.` : "channels.mattermost.";
			return {
				policy: account.config.dmPolicy ?? "pairing",
				allowFrom: account.config.allowFrom ?? [],
				policyPath: `${basePath}dmPolicy`,
				allowFromPath: basePath,
				approveHint: formatPairingApproveHint("mattermost"),
				normalizeEntry: (raw) => normalizeAllowEntry(raw)
			};
		},
		collectWarnings: ({ account, cfg }) => {
			const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
			if ((account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist") !== "open") return [];
			return [`- Mattermost channels: groupPolicy="open" allows any member to trigger (mention-gated). Set channels.mattermost.groupPolicy="allowlist" + channels.mattermost.groupAllowFrom to restrict senders.`];
		}
	},
	groups: { resolveRequireMention: resolveMattermostGroupRequireMention },
	actions: mattermostMessageActions,
	messaging: {
		normalizeTarget: normalizeMattermostMessagingTarget,
		targetResolver: {
			looksLikeId: looksLikeMattermostTargetId,
			hint: "<channelId|user:ID|channel:ID>"
		}
	},
	outbound: {
		deliveryMode: "direct",
		chunker: (text, limit) => getMattermostRuntime().channel.text.chunkMarkdownText(text, limit),
		chunkerMode: "markdown",
		textChunkLimit: 4e3,
		resolveTarget: ({ to }) => {
			const trimmed = to?.trim();
			if (!trimmed) return {
				ok: false,
				error: /* @__PURE__ */ new Error("Delivering to Mattermost requires --to <channelId|@username|user:ID|channel:ID>")
			};
			return {
				ok: true,
				to: trimmed
			};
		},
		sendText: async ({ to, text, accountId, replyToId }) => {
			return {
				channel: "mattermost",
				...await sendMessageMattermost(to, text, {
					accountId: accountId ?? void 0,
					replyToId: replyToId ?? void 0
				})
			};
		},
		sendMedia: async ({ to, text, mediaUrl, accountId, replyToId }) => {
			return {
				channel: "mattermost",
				...await sendMessageMattermost(to, text, {
					accountId: accountId ?? void 0,
					mediaUrl,
					replyToId: replyToId ?? void 0
				})
			};
		}
	},
	status: {
		defaultRuntime: {
			accountId: DEFAULT_ACCOUNT_ID,
			running: false,
			connected: false,
			lastConnectedAt: null,
			lastDisconnect: null,
			lastStartAt: null,
			lastStopAt: null,
			lastError: null
		},
		buildChannelSummary: ({ snapshot }) => ({
			configured: snapshot.configured ?? false,
			botTokenSource: snapshot.botTokenSource ?? "none",
			running: snapshot.running ?? false,
			connected: snapshot.connected ?? false,
			lastStartAt: snapshot.lastStartAt ?? null,
			lastStopAt: snapshot.lastStopAt ?? null,
			lastError: snapshot.lastError ?? null,
			baseUrl: snapshot.baseUrl ?? null,
			probe: snapshot.probe,
			lastProbeAt: snapshot.lastProbeAt ?? null
		}),
		probeAccount: async ({ account, timeoutMs }) => {
			const token = account.botToken?.trim();
			const baseUrl = account.baseUrl?.trim();
			if (!token || !baseUrl) return {
				ok: false,
				error: "bot token or baseUrl missing"
			};
			return await probeMattermost(baseUrl, token, timeoutMs);
		},
		buildAccountSnapshot: ({ account, runtime, probe }) => ({
			accountId: account.accountId,
			name: account.name,
			enabled: account.enabled,
			configured: Boolean(account.botToken && account.baseUrl),
			botTokenSource: account.botTokenSource,
			baseUrl: account.baseUrl,
			running: runtime?.running ?? false,
			connected: runtime?.connected ?? false,
			lastConnectedAt: runtime?.lastConnectedAt ?? null,
			lastDisconnect: runtime?.lastDisconnect ?? null,
			lastStartAt: runtime?.lastStartAt ?? null,
			lastStopAt: runtime?.lastStopAt ?? null,
			lastError: runtime?.lastError ?? null,
			probe,
			lastInboundAt: runtime?.lastInboundAt ?? null,
			lastOutboundAt: runtime?.lastOutboundAt ?? null
		})
	},
	setup: {
		resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
		applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
			cfg,
			channelKey: "mattermost",
			accountId,
			name
		}),
		validateInput: ({ accountId, input }) => {
			if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) return "Mattermost env vars can only be used for the default account.";
			const token = input.botToken ?? input.token;
			const baseUrl = input.httpUrl;
			if (!input.useEnv && (!token || !baseUrl)) return "Mattermost requires --bot-token and --http-url (or --use-env).";
			if (baseUrl && !normalizeMattermostBaseUrl(baseUrl)) return "Mattermost --http-url must include a valid base URL.";
			return null;
		},
		applyAccountConfig: ({ cfg, accountId, input }) => {
			const token = input.botToken ?? input.token;
			const baseUrl = input.httpUrl?.trim();
			const namedConfig = applyAccountNameToChannelSection({
				cfg,
				channelKey: "mattermost",
				accountId,
				name: input.name
			});
			const next = accountId !== DEFAULT_ACCOUNT_ID ? migrateBaseNameToDefaultAccount({
				cfg: namedConfig,
				channelKey: "mattermost"
			}) : namedConfig;
			if (accountId === DEFAULT_ACCOUNT_ID) return {
				...next,
				channels: {
					...next.channels,
					mattermost: {
						...next.channels?.mattermost,
						enabled: true,
						...input.useEnv ? {} : {
							...token ? { botToken: token } : {},
							...baseUrl ? { baseUrl } : {}
						}
					}
				}
			};
			return {
				...next,
				channels: {
					...next.channels,
					mattermost: {
						...next.channels?.mattermost,
						enabled: true,
						accounts: {
							...next.channels?.mattermost?.accounts,
							[accountId]: {
								...next.channels?.mattermost?.accounts?.[accountId],
								enabled: true,
								...token ? { botToken: token } : {},
								...baseUrl ? { baseUrl } : {}
							}
						}
					}
				}
			};
		}
	},
	gateway: { startAccount: async (ctx) => {
		const account = ctx.account;
		ctx.setStatus({
			accountId: account.accountId,
			baseUrl: account.baseUrl,
			botTokenSource: account.botTokenSource
		});
		ctx.log?.info(`[${account.accountId}] starting channel`);
		return monitorMattermostProvider({
			botToken: account.botToken ?? void 0,
			baseUrl: account.baseUrl ?? void 0,
			accountId: account.accountId,
			config: ctx.cfg,
			runtime: ctx.runtime,
			abortSignal: ctx.abortSignal,
			statusSink: (patch) => ctx.setStatus({
				accountId: ctx.accountId,
				...patch
			})
		});
	} }
};

//#endregion
//#region extensions/mattermost/index.ts
const plugin = {
	id: "mattermost",
	name: "Mattermost",
	description: "Mattermost channel plugin",
	configSchema: emptyPluginConfigSchema(),
	register(api) {
		setMattermostRuntime(api.runtime);
		api.registerChannel({ plugin: mattermostPlugin });
	}
};

//#endregion
export { plugin as default };