import { h as __exportAll } from "./pi-embedded-utils-CPkknmhn.js";
import { r as defaultRuntime } from "./subsystem-CBhYrL33.js";
import { l as normalizeAccountId, m as resolveThreadSessionKeys } from "./session-key-BSfKMcoD.js";
import { u as resolveSessionAgentId } from "./agent-scope-BzfnY0io.js";
import { n as loadConfig } from "./config-CCV5uD38.js";
import { C as parseReplyDirectives, S as normalizeTargetForProvider, b as buildTargetResolverSignature, x as normalizeChannelTargetInput } from "./send-BsHWR0r1.js";
import { s as resolveStorePath } from "./paths-DKqd8NBB.js";
import { l as extensionForMime } from "./image-ops-CkVtcgS9.js";
import { a as jsonResult, c as readStringArrayParam, o as readNumberParam, u as readStringParam } from "./common-D7lLwt9D.js";
import { f as resolveSlackAccount, n as normalizeWhatsAppTarget, t as isWhatsAppGroupJid } from "./normalize-C-uc4MlX.js";
import { c as createSlackWebClient, t as parseSlackTarget } from "./targets-Cn2eLkjl.js";
import { a as listDeliverableMessageChannels, d as GATEWAY_CLIENT_NAMES, l as GATEWAY_CLIENT_IDS, n as isDeliverableMessageChannel, o as normalizeMessageChannel, u as GATEWAY_CLIENT_MODES } from "./message-channel-B5UwhWVn.js";
import { n as listChannelPlugins, r as normalizeChannelId, t as getChannelPlugin } from "./plugins-BBDtlgEl.js";
import { B as normalizeIMessageHandle, Dt as resolveTelegramTargetChatType, Et as parseTelegramTarget, F as resolveSignalSender, H as DiscordUiContainer, J as resolveOutboundTarget, N as resolveSignalPeerId, P as resolveSignalRecipient, St as listChannelSupportedActions, T as normalizeAllowListLower, U as parseDiscordTarget, V as parseIMessageTarget, X as ambiguousTargetError, Z as unknownTargetError, a as buildTelegramGroupPeerId } from "./helpers-BZiqjMxc.js";
import { n as loadWebMedia } from "./media-IKchB7_u.js";
import { d as resolveSandboxedMediaSource, s as assertMediaNotDataUrl } from "./skills-BEdkiMY1.js";
import { n as appendAssistantMessageToSessionTranscript, o as recordSessionMetaFromInbound } from "./sessions-ByypVr-c.js";
import { n as callGateway, r as randomIdempotencyKey } from "./call-BliLbuG-.js";
import { r as resolveGatewayOptions$1 } from "./gateway-CKFg1Dnv.js";
import { t as buildAgentSessionKey } from "./resolve-route-twk0QWtH.js";
import { t as stripReasoningTagsFromText } from "./reasoning-tags-DcR-UA-n.js";
import { n as normalizePollInput } from "./polls-CCM1wPi7.js";
import { a as applyTargetToParams, i as stringEnum, n as channelTargetsSchema, o as actionHasTarget, s as actionRequiresTarget, t as channelTargetSchema } from "./typebox-Dqngjw-j.js";
import { i as throwIfAborted, r as normalizeReplyPayloadsForDelivery, t as deliverOutboundPayloads } from "./deliver-DACUBWDa.js";
import path from "node:path";
import fs from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { Type } from "@sinclair/typebox";
import { Separator, TextDisplay } from "@buape/carbon";

//#region src/channels/plugins/bluebubbles-actions.ts
const BLUEBUBBLES_ACTIONS = {
	react: { gate: "reactions" },
	edit: {
		gate: "edit",
		unsupportedOnMacOS26: true
	},
	unsend: { gate: "unsend" },
	reply: { gate: "reply" },
	sendWithEffect: { gate: "sendWithEffect" },
	renameGroup: {
		gate: "renameGroup",
		groupOnly: true
	},
	setGroupIcon: {
		gate: "setGroupIcon",
		groupOnly: true
	},
	addParticipant: {
		gate: "addParticipant",
		groupOnly: true
	},
	removeParticipant: {
		gate: "removeParticipant",
		groupOnly: true
	},
	leaveGroup: {
		gate: "leaveGroup",
		groupOnly: true
	},
	sendAttachment: { gate: "sendAttachment" }
};
const BLUEBUBBLES_ACTION_SPECS = BLUEBUBBLES_ACTIONS;
const BLUEBUBBLES_ACTION_NAMES = Object.keys(BLUEBUBBLES_ACTIONS);
const BLUEBUBBLES_GROUP_ACTIONS = new Set(BLUEBUBBLES_ACTION_NAMES.filter((action) => BLUEBUBBLES_ACTION_SPECS[action]?.groupOnly));

//#endregion
//#region src/channels/plugins/message-actions.ts
function listChannelMessageActions(cfg) {
	const actions = new Set(["send", "broadcast"]);
	for (const plugin of listChannelPlugins()) {
		const list = plugin.actions?.listActions?.({ cfg });
		if (!list) continue;
		for (const action of list) actions.add(action);
	}
	return Array.from(actions);
}
function supportsChannelMessageButtons(cfg) {
	for (const plugin of listChannelPlugins()) if (plugin.actions?.supportsButtons?.({ cfg })) return true;
	return false;
}
function supportsChannelMessageButtonsForChannel(params) {
	if (!params.channel) return false;
	return getChannelPlugin(params.channel)?.actions?.supportsButtons?.({ cfg: params.cfg }) === true;
}
function supportsChannelMessageCards(cfg) {
	for (const plugin of listChannelPlugins()) if (plugin.actions?.supportsCards?.({ cfg })) return true;
	return false;
}
function supportsChannelMessageCardsForChannel(params) {
	if (!params.channel) return false;
	return getChannelPlugin(params.channel)?.actions?.supportsCards?.({ cfg: params.cfg }) === true;
}
async function dispatchChannelMessageAction(ctx) {
	const plugin = getChannelPlugin(ctx.channel);
	if (!plugin?.actions?.handleAction) return null;
	if (plugin.actions.supportsAction && !plugin.actions.supportsAction({ action: ctx.action })) return null;
	return await plugin.actions.handleAction(ctx);
}

//#endregion
//#region src/channels/plugins/message-action-names.ts
const CHANNEL_MESSAGE_ACTION_NAMES = [
	"send",
	"broadcast",
	"poll",
	"react",
	"reactions",
	"read",
	"edit",
	"unsend",
	"reply",
	"sendWithEffect",
	"renameGroup",
	"setGroupIcon",
	"addParticipant",
	"removeParticipant",
	"leaveGroup",
	"sendAttachment",
	"delete",
	"pin",
	"unpin",
	"list-pins",
	"permissions",
	"thread-create",
	"thread-list",
	"thread-reply",
	"search",
	"sticker",
	"sticker-search",
	"member-info",
	"role-info",
	"emoji-list",
	"emoji-upload",
	"sticker-upload",
	"role-add",
	"role-remove",
	"channel-info",
	"channel-list",
	"channel-create",
	"channel-edit",
	"channel-delete",
	"channel-move",
	"category-create",
	"category-edit",
	"category-delete",
	"topic-create",
	"voice-status",
	"event-list",
	"event-create",
	"timeout",
	"kick",
	"ban",
	"set-presence"
];

//#endregion
//#region src/infra/outbound/channel-selection.ts
const getMessageChannels = () => listDeliverableMessageChannels();
function isKnownChannel(value) {
	return getMessageChannels().includes(value);
}
function isAccountEnabled(account) {
	if (!account || typeof account !== "object") return true;
	return account.enabled !== false;
}
async function isPluginConfigured(plugin, cfg) {
	const accountIds = plugin.config.listAccountIds(cfg);
	if (accountIds.length === 0) return false;
	for (const accountId of accountIds) {
		const account = plugin.config.resolveAccount(cfg, accountId);
		if (!(plugin.config.isEnabled ? plugin.config.isEnabled(account, cfg) : isAccountEnabled(account))) continue;
		if (!plugin.config.isConfigured) return true;
		if (await plugin.config.isConfigured(account, cfg)) return true;
	}
	return false;
}
async function listConfiguredMessageChannels(cfg) {
	const channels = [];
	for (const plugin of listChannelPlugins()) {
		if (!isKnownChannel(plugin.id)) continue;
		if (await isPluginConfigured(plugin, cfg)) channels.push(plugin.id);
	}
	return channels;
}
async function resolveMessageChannelSelection(params) {
	const normalized = normalizeMessageChannel(params.channel);
	if (normalized) {
		if (!isKnownChannel(normalized)) throw new Error(`Unknown channel: ${String(normalized)}`);
		return {
			channel: normalized,
			configured: await listConfiguredMessageChannels(params.cfg)
		};
	}
	const configured = await listConfiguredMessageChannels(params.cfg);
	if (configured.length === 1) return {
		channel: configured[0],
		configured
	};
	if (configured.length === 0) throw new Error("Channel is required (no configured channels detected).");
	throw new Error(`Channel is required when multiple channels are configured: ${configured.join(", ")}`);
}

//#endregion
//#region src/infra/outbound/message-action-params.ts
function readBooleanParam(params, key) {
	const raw = params[key];
	if (typeof raw === "boolean") return raw;
	if (typeof raw === "string") {
		const trimmed = raw.trim().toLowerCase();
		if (trimmed === "true") return true;
		if (trimmed === "false") return false;
	}
}
function resolveSlackAutoThreadId(params) {
	const context = params.toolContext;
	if (!context?.currentThreadTs || !context.currentChannelId) return;
	if (context.replyToMode !== "all" && context.replyToMode !== "first") return;
	const parsedTarget = parseSlackTarget(params.to, { defaultKind: "channel" });
	if (!parsedTarget || parsedTarget.kind !== "channel") return;
	if (parsedTarget.id.toLowerCase() !== context.currentChannelId.toLowerCase()) return;
	if (context.replyToMode === "first" && context.hasRepliedRef?.value) return;
	return context.currentThreadTs;
}
/**
* Auto-inject Telegram forum topic thread ID when the message tool targets
* the same chat the session originated from.  Mirrors the Slack auto-threading
* pattern so media, buttons, and other tool-sent messages land in the correct
* topic instead of the General Topic.
*
* Unlike Slack, we do not gate on `replyToMode` here: Telegram forum topics
* are persistent sub-channels (not ephemeral reply threads), so auto-injection
* should always apply when the target chat matches.
*/
function resolveTelegramAutoThreadId(params) {
	const context = params.toolContext;
	if (!context?.currentThreadTs || !context.currentChannelId) return;
	const parsedTo = parseTelegramTarget(params.to);
	const parsedChannel = parseTelegramTarget(context.currentChannelId);
	if (parsedTo.chatId.toLowerCase() !== parsedChannel.chatId.toLowerCase()) return;
	return context.currentThreadTs;
}
function resolveAttachmentMaxBytes(params) {
	const accountId = typeof params.accountId === "string" ? params.accountId.trim() : "";
	const channelCfg = params.cfg.channels?.[params.channel];
	const channelObj = channelCfg && typeof channelCfg === "object" ? channelCfg : void 0;
	const channelMediaMax = typeof channelObj?.mediaMaxMb === "number" ? channelObj.mediaMaxMb : void 0;
	const accountsObj = channelObj?.accounts && typeof channelObj.accounts === "object" ? channelObj.accounts : void 0;
	const accountCfg = accountId && accountsObj ? accountsObj[accountId] : void 0;
	const accountMediaMax = accountCfg && typeof accountCfg === "object" ? accountCfg.mediaMaxMb : void 0;
	const limitMb = (typeof accountMediaMax === "number" ? accountMediaMax : void 0) ?? channelMediaMax ?? params.cfg.agents?.defaults?.mediaMaxMb;
	return typeof limitMb === "number" ? limitMb * 1024 * 1024 : void 0;
}
function inferAttachmentFilename(params) {
	const mediaHint = params.mediaHint?.trim();
	if (mediaHint) try {
		if (mediaHint.startsWith("file://")) {
			const filePath = fileURLToPath(mediaHint);
			const base = path.basename(filePath);
			if (base) return base;
		} else if (/^https?:\/\//i.test(mediaHint)) {
			const url = new URL(mediaHint);
			const base = path.basename(url.pathname);
			if (base) return base;
		} else {
			const base = path.basename(mediaHint);
			if (base) return base;
		}
	} catch {}
	const ext = params.contentType ? extensionForMime(params.contentType) : void 0;
	return ext ? `attachment${ext}` : "attachment";
}
function normalizeBase64Payload(params) {
	if (!params.base64) return {
		base64: params.base64,
		contentType: params.contentType
	};
	const match = /^data:([^;]+);base64,(.*)$/i.exec(params.base64.trim());
	if (!match) return {
		base64: params.base64,
		contentType: params.contentType
	};
	const [, mime, payload] = match;
	return {
		base64: payload,
		contentType: params.contentType ?? mime
	};
}
async function hydrateAttachmentPayload(params) {
	const contentTypeParam = params.contentTypeParam ?? void 0;
	const rawBuffer = readStringParam(params.args, "buffer", { trim: false });
	const normalized = normalizeBase64Payload({
		base64: rawBuffer,
		contentType: contentTypeParam ?? void 0
	});
	if (normalized.base64 !== rawBuffer && normalized.base64) {
		params.args.buffer = normalized.base64;
		if (normalized.contentType && !contentTypeParam) params.args.contentType = normalized.contentType;
	}
	const filename = readStringParam(params.args, "filename");
	const mediaSource = (params.mediaHint ?? void 0) || (params.fileHint ?? void 0);
	if (!params.dryRun && !readStringParam(params.args, "buffer", { trim: false }) && mediaSource) {
		const media = await loadWebMedia(mediaSource, {
			maxBytes: resolveAttachmentMaxBytes({
				cfg: params.cfg,
				channel: params.channel,
				accountId: params.accountId
			}),
			sandboxValidated: true,
			readFile: (filePath) => fs.readFile(filePath)
		});
		params.args.buffer = media.buffer.toString("base64");
		if (!contentTypeParam && media.contentType) params.args.contentType = media.contentType;
		if (!filename) params.args.filename = inferAttachmentFilename({
			mediaHint: media.fileName ?? mediaSource,
			contentType: media.contentType ?? contentTypeParam ?? void 0
		});
	} else if (!filename) params.args.filename = inferAttachmentFilename({
		mediaHint: mediaSource,
		contentType: contentTypeParam ?? void 0
	});
}
async function normalizeSandboxMediaParams(params) {
	const sandboxRoot = params.sandboxRoot?.trim();
	for (const key of [
		"media",
		"path",
		"filePath"
	]) {
		const raw = readStringParam(params.args, key, { trim: false });
		if (!raw) continue;
		assertMediaNotDataUrl(raw);
		if (!sandboxRoot) continue;
		const normalized = await resolveSandboxedMediaSource({
			media: raw,
			sandboxRoot
		});
		if (normalized !== raw) params.args[key] = normalized;
	}
}
async function normalizeSandboxMediaList(params) {
	const sandboxRoot = params.sandboxRoot?.trim();
	const normalized = [];
	const seen = /* @__PURE__ */ new Set();
	for (const value of params.values) {
		const raw = value?.trim();
		if (!raw) continue;
		assertMediaNotDataUrl(raw);
		const resolved = sandboxRoot ? await resolveSandboxedMediaSource({
			media: raw,
			sandboxRoot
		}) : raw;
		if (seen.has(resolved)) continue;
		seen.add(resolved);
		normalized.push(resolved);
	}
	return normalized;
}
async function hydrateAttachmentActionPayload(params) {
	const mediaHint = readStringParam(params.args, "media", { trim: false });
	const fileHint = readStringParam(params.args, "path", { trim: false }) ?? readStringParam(params.args, "filePath", { trim: false });
	const contentTypeParam = readStringParam(params.args, "contentType") ?? readStringParam(params.args, "mimeType");
	if (params.allowMessageCaptionFallback) {
		const caption = readStringParam(params.args, "caption", { allowEmpty: true })?.trim();
		const message = readStringParam(params.args, "message", { allowEmpty: true })?.trim();
		if (!caption && message) params.args.caption = message;
	}
	await hydrateAttachmentPayload({
		cfg: params.cfg,
		channel: params.channel,
		accountId: params.accountId,
		args: params.args,
		dryRun: params.dryRun,
		contentTypeParam,
		mediaHint,
		fileHint
	});
}
async function hydrateSetGroupIconParams(params) {
	if (params.action !== "setGroupIcon") return;
	await hydrateAttachmentActionPayload(params);
}
async function hydrateSendAttachmentParams(params) {
	if (params.action !== "sendAttachment") return;
	await hydrateAttachmentActionPayload({
		...params,
		allowMessageCaptionFallback: true
	});
}
function parseButtonsParam(params) {
	const raw = params.buttons;
	if (typeof raw !== "string") return;
	const trimmed = raw.trim();
	if (!trimmed) {
		delete params.buttons;
		return;
	}
	try {
		params.buttons = JSON.parse(trimmed);
	} catch {
		throw new Error("--buttons must be valid JSON");
	}
}
function parseCardParam(params) {
	const raw = params.card;
	if (typeof raw !== "string") return;
	const trimmed = raw.trim();
	if (!trimmed) {
		delete params.card;
		return;
	}
	try {
		params.card = JSON.parse(trimmed);
	} catch {
		throw new Error("--card must be valid JSON");
	}
}
function parseComponentsParam(params) {
	const raw = params.components;
	if (typeof raw !== "string") return;
	const trimmed = raw.trim();
	if (!trimmed) {
		delete params.components;
		return;
	}
	try {
		params.components = JSON.parse(trimmed);
	} catch {
		throw new Error("--components must be valid JSON");
	}
}

//#endregion
//#region src/infra/outbound/channel-adapters.ts
var CrossContextContainer = class extends DiscordUiContainer {
	constructor({ originLabel, message, cfg, accountId }) {
		const trimmed = message.trim();
		const components = [];
		if (trimmed) {
			components.push(new TextDisplay(message));
			components.push(new Separator({
				divider: true,
				spacing: "small"
			}));
		}
		components.push(new TextDisplay(`*From ${originLabel}*`));
		super({
			cfg,
			accountId,
			components
		});
	}
};
const DEFAULT_ADAPTER = { supportsComponentsV2: false };
const DISCORD_ADAPTER = {
	supportsComponentsV2: true,
	buildCrossContextComponents: ({ originLabel, message, cfg, accountId }) => [new CrossContextContainer({
		originLabel,
		message,
		cfg,
		accountId
	})]
};
function getChannelMessageAdapter(channel) {
	if (channel === "discord") return DISCORD_ADAPTER;
	return DEFAULT_ADAPTER;
}

//#endregion
//#region src/infra/outbound/directory-cache.ts
function buildDirectoryCacheKey(key) {
	const signature = key.signature ?? "default";
	return `${key.channel}:${key.accountId ?? "default"}:${key.kind}:${key.source}:${signature}`;
}
var DirectoryCache = class {
	constructor(ttlMs, maxSize = 2e3) {
		this.ttlMs = ttlMs;
		this.cache = /* @__PURE__ */ new Map();
		this.lastConfigRef = null;
		this.maxSize = Math.max(1, Math.floor(maxSize));
	}
	get(key, cfg) {
		this.resetIfConfigChanged(cfg);
		this.pruneExpired(Date.now());
		const entry = this.cache.get(key);
		if (!entry) return;
		return entry.value;
	}
	set(key, value, cfg) {
		this.resetIfConfigChanged(cfg);
		const now = Date.now();
		this.pruneExpired(now);
		if (this.cache.has(key)) this.cache.delete(key);
		this.cache.set(key, {
			value,
			fetchedAt: now
		});
		this.evictToMaxSize();
	}
	clearMatching(match) {
		for (const key of this.cache.keys()) if (match(key)) this.cache.delete(key);
	}
	clear(cfg) {
		this.cache.clear();
		if (cfg) this.lastConfigRef = cfg;
	}
	resetIfConfigChanged(cfg) {
		if (this.lastConfigRef && this.lastConfigRef !== cfg) this.cache.clear();
		this.lastConfigRef = cfg;
	}
	pruneExpired(now) {
		if (this.ttlMs <= 0) return;
		for (const [cacheKey, entry] of this.cache.entries()) if (now - entry.fetchedAt > this.ttlMs) this.cache.delete(cacheKey);
	}
	evictToMaxSize() {
		while (this.cache.size > this.maxSize) {
			const oldestKey = this.cache.keys().next().value;
			if (typeof oldestKey !== "string") break;
			this.cache.delete(oldestKey);
		}
	}
};

//#endregion
//#region src/infra/outbound/target-resolver.ts
async function resolveChannelTarget(params) {
	return resolveMessagingTarget(params);
}
const directoryCache = new DirectoryCache(1800 * 1e3);
function normalizeQuery(value) {
	return value.trim().toLowerCase();
}
function stripTargetPrefixes(value) {
	return value.replace(/^(channel|user):/i, "").replace(/^[@#]/, "").trim();
}
function formatTargetDisplay(params) {
	const plugin = getChannelPlugin(params.channel);
	if (plugin?.messaging?.formatTargetDisplay) return plugin.messaging.formatTargetDisplay({
		target: params.target,
		display: params.display,
		kind: params.kind
	});
	const trimmedTarget = params.target.trim();
	const lowered = trimmedTarget.toLowerCase();
	const display = params.display?.trim();
	const kind = params.kind ?? (lowered.startsWith("user:") ? "user" : lowered.startsWith("channel:") ? "group" : void 0);
	if (display) {
		if (display.startsWith("#") || display.startsWith("@")) return display;
		if (kind === "user") return `@${display}`;
		if (kind === "group" || kind === "channel") return `#${display}`;
		return display;
	}
	if (!trimmedTarget) return trimmedTarget;
	if (trimmedTarget.startsWith("#") || trimmedTarget.startsWith("@")) return trimmedTarget;
	const channelPrefix = `${params.channel}:`;
	const withoutPrefix = (trimmedTarget.toLowerCase().startsWith(channelPrefix) ? trimmedTarget.slice(channelPrefix.length) : trimmedTarget).replace(/^telegram:/i, "");
	if (/^channel:/i.test(withoutPrefix)) return `#${withoutPrefix.replace(/^channel:/i, "")}`;
	if (/^user:/i.test(withoutPrefix)) return `@${withoutPrefix.replace(/^user:/i, "")}`;
	return withoutPrefix;
}
function preserveTargetCase(channel, raw, normalized) {
	if (channel !== "slack") return normalized;
	const trimmed = raw.trim();
	if (/^channel:/i.test(trimmed) || /^user:/i.test(trimmed)) return trimmed;
	if (trimmed.startsWith("#")) return `channel:${trimmed.slice(1).trim()}`;
	if (trimmed.startsWith("@")) return `user:${trimmed.slice(1).trim()}`;
	return trimmed;
}
function detectTargetKind(channel, raw, preferred) {
	if (preferred) return preferred;
	const trimmed = raw.trim();
	if (!trimmed) return "group";
	if (trimmed.startsWith("@") || /^<@!?/.test(trimmed) || /^user:/i.test(trimmed)) return "user";
	if (trimmed.startsWith("#") || /^channel:/i.test(trimmed)) return "group";
	if ((channel === "bluebubbles" || channel === "imessage") && /^\+?\d{6,}$/.test(trimmed)) return "user";
	return "group";
}
function normalizeDirectoryEntryId(channel, entry) {
	return normalizeTargetForProvider(channel, entry.id) ?? entry.id.trim();
}
function matchesDirectoryEntry(params) {
	const query = normalizeQuery(params.query);
	if (!query) return false;
	return [
		stripTargetPrefixes(normalizeDirectoryEntryId(params.channel, params.entry)),
		params.entry.name ? stripTargetPrefixes(params.entry.name) : "",
		params.entry.handle ? stripTargetPrefixes(params.entry.handle) : ""
	].map((value) => normalizeQuery(value)).filter(Boolean).some((value) => value === query || value.includes(query));
}
function resolveMatch(params) {
	const matches = params.entries.filter((entry) => matchesDirectoryEntry({
		channel: params.channel,
		entry,
		query: params.query
	}));
	if (matches.length === 0) return { kind: "none" };
	if (matches.length === 1) return {
		kind: "single",
		entry: matches[0]
	};
	return {
		kind: "ambiguous",
		entries: matches
	};
}
async function listDirectoryEntries(params) {
	const directory = getChannelPlugin(params.channel)?.directory;
	if (!directory) return [];
	const runtime = params.runtime ?? defaultRuntime;
	const useLive = params.source === "live";
	const fn = params.kind === "user" ? useLive ? directory.listPeersLive ?? directory.listPeers : directory.listPeers : useLive ? directory.listGroupsLive ?? directory.listGroups : directory.listGroups;
	if (!fn) return [];
	return await fn({
		cfg: params.cfg,
		accountId: params.accountId ?? void 0,
		query: params.query ?? void 0,
		limit: void 0,
		runtime
	});
}
async function getDirectoryEntries(params) {
	const signature = buildTargetResolverSignature(params.channel);
	const cacheKey = buildDirectoryCacheKey({
		channel: params.channel,
		accountId: params.accountId,
		kind: params.kind,
		source: "cache",
		signature
	});
	const cached = directoryCache.get(cacheKey, params.cfg);
	if (cached) return cached;
	const entries = await listDirectoryEntries({
		cfg: params.cfg,
		channel: params.channel,
		accountId: params.accountId,
		kind: params.kind,
		query: params.query,
		runtime: params.runtime,
		source: "cache"
	});
	if (entries.length > 0 || !params.preferLiveOnMiss) {
		directoryCache.set(cacheKey, entries, params.cfg);
		return entries;
	}
	const liveKey = buildDirectoryCacheKey({
		channel: params.channel,
		accountId: params.accountId,
		kind: params.kind,
		source: "live",
		signature
	});
	const liveEntries = await listDirectoryEntries({
		cfg: params.cfg,
		channel: params.channel,
		accountId: params.accountId,
		kind: params.kind,
		query: params.query,
		runtime: params.runtime,
		source: "live"
	});
	directoryCache.set(liveKey, liveEntries, params.cfg);
	directoryCache.set(cacheKey, liveEntries, params.cfg);
	return liveEntries;
}
function pickAmbiguousMatch(entries, mode) {
	if (entries.length === 0) return null;
	if (mode === "first") return entries[0] ?? null;
	const ranked = entries.map((entry) => ({
		entry,
		rank: typeof entry.rank === "number" ? entry.rank : 0
	}));
	const bestRank = Math.max(...ranked.map((item) => item.rank));
	return ranked.find((item) => item.rank === bestRank)?.entry ?? entries[0] ?? null;
}
async function resolveMessagingTarget(params) {
	const raw = normalizeChannelTargetInput(params.input);
	if (!raw) return {
		ok: false,
		error: /* @__PURE__ */ new Error("Target is required")
	};
	const plugin = getChannelPlugin(params.channel);
	const providerLabel = plugin?.meta?.label ?? params.channel;
	const hint = plugin?.messaging?.targetResolver?.hint;
	const kind = detectTargetKind(params.channel, raw, params.preferredKind);
	const normalized = normalizeTargetForProvider(params.channel, raw) ?? raw;
	const looksLikeTargetId = () => {
		const trimmed = raw.trim();
		if (!trimmed) return false;
		const lookup = plugin?.messaging?.targetResolver?.looksLikeId;
		if (lookup) return lookup(trimmed, normalized);
		if (/^(channel|group|user):/i.test(trimmed)) return true;
		if (/^[@#]/.test(trimmed)) return true;
		if (/^\+?\d{6,}$/.test(trimmed)) {
			if (params.channel === "bluebubbles" || params.channel === "imessage") return false;
			return true;
		}
		if (trimmed.includes("@thread")) return true;
		if (/^(conversation|user):/i.test(trimmed)) return true;
		return false;
	};
	if (looksLikeTargetId()) return {
		ok: true,
		target: {
			to: preserveTargetCase(params.channel, raw, normalized),
			kind,
			display: stripTargetPrefixes(raw),
			source: "normalized"
		}
	};
	const query = stripTargetPrefixes(raw);
	const entries = await getDirectoryEntries({
		cfg: params.cfg,
		channel: params.channel,
		accountId: params.accountId,
		kind: kind === "user" ? "user" : "group",
		query,
		runtime: params.runtime,
		preferLiveOnMiss: true
	});
	const match = resolveMatch({
		channel: params.channel,
		entries,
		query
	});
	if (match.kind === "single") {
		const entry = match.entry;
		return {
			ok: true,
			target: {
				to: normalizeDirectoryEntryId(params.channel, entry),
				kind,
				display: entry.name ?? entry.handle ?? stripTargetPrefixes(entry.id),
				source: "directory"
			}
		};
	}
	if (match.kind === "ambiguous") {
		const mode = params.resolveAmbiguous ?? "error";
		if (mode !== "error") {
			const best = pickAmbiguousMatch(match.entries, mode);
			if (best) return {
				ok: true,
				target: {
					to: normalizeDirectoryEntryId(params.channel, best),
					kind,
					display: best.name ?? best.handle ?? stripTargetPrefixes(best.id),
					source: "directory"
				}
			};
		}
		return {
			ok: false,
			error: ambiguousTargetError(providerLabel, raw, hint),
			candidates: match.entries
		};
	}
	if ((params.channel === "bluebubbles" || params.channel === "imessage") && /^\+?\d{6,}$/.test(query)) return {
		ok: true,
		target: {
			to: preserveTargetCase(params.channel, raw, normalized),
			kind,
			display: stripTargetPrefixes(raw),
			source: "normalized"
		}
	};
	return {
		ok: false,
		error: unknownTargetError(providerLabel, raw, hint)
	};
}
async function lookupDirectoryDisplay(params) {
	const normalized = normalizeTargetForProvider(params.channel, params.targetId) ?? params.targetId;
	const [groups, users] = await Promise.all([getDirectoryEntries({
		cfg: params.cfg,
		channel: params.channel,
		accountId: params.accountId,
		kind: "group",
		runtime: params.runtime,
		preferLiveOnMiss: false
	}), getDirectoryEntries({
		cfg: params.cfg,
		channel: params.channel,
		accountId: params.accountId,
		kind: "user",
		runtime: params.runtime,
		preferLiveOnMiss: false
	})]);
	const findMatch = (candidates) => candidates.find((candidate) => normalizeDirectoryEntryId(params.channel, candidate) === normalized);
	const entry = findMatch(groups) ?? findMatch(users);
	return entry?.name ?? entry?.handle ?? void 0;
}

//#endregion
//#region src/infra/outbound/outbound-policy.ts
const CONTEXT_GUARDED_ACTIONS = new Set([
	"send",
	"poll",
	"reply",
	"sendWithEffect",
	"sendAttachment",
	"thread-create",
	"thread-reply",
	"sticker"
]);
const CONTEXT_MARKER_ACTIONS = new Set([
	"send",
	"poll",
	"reply",
	"sendWithEffect",
	"sendAttachment",
	"thread-reply",
	"sticker"
]);
function resolveContextGuardTarget(action, params) {
	if (!CONTEXT_GUARDED_ACTIONS.has(action)) return;
	if (action === "thread-reply" || action === "thread-create") {
		if (typeof params.channelId === "string") return params.channelId;
		if (typeof params.to === "string") return params.to;
		return;
	}
	if (typeof params.to === "string") return params.to;
	if (typeof params.channelId === "string") return params.channelId;
}
function normalizeTarget(channel, raw) {
	return normalizeTargetForProvider(channel, raw) ?? raw.trim().toLowerCase();
}
function isCrossContextTarget(params) {
	const currentTarget = params.toolContext?.currentChannelId?.trim();
	if (!currentTarget) return false;
	const normalizedTarget = normalizeTarget(params.channel, params.target);
	const normalizedCurrent = normalizeTarget(params.channel, currentTarget);
	if (!normalizedTarget || !normalizedCurrent) return false;
	return normalizedTarget !== normalizedCurrent;
}
function enforceCrossContextPolicy(params) {
	const currentTarget = params.toolContext?.currentChannelId?.trim();
	if (!currentTarget) return;
	if (!CONTEXT_GUARDED_ACTIONS.has(params.action)) return;
	if (params.cfg.tools?.message?.allowCrossContextSend) return;
	const currentProvider = params.toolContext?.currentChannelProvider;
	const allowWithinProvider = params.cfg.tools?.message?.crossContext?.allowWithinProvider !== false;
	const allowAcrossProviders = params.cfg.tools?.message?.crossContext?.allowAcrossProviders === true;
	if (currentProvider && currentProvider !== params.channel) {
		if (!allowAcrossProviders) throw new Error(`Cross-context messaging denied: action=${params.action} target provider "${params.channel}" while bound to "${currentProvider}".`);
		return;
	}
	if (allowWithinProvider) return;
	const target = resolveContextGuardTarget(params.action, params.args);
	if (!target) return;
	if (!isCrossContextTarget({
		channel: params.channel,
		target,
		toolContext: params.toolContext
	})) return;
	throw new Error(`Cross-context messaging denied: action=${params.action} target="${target}" while bound to "${currentTarget}" (channel=${params.channel}).`);
}
async function buildCrossContextDecoration(params) {
	if (!params.toolContext?.currentChannelId) return null;
	if (params.toolContext.skipCrossContextDecoration) return null;
	if (!isCrossContextTarget(params)) return null;
	const markerConfig = params.cfg.tools?.message?.crossContext?.marker;
	if (markerConfig?.enabled === false) return null;
	const currentName = await lookupDirectoryDisplay({
		cfg: params.cfg,
		channel: params.channel,
		targetId: params.toolContext.currentChannelId,
		accountId: params.accountId ?? void 0
	}) ?? params.toolContext.currentChannelId;
	const originLabel = formatTargetDisplay({
		channel: params.channel,
		target: params.toolContext.currentChannelId,
		display: currentName
	});
	const prefixTemplate = markerConfig?.prefix ?? "[from {channel}] ";
	const suffixTemplate = markerConfig?.suffix ?? "";
	const prefix = prefixTemplate.replaceAll("{channel}", originLabel);
	const suffix = suffixTemplate.replaceAll("{channel}", originLabel);
	const adapter = getChannelMessageAdapter(params.channel);
	return {
		prefix,
		suffix,
		componentsBuilder: adapter.supportsComponentsV2 ? adapter.buildCrossContextComponents ? (message) => adapter.buildCrossContextComponents({
			originLabel,
			message,
			cfg: params.cfg,
			accountId: params.accountId ?? void 0
		}) : void 0 : void 0
	};
}
function shouldApplyCrossContextMarker(action) {
	return CONTEXT_MARKER_ACTIONS.has(action);
}
function applyCrossContextDecoration(params) {
	if (params.preferComponents && params.decoration.componentsBuilder) return {
		message: params.message,
		componentsBuilder: params.decoration.componentsBuilder,
		usedComponents: true
	};
	return {
		message: `${params.decoration.prefix}${params.message}${params.decoration.suffix}`,
		usedComponents: false
	};
}

//#endregion
//#region src/infra/outbound/message.ts
async function resolveRequiredChannel(params) {
	const channel = params.channel?.trim() ? normalizeChannelId(params.channel) : (await resolveMessageChannelSelection({ cfg: params.cfg })).channel;
	if (!channel) throw new Error(`Unknown channel: ${params.channel}`);
	return channel;
}
function resolveRequiredPlugin(channel) {
	const plugin = getChannelPlugin(channel);
	if (!plugin) throw new Error(`Unknown channel: ${channel}`);
	return plugin;
}
function resolveGatewayOptions(opts) {
	return {
		url: opts?.mode === GATEWAY_CLIENT_MODES.BACKEND || opts?.clientName === GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT ? void 0 : opts?.url,
		token: opts?.token,
		timeoutMs: typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? Math.max(1, Math.floor(opts.timeoutMs)) : 1e4,
		clientName: opts?.clientName ?? GATEWAY_CLIENT_NAMES.CLI,
		clientDisplayName: opts?.clientDisplayName,
		mode: opts?.mode ?? GATEWAY_CLIENT_MODES.CLI
	};
}
async function callMessageGateway(params) {
	const gateway = resolveGatewayOptions(params.gateway);
	return await callGateway({
		url: gateway.url,
		token: gateway.token,
		method: params.method,
		params: params.params,
		timeoutMs: gateway.timeoutMs,
		clientName: gateway.clientName,
		clientDisplayName: gateway.clientDisplayName,
		mode: gateway.mode
	});
}
async function sendMessage(params) {
	const cfg = params.cfg ?? loadConfig();
	const channel = await resolveRequiredChannel({
		cfg,
		channel: params.channel
	});
	const deliveryMode = resolveRequiredPlugin(channel).outbound?.deliveryMode ?? "direct";
	const normalizedPayloads = normalizeReplyPayloadsForDelivery([{
		text: params.content,
		mediaUrl: params.mediaUrl,
		mediaUrls: params.mediaUrls
	}]);
	const mirrorText = normalizedPayloads.map((payload) => payload.text).filter(Boolean).join("\n");
	const mirrorMediaUrls = normalizedPayloads.flatMap((payload) => payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []));
	const primaryMediaUrl = mirrorMediaUrls[0] ?? params.mediaUrl ?? null;
	if (params.dryRun) return {
		channel,
		to: params.to,
		via: deliveryMode === "gateway" ? "gateway" : "direct",
		mediaUrl: primaryMediaUrl,
		mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0,
		dryRun: true
	};
	if (deliveryMode !== "gateway") {
		const outboundChannel = channel;
		const resolvedTarget = resolveOutboundTarget({
			channel: outboundChannel,
			to: params.to,
			cfg,
			accountId: params.accountId,
			mode: "explicit"
		});
		if (!resolvedTarget.ok) throw resolvedTarget.error;
		const results = await deliverOutboundPayloads({
			cfg,
			channel: outboundChannel,
			to: resolvedTarget.to,
			agentId: params.agentId,
			accountId: params.accountId,
			payloads: normalizedPayloads,
			replyToId: params.replyToId,
			threadId: params.threadId,
			gifPlayback: params.gifPlayback,
			deps: params.deps,
			bestEffort: params.bestEffort,
			abortSignal: params.abortSignal,
			silent: params.silent,
			mirror: params.mirror ? {
				...params.mirror,
				text: mirrorText || params.content,
				mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0
			} : void 0
		});
		return {
			channel,
			to: params.to,
			via: "direct",
			mediaUrl: primaryMediaUrl,
			mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0,
			result: results.at(-1)
		};
	}
	const result = await callMessageGateway({
		gateway: params.gateway,
		method: "send",
		params: {
			to: params.to,
			message: params.content,
			mediaUrl: params.mediaUrl,
			mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : params.mediaUrls,
			gifPlayback: params.gifPlayback,
			accountId: params.accountId,
			channel,
			sessionKey: params.mirror?.sessionKey,
			idempotencyKey: params.idempotencyKey ?? randomIdempotencyKey()
		}
	});
	return {
		channel,
		to: params.to,
		via: "gateway",
		mediaUrl: primaryMediaUrl,
		mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0,
		result
	};
}
async function sendPoll(params) {
	const channel = await resolveRequiredChannel({
		cfg: params.cfg ?? loadConfig(),
		channel: params.channel
	});
	const pollInput = {
		question: params.question,
		options: params.options,
		maxSelections: params.maxSelections,
		durationSeconds: params.durationSeconds,
		durationHours: params.durationHours
	};
	const outbound = resolveRequiredPlugin(channel)?.outbound;
	if (!outbound?.sendPoll) throw new Error(`Unsupported poll channel: ${channel}`);
	const normalized = outbound.pollMaxOptions ? normalizePollInput(pollInput, { maxOptions: outbound.pollMaxOptions }) : normalizePollInput(pollInput);
	if (params.dryRun) return {
		channel,
		to: params.to,
		question: normalized.question,
		options: normalized.options,
		maxSelections: normalized.maxSelections,
		durationSeconds: normalized.durationSeconds ?? null,
		durationHours: normalized.durationHours ?? null,
		via: "gateway",
		dryRun: true
	};
	const result = await callMessageGateway({
		gateway: params.gateway,
		method: "poll",
		params: {
			to: params.to,
			question: normalized.question,
			options: normalized.options,
			maxSelections: normalized.maxSelections,
			durationSeconds: normalized.durationSeconds,
			durationHours: normalized.durationHours,
			threadId: params.threadId,
			silent: params.silent,
			isAnonymous: params.isAnonymous,
			channel,
			accountId: params.accountId,
			idempotencyKey: params.idempotencyKey ?? randomIdempotencyKey()
		}
	});
	return {
		channel,
		to: params.to,
		question: normalized.question,
		options: normalized.options,
		maxSelections: normalized.maxSelections,
		durationSeconds: normalized.durationSeconds ?? null,
		durationHours: normalized.durationHours ?? null,
		via: "gateway",
		result
	};
}

//#endregion
//#region src/infra/outbound/tool-payload.ts
function extractToolPayload(result) {
	if (result.details !== void 0) return result.details;
	const text = (Array.isArray(result.content) ? result.content.find((block) => block && typeof block === "object" && block.type === "text" && typeof block.text === "string") : void 0)?.text;
	if (text) try {
		return JSON.parse(text);
	} catch {
		return text;
	}
	return result.content ?? result;
}

//#endregion
//#region src/infra/outbound/outbound-send-service.ts
async function tryHandleWithPluginAction(params) {
	if (params.ctx.dryRun) return null;
	const handled = await dispatchChannelMessageAction({
		channel: params.ctx.channel,
		action: params.action,
		cfg: params.ctx.cfg,
		params: params.ctx.params,
		accountId: params.ctx.accountId ?? void 0,
		gateway: params.ctx.gateway,
		toolContext: params.ctx.toolContext,
		dryRun: params.ctx.dryRun
	});
	if (!handled) return null;
	await params.onHandled?.();
	return {
		handledBy: "plugin",
		payload: extractToolPayload(handled),
		toolResult: handled
	};
}
async function executeSendAction(params) {
	throwIfAborted(params.ctx.abortSignal);
	const pluginHandled = await tryHandleWithPluginAction({
		ctx: params.ctx,
		action: "send",
		onHandled: async () => {
			if (!params.ctx.mirror) return;
			const mirrorText = params.ctx.mirror.text ?? params.message;
			const mirrorMediaUrls = params.ctx.mirror.mediaUrls ?? params.mediaUrls ?? (params.mediaUrl ? [params.mediaUrl] : void 0);
			await appendAssistantMessageToSessionTranscript({
				agentId: params.ctx.mirror.agentId,
				sessionKey: params.ctx.mirror.sessionKey,
				text: mirrorText,
				mediaUrls: mirrorMediaUrls
			});
		}
	});
	if (pluginHandled) return pluginHandled;
	throwIfAborted(params.ctx.abortSignal);
	const result = await sendMessage({
		cfg: params.ctx.cfg,
		to: params.to,
		content: params.message,
		agentId: params.ctx.agentId,
		mediaUrl: params.mediaUrl || void 0,
		mediaUrls: params.mediaUrls,
		channel: params.ctx.channel || void 0,
		accountId: params.ctx.accountId ?? void 0,
		replyToId: params.replyToId,
		threadId: params.threadId,
		gifPlayback: params.gifPlayback,
		dryRun: params.ctx.dryRun,
		bestEffort: params.bestEffort ?? void 0,
		deps: params.ctx.deps,
		gateway: params.ctx.gateway,
		mirror: params.ctx.mirror,
		abortSignal: params.ctx.abortSignal,
		silent: params.ctx.silent
	});
	return {
		handledBy: "core",
		payload: result,
		sendResult: result
	};
}
async function executePollAction(params) {
	const pluginHandled = await tryHandleWithPluginAction({
		ctx: params.ctx,
		action: "poll"
	});
	if (pluginHandled) return pluginHandled;
	const result = await sendPoll({
		cfg: params.ctx.cfg,
		to: params.to,
		question: params.question,
		options: params.options,
		maxSelections: params.maxSelections,
		durationSeconds: params.durationSeconds ?? void 0,
		durationHours: params.durationHours ?? void 0,
		channel: params.ctx.channel,
		accountId: params.ctx.accountId ?? void 0,
		threadId: params.threadId ?? void 0,
		silent: params.ctx.silent ?? void 0,
		isAnonymous: params.isAnonymous ?? void 0,
		dryRun: params.ctx.dryRun,
		gateway: params.ctx.gateway
	});
	return {
		handledBy: "core",
		payload: result,
		pollResult: result
	};
}

//#endregion
//#region src/infra/outbound/outbound-session.ts
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i;
const SLACK_CHANNEL_TYPE_CACHE = /* @__PURE__ */ new Map();
function looksLikeUuid(value) {
	if (UUID_RE.test(value) || UUID_COMPACT_RE.test(value)) return true;
	const compact = value.replace(/-/g, "");
	if (!/^[0-9a-f]+$/i.test(compact)) return false;
	return /[a-f]/i.test(compact);
}
function normalizeThreadId(value) {
	if (value == null) return;
	if (typeof value === "number") {
		if (!Number.isFinite(value)) return;
		return String(Math.trunc(value));
	}
	const trimmed = value.trim();
	return trimmed ? trimmed : void 0;
}
function stripProviderPrefix(raw, channel) {
	const trimmed = raw.trim();
	const lower = trimmed.toLowerCase();
	const prefix = `${channel.toLowerCase()}:`;
	if (lower.startsWith(prefix)) return trimmed.slice(prefix.length).trim();
	return trimmed;
}
function stripKindPrefix(raw) {
	return raw.replace(/^(user|channel|group|conversation|room|dm):/i, "").trim();
}
function inferPeerKind(params) {
	const resolvedKind = params.resolvedTarget?.kind;
	if (resolvedKind === "user") return "direct";
	if (resolvedKind === "channel") return "channel";
	if (resolvedKind === "group") {
		const chatTypes = getChannelPlugin(params.channel)?.capabilities?.chatTypes ?? [];
		const supportsChannel = chatTypes.includes("channel");
		const supportsGroup = chatTypes.includes("group");
		if (supportsChannel && !supportsGroup) return "channel";
		return "group";
	}
	return "direct";
}
function buildBaseSessionKey(params) {
	return buildAgentSessionKey({
		agentId: params.agentId,
		channel: params.channel,
		accountId: params.accountId,
		peer: params.peer,
		dmScope: params.cfg.session?.dmScope ?? "main",
		identityLinks: params.cfg.session?.identityLinks
	});
}
async function resolveSlackChannelType(params) {
	const channelId = params.channelId.trim();
	if (!channelId) return "unknown";
	const cached = SLACK_CHANNEL_TYPE_CACHE.get(`${params.accountId ?? "default"}:${channelId}`);
	if (cached) return cached;
	const account = resolveSlackAccount({
		cfg: params.cfg,
		accountId: params.accountId
	});
	const groupChannels = normalizeAllowListLower(account.dm?.groupChannels);
	const channelIdLower = channelId.toLowerCase();
	if (groupChannels.includes(channelIdLower) || groupChannels.includes(`slack:${channelIdLower}`) || groupChannels.includes(`channel:${channelIdLower}`) || groupChannels.includes(`group:${channelIdLower}`) || groupChannels.includes(`mpim:${channelIdLower}`)) {
		SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, "group");
		return "group";
	}
	if (Object.keys(account.channels ?? {}).some((key) => {
		const normalized = key.trim().toLowerCase();
		return normalized === channelIdLower || normalized === `channel:${channelIdLower}` || normalized.replace(/^#/, "") === channelIdLower;
	})) {
		SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, "channel");
		return "channel";
	}
	const token = account.botToken?.trim() || (typeof account.config.userToken === "string" ? account.config.userToken.trim() : "");
	if (!token) {
		SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, "unknown");
		return "unknown";
	}
	try {
		const channel = (await createSlackWebClient(token).conversations.info({ channel: channelId })).channel;
		const type = channel?.is_im ? "dm" : channel?.is_mpim ? "group" : "channel";
		SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, type);
		return type;
	} catch {
		SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, "unknown");
		return "unknown";
	}
}
async function resolveSlackSession(params) {
	const parsed = parseSlackTarget(params.target, { defaultKind: "channel" });
	if (!parsed) return null;
	const isDm = parsed.kind === "user";
	let peerKind = isDm ? "direct" : "channel";
	if (!isDm && /^G/i.test(parsed.id)) {
		const channelType = await resolveSlackChannelType({
			cfg: params.cfg,
			accountId: params.accountId,
			channelId: parsed.id
		});
		if (channelType === "group") peerKind = "group";
		if (channelType === "dm") peerKind = "direct";
	}
	const peer = {
		kind: peerKind,
		id: parsed.id
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "slack",
		accountId: params.accountId,
		peer
	});
	const threadId = normalizeThreadId(params.threadId ?? params.replyToId);
	return {
		sessionKey: resolveThreadSessionKeys({
			baseSessionKey,
			threadId
		}).sessionKey,
		baseSessionKey,
		peer,
		chatType: peerKind === "direct" ? "direct" : "channel",
		from: peerKind === "direct" ? `slack:${parsed.id}` : peerKind === "group" ? `slack:group:${parsed.id}` : `slack:channel:${parsed.id}`,
		to: peerKind === "direct" ? `user:${parsed.id}` : `channel:${parsed.id}`,
		threadId
	};
}
function resolveDiscordSession(params) {
	const parsed = parseDiscordTarget(params.target, { defaultKind: "channel" });
	if (!parsed) return null;
	const isDm = parsed.kind === "user";
	const peer = {
		kind: isDm ? "direct" : "channel",
		id: parsed.id
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "discord",
		accountId: params.accountId,
		peer
	});
	const explicitThreadId = normalizeThreadId(params.threadId);
	return {
		sessionKey: resolveThreadSessionKeys({
			baseSessionKey,
			threadId: explicitThreadId ?? normalizeThreadId(params.replyToId),
			useSuffix: false
		}).sessionKey,
		baseSessionKey,
		peer,
		chatType: isDm ? "direct" : "channel",
		from: isDm ? `discord:${parsed.id}` : `discord:channel:${parsed.id}`,
		to: isDm ? `user:${parsed.id}` : `channel:${parsed.id}`,
		threadId: explicitThreadId ?? void 0
	};
}
function resolveTelegramSession(params) {
	const parsed = parseTelegramTarget(params.target);
	const chatId = parsed.chatId.trim();
	if (!chatId) return null;
	const parsedThreadId = parsed.messageThreadId;
	const fallbackThreadId = normalizeThreadId(params.threadId);
	const resolvedThreadId = parsedThreadId ?? (fallbackThreadId ? Number.parseInt(fallbackThreadId, 10) : void 0);
	const chatType = resolveTelegramTargetChatType(params.target);
	const isGroup = chatType === "group" || chatType === "unknown" && params.resolvedTarget?.kind && params.resolvedTarget.kind !== "user";
	const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : chatId;
	const peer = {
		kind: isGroup ? "group" : "direct",
		id: peerId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "telegram",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: isGroup ? "group" : "direct",
		from: isGroup ? `telegram:group:${peerId}` : `telegram:${chatId}`,
		to: `telegram:${chatId}`,
		threadId: resolvedThreadId
	};
}
function resolveWhatsAppSession(params) {
	const normalized = normalizeWhatsAppTarget(params.target);
	if (!normalized) return null;
	const isGroup = isWhatsAppGroupJid(normalized);
	const peer = {
		kind: isGroup ? "group" : "direct",
		id: normalized
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "whatsapp",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: isGroup ? "group" : "direct",
		from: normalized,
		to: normalized
	};
}
function resolveSignalSession(params) {
	const stripped = stripProviderPrefix(params.target, "signal");
	const lowered = stripped.toLowerCase();
	if (lowered.startsWith("group:")) {
		const groupId = stripped.slice(6).trim();
		if (!groupId) return null;
		const peer = {
			kind: "group",
			id: groupId
		};
		const baseSessionKey = buildBaseSessionKey({
			cfg: params.cfg,
			agentId: params.agentId,
			channel: "signal",
			accountId: params.accountId,
			peer
		});
		return {
			sessionKey: baseSessionKey,
			baseSessionKey,
			peer,
			chatType: "group",
			from: `group:${groupId}`,
			to: `group:${groupId}`
		};
	}
	let recipient = stripped.trim();
	if (lowered.startsWith("username:")) recipient = stripped.slice(9).trim();
	else if (lowered.startsWith("u:")) recipient = stripped.slice(2).trim();
	if (!recipient) return null;
	const uuidCandidate = recipient.toLowerCase().startsWith("uuid:") ? recipient.slice(5) : recipient;
	const sender = resolveSignalSender({
		sourceUuid: looksLikeUuid(uuidCandidate) ? uuidCandidate : null,
		sourceNumber: looksLikeUuid(uuidCandidate) ? null : recipient
	});
	const peerId = sender ? resolveSignalPeerId(sender) : recipient;
	const displayRecipient = sender ? resolveSignalRecipient(sender) : recipient;
	const peer = {
		kind: "direct",
		id: peerId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "signal",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: "direct",
		from: `signal:${displayRecipient}`,
		to: `signal:${displayRecipient}`
	};
}
function resolveIMessageSession(params) {
	const parsed = parseIMessageTarget(params.target);
	if (parsed.kind === "handle") {
		const handle = normalizeIMessageHandle(parsed.to);
		if (!handle) return null;
		const peer = {
			kind: "direct",
			id: handle
		};
		const baseSessionKey = buildBaseSessionKey({
			cfg: params.cfg,
			agentId: params.agentId,
			channel: "imessage",
			accountId: params.accountId,
			peer
		});
		return {
			sessionKey: baseSessionKey,
			baseSessionKey,
			peer,
			chatType: "direct",
			from: `imessage:${handle}`,
			to: `imessage:${handle}`
		};
	}
	const peerId = parsed.kind === "chat_id" ? String(parsed.chatId) : parsed.kind === "chat_guid" ? parsed.chatGuid : parsed.chatIdentifier;
	if (!peerId) return null;
	const peer = {
		kind: "group",
		id: peerId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "imessage",
		accountId: params.accountId,
		peer
	});
	const toPrefix = parsed.kind === "chat_id" ? "chat_id" : parsed.kind === "chat_guid" ? "chat_guid" : "chat_identifier";
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: "group",
		from: `imessage:group:${peerId}`,
		to: `${toPrefix}:${peerId}`
	};
}
function resolveMatrixSession(params) {
	const stripped = stripProviderPrefix(params.target, "matrix");
	const isUser = params.resolvedTarget?.kind === "user" || stripped.startsWith("@") || /^user:/i.test(stripped);
	const rawId = stripKindPrefix(stripped);
	if (!rawId) return null;
	const peer = {
		kind: isUser ? "direct" : "channel",
		id: rawId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "matrix",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: isUser ? "direct" : "channel",
		from: isUser ? `matrix:${rawId}` : `matrix:channel:${rawId}`,
		to: `room:${rawId}`
	};
}
function resolveMSTeamsSession(params) {
	let trimmed = params.target.trim();
	if (!trimmed) return null;
	trimmed = trimmed.replace(/^(msteams|teams):/i, "").trim();
	const isUser = trimmed.toLowerCase().startsWith("user:");
	const rawId = stripKindPrefix(trimmed);
	if (!rawId) return null;
	const conversationId = rawId.split(";")[0] ?? rawId;
	const isChannel = !isUser && /@thread\.tacv2/i.test(conversationId);
	const peer = {
		kind: isUser ? "direct" : isChannel ? "channel" : "group",
		id: conversationId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "msteams",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: isUser ? "direct" : isChannel ? "channel" : "group",
		from: isUser ? `msteams:${conversationId}` : isChannel ? `msteams:channel:${conversationId}` : `msteams:group:${conversationId}`,
		to: isUser ? `user:${conversationId}` : `conversation:${conversationId}`
	};
}
function resolveMattermostSession(params) {
	let trimmed = params.target.trim();
	if (!trimmed) return null;
	trimmed = trimmed.replace(/^mattermost:/i, "").trim();
	const isUser = trimmed.toLowerCase().startsWith("user:") || trimmed.startsWith("@");
	if (trimmed.startsWith("@")) trimmed = trimmed.slice(1).trim();
	const rawId = stripKindPrefix(trimmed);
	if (!rawId) return null;
	const peer = {
		kind: isUser ? "direct" : "channel",
		id: rawId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "mattermost",
		accountId: params.accountId,
		peer
	});
	const threadId = normalizeThreadId(params.replyToId ?? params.threadId);
	return {
		sessionKey: resolveThreadSessionKeys({
			baseSessionKey,
			threadId
		}).sessionKey,
		baseSessionKey,
		peer,
		chatType: isUser ? "direct" : "channel",
		from: isUser ? `mattermost:${rawId}` : `mattermost:channel:${rawId}`,
		to: isUser ? `user:${rawId}` : `channel:${rawId}`,
		threadId
	};
}
function resolveBlueBubblesSession(params) {
	const stripped = stripProviderPrefix(params.target, "bluebubbles");
	const lower = stripped.toLowerCase();
	const isGroup = lower.startsWith("chat_id:") || lower.startsWith("chat_guid:") || lower.startsWith("chat_identifier:") || lower.startsWith("group:");
	const rawPeerId = isGroup ? stripKindPrefix(stripped) : stripped.replace(/^(imessage|sms|auto):/i, "");
	const peerId = isGroup ? rawPeerId.replace(/^(chat_id|chat_guid|chat_identifier):/i, "") : rawPeerId;
	if (!peerId) return null;
	const peer = {
		kind: isGroup ? "group" : "direct",
		id: peerId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "bluebubbles",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: isGroup ? "group" : "direct",
		from: isGroup ? `group:${peerId}` : `bluebubbles:${peerId}`,
		to: `bluebubbles:${stripped}`
	};
}
function resolveNextcloudTalkSession(params) {
	let trimmed = params.target.trim();
	if (!trimmed) return null;
	trimmed = trimmed.replace(/^(nextcloud-talk|nc-talk|nc):/i, "").trim();
	trimmed = trimmed.replace(/^room:/i, "").trim();
	if (!trimmed) return null;
	const peer = {
		kind: "group",
		id: trimmed
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "nextcloud-talk",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: "group",
		from: `nextcloud-talk:room:${trimmed}`,
		to: `nextcloud-talk:${trimmed}`
	};
}
function resolveZaloSession(params) {
	const trimmed = stripProviderPrefix(params.target, "zalo").replace(/^(zl):/i, "").trim();
	if (!trimmed) return null;
	const isGroup = trimmed.toLowerCase().startsWith("group:");
	const peerId = stripKindPrefix(trimmed);
	const peer = {
		kind: isGroup ? "group" : "direct",
		id: peerId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "zalo",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: isGroup ? "group" : "direct",
		from: isGroup ? `zalo:group:${peerId}` : `zalo:${peerId}`,
		to: `zalo:${peerId}`
	};
}
function resolveZalouserSession(params) {
	const trimmed = stripProviderPrefix(params.target, "zalouser").replace(/^(zlu):/i, "").trim();
	if (!trimmed) return null;
	const isGroup = trimmed.toLowerCase().startsWith("group:");
	const peerId = stripKindPrefix(trimmed);
	const peer = {
		kind: isGroup ? "group" : "direct",
		id: peerId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "zalouser",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: isGroup ? "group" : "direct",
		from: isGroup ? `zalouser:group:${peerId}` : `zalouser:${peerId}`,
		to: `zalouser:${peerId}`
	};
}
function resolveNostrSession(params) {
	const trimmed = stripProviderPrefix(params.target, "nostr").trim();
	if (!trimmed) return null;
	const peer = {
		kind: "direct",
		id: trimmed
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "nostr",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: "direct",
		from: `nostr:${trimmed}`,
		to: `nostr:${trimmed}`
	};
}
function normalizeTlonShip(raw) {
	const trimmed = raw.trim();
	if (!trimmed) return trimmed;
	return trimmed.startsWith("~") ? trimmed : `~${trimmed}`;
}
function resolveTlonSession(params) {
	let trimmed = stripProviderPrefix(params.target, "tlon");
	trimmed = trimmed.trim();
	if (!trimmed) return null;
	const lower = trimmed.toLowerCase();
	let isGroup = lower.startsWith("group:") || lower.startsWith("room:") || lower.startsWith("chat/");
	let peerId = trimmed;
	if (lower.startsWith("group:") || lower.startsWith("room:")) {
		peerId = trimmed.replace(/^(group|room):/i, "").trim();
		if (!peerId.startsWith("chat/")) {
			const parts = peerId.split("/").filter(Boolean);
			if (parts.length === 2) peerId = `chat/${normalizeTlonShip(parts[0])}/${parts[1]}`;
		}
		isGroup = true;
	} else if (lower.startsWith("dm:")) {
		peerId = normalizeTlonShip(trimmed.slice(3));
		isGroup = false;
	} else if (lower.startsWith("chat/")) {
		peerId = trimmed;
		isGroup = true;
	} else if (trimmed.includes("/")) {
		const parts = trimmed.split("/").filter(Boolean);
		if (parts.length === 2) {
			peerId = `chat/${normalizeTlonShip(parts[0])}/${parts[1]}`;
			isGroup = true;
		}
	} else peerId = normalizeTlonShip(trimmed);
	const peer = {
		kind: isGroup ? "group" : "direct",
		id: peerId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "tlon",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: isGroup ? "group" : "direct",
		from: isGroup ? `tlon:group:${peerId}` : `tlon:${peerId}`,
		to: `tlon:${peerId}`
	};
}
/**
* Feishu ID formats:
* - oc_xxx: chat_id (group chat)
* - ou_xxx: user open_id (DM)
* - on_xxx: user union_id (DM)
* - cli_xxx: app_id (not a valid send target)
*/
function resolveFeishuSession(params) {
	let trimmed = stripProviderPrefix(params.target, "feishu");
	trimmed = stripProviderPrefix(trimmed, "lark").trim();
	if (!trimmed) return null;
	const lower = trimmed.toLowerCase();
	let isGroup = false;
	if (lower.startsWith("group:") || lower.startsWith("chat:")) {
		trimmed = trimmed.replace(/^(group|chat):/i, "").trim();
		isGroup = true;
	} else if (lower.startsWith("user:") || lower.startsWith("dm:")) {
		trimmed = trimmed.replace(/^(user|dm):/i, "").trim();
		isGroup = false;
	}
	const idLower = trimmed.toLowerCase();
	if (idLower.startsWith("oc_")) isGroup = true;
	else if (idLower.startsWith("ou_") || idLower.startsWith("on_")) isGroup = false;
	const peer = {
		kind: isGroup ? "group" : "direct",
		id: trimmed
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: "feishu",
		accountId: params.accountId,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: isGroup ? "group" : "direct",
		from: isGroup ? `feishu:group:${trimmed}` : `feishu:${trimmed}`,
		to: trimmed
	};
}
function resolveFallbackSession(params) {
	const trimmed = stripProviderPrefix(params.target, params.channel).trim();
	if (!trimmed) return null;
	const peerKind = inferPeerKind({
		channel: params.channel,
		resolvedTarget: params.resolvedTarget
	});
	const peerId = stripKindPrefix(trimmed);
	if (!peerId) return null;
	const peer = {
		kind: peerKind,
		id: peerId
	};
	const baseSessionKey = buildBaseSessionKey({
		cfg: params.cfg,
		agentId: params.agentId,
		channel: params.channel,
		peer
	});
	return {
		sessionKey: baseSessionKey,
		baseSessionKey,
		peer,
		chatType: peerKind === "direct" ? "direct" : peerKind === "channel" ? "channel" : "group",
		from: peerKind === "direct" ? `${params.channel}:${peerId}` : `${params.channel}:${peerKind}:${peerId}`,
		to: `${peerKind === "direct" ? "user" : "channel"}:${peerId}`
	};
}
async function resolveOutboundSessionRoute(params) {
	const target = params.target.trim();
	if (!target) return null;
	switch (params.channel) {
		case "slack": return await resolveSlackSession({
			...params,
			target
		});
		case "discord": return resolveDiscordSession({
			...params,
			target
		});
		case "telegram": return resolveTelegramSession({
			...params,
			target
		});
		case "whatsapp": return resolveWhatsAppSession({
			...params,
			target
		});
		case "signal": return resolveSignalSession({
			...params,
			target
		});
		case "imessage": return resolveIMessageSession({
			...params,
			target
		});
		case "matrix": return resolveMatrixSession({
			...params,
			target
		});
		case "msteams": return resolveMSTeamsSession({
			...params,
			target
		});
		case "mattermost": return resolveMattermostSession({
			...params,
			target
		});
		case "bluebubbles": return resolveBlueBubblesSession({
			...params,
			target
		});
		case "nextcloud-talk": return resolveNextcloudTalkSession({
			...params,
			target
		});
		case "zalo": return resolveZaloSession({
			...params,
			target
		});
		case "zalouser": return resolveZalouserSession({
			...params,
			target
		});
		case "nostr": return resolveNostrSession({
			...params,
			target
		});
		case "tlon": return resolveTlonSession({
			...params,
			target
		});
		case "feishu": return resolveFeishuSession({
			...params,
			target
		});
		default: return resolveFallbackSession({
			...params,
			target
		});
	}
}
async function ensureOutboundSessionEntry(params) {
	const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.agentId });
	const ctx = {
		From: params.route.from,
		To: params.route.to,
		SessionKey: params.route.sessionKey,
		AccountId: params.accountId ?? void 0,
		ChatType: params.route.chatType,
		Provider: params.channel,
		Surface: params.channel,
		MessageThreadId: params.route.threadId,
		OriginatingChannel: params.channel,
		OriginatingTo: params.route.to
	};
	try {
		await recordSessionMetaFromInbound({
			storePath,
			sessionKey: params.route.sessionKey,
			ctx
		});
	} catch {}
}

//#endregion
//#region src/infra/outbound/message-action-runner.ts
function resolveAndApplyOutboundThreadId(params, ctx) {
	const threadId = readStringParam(params, "threadId");
	const slackAutoThreadId = ctx.allowSlackAutoThread && ctx.channel === "slack" && !threadId ? resolveSlackAutoThreadId({
		to: ctx.to,
		toolContext: ctx.toolContext
	}) : void 0;
	const telegramAutoThreadId = ctx.channel === "telegram" && !threadId ? resolveTelegramAutoThreadId({
		to: ctx.to,
		toolContext: ctx.toolContext
	}) : void 0;
	const resolved = threadId ?? slackAutoThreadId ?? telegramAutoThreadId;
	if (resolved && !params.threadId) params.threadId = resolved;
	return resolved ?? void 0;
}
function getToolResult(result) {
	return "toolResult" in result ? result.toolResult : void 0;
}
function applyCrossContextMessageDecoration({ params, message, decoration, preferComponents }) {
	const applied = applyCrossContextDecoration({
		message,
		decoration,
		preferComponents
	});
	params.message = applied.message;
	if (applied.componentsBuilder) params.components = applied.componentsBuilder;
	return applied.message;
}
async function maybeApplyCrossContextMarker(params) {
	if (!shouldApplyCrossContextMarker(params.action) || !params.toolContext) return params.message;
	const decoration = await buildCrossContextDecoration({
		cfg: params.cfg,
		channel: params.channel,
		target: params.target,
		toolContext: params.toolContext,
		accountId: params.accountId ?? void 0
	});
	if (!decoration) return params.message;
	return applyCrossContextMessageDecoration({
		params: params.args,
		message: params.message,
		decoration,
		preferComponents: params.preferComponents
	});
}
async function resolveChannel(cfg, params) {
	return (await resolveMessageChannelSelection({
		cfg,
		channel: readStringParam(params, "channel")
	})).channel;
}
async function resolveActionTarget(params) {
	let resolvedTarget;
	const toRaw = typeof params.args.to === "string" ? params.args.to.trim() : "";
	if (toRaw) {
		const resolved = await resolveChannelTarget({
			cfg: params.cfg,
			channel: params.channel,
			input: toRaw,
			accountId: params.accountId ?? void 0
		});
		if (resolved.ok) {
			params.args.to = resolved.target.to;
			resolvedTarget = resolved.target;
		} else throw resolved.error;
	}
	const channelIdRaw = typeof params.args.channelId === "string" ? params.args.channelId.trim() : "";
	if (channelIdRaw) {
		const resolved = await resolveChannelTarget({
			cfg: params.cfg,
			channel: params.channel,
			input: channelIdRaw,
			accountId: params.accountId ?? void 0,
			preferredKind: "group"
		});
		if (resolved.ok) {
			if (resolved.target.kind === "user") throw new Error(`Channel id "${channelIdRaw}" resolved to a user target.`);
			params.args.channelId = resolved.target.to.replace(/^(channel|group):/i, "");
		} else throw resolved.error;
	}
	return resolvedTarget;
}
function resolveGateway(input) {
	if (!input.gateway) return;
	return {
		url: input.gateway.url,
		token: input.gateway.token,
		timeoutMs: input.gateway.timeoutMs,
		clientName: input.gateway.clientName,
		clientDisplayName: input.gateway.clientDisplayName,
		mode: input.gateway.mode
	};
}
async function handleBroadcastAction(input, params) {
	throwIfAborted(input.abortSignal);
	if (!(input.cfg.tools?.message?.broadcast?.enabled !== false)) throw new Error("Broadcast is disabled. Set tools.message.broadcast.enabled to true.");
	const rawTargets = readStringArrayParam(params, "targets", { required: true }) ?? [];
	if (rawTargets.length === 0) throw new Error("Broadcast requires at least one target in --targets.");
	const channelHint = readStringParam(params, "channel");
	const configured = await listConfiguredMessageChannels(input.cfg);
	if (configured.length === 0) throw new Error("Broadcast requires at least one configured channel.");
	const targetChannels = channelHint && channelHint.trim().toLowerCase() !== "all" ? [await resolveChannel(input.cfg, { channel: channelHint })] : configured;
	const results = [];
	const isAbortError = (err) => err instanceof Error && err.name === "AbortError";
	for (const targetChannel of targetChannels) {
		throwIfAborted(input.abortSignal);
		for (const target of rawTargets) {
			throwIfAborted(input.abortSignal);
			try {
				const resolved = await resolveChannelTarget({
					cfg: input.cfg,
					channel: targetChannel,
					input: target
				});
				if (!resolved.ok) throw resolved.error;
				const sendResult = await runMessageAction({
					...input,
					action: "send",
					params: {
						...params,
						channel: targetChannel,
						target: resolved.target.to
					}
				});
				results.push({
					channel: targetChannel,
					to: resolved.target.to,
					ok: true,
					result: sendResult.kind === "send" ? sendResult.sendResult : void 0
				});
			} catch (err) {
				if (isAbortError(err)) throw err;
				results.push({
					channel: targetChannel,
					to: target,
					ok: false,
					error: err instanceof Error ? err.message : String(err)
				});
			}
		}
	}
	return {
		kind: "broadcast",
		channel: targetChannels[0] ?? "discord",
		action: "broadcast",
		handledBy: input.dryRun ? "dry-run" : "core",
		payload: { results },
		dryRun: Boolean(input.dryRun)
	};
}
async function handleSendAction(ctx) {
	const { cfg, params, channel, accountId, dryRun, gateway, input, agentId, resolvedTarget, abortSignal } = ctx;
	throwIfAborted(abortSignal);
	const action = "send";
	const to = readStringParam(params, "to", { required: true });
	const mediaHint = readStringParam(params, "media", { trim: false }) ?? readStringParam(params, "path", { trim: false }) ?? readStringParam(params, "filePath", { trim: false });
	const hasCard = params.card != null && typeof params.card === "object";
	const hasComponents = params.components != null && typeof params.components === "object";
	const caption = readStringParam(params, "caption", { allowEmpty: true }) ?? "";
	let message = readStringParam(params, "message", {
		required: !mediaHint && !hasCard && !hasComponents,
		allowEmpty: true
	}) ?? "";
	if (message.includes("\\n")) message = message.replaceAll("\\n", "\n");
	if (!message.trim() && caption.trim()) message = caption;
	const parsed = parseReplyDirectives(message);
	const mergedMediaUrls = [];
	const seenMedia = /* @__PURE__ */ new Set();
	const pushMedia = (value) => {
		const trimmed = value?.trim();
		if (!trimmed) return;
		if (seenMedia.has(trimmed)) return;
		seenMedia.add(trimmed);
		mergedMediaUrls.push(trimmed);
	};
	pushMedia(mediaHint);
	for (const url of parsed.mediaUrls ?? []) pushMedia(url);
	pushMedia(parsed.mediaUrl);
	const normalizedMediaUrls = await normalizeSandboxMediaList({
		values: mergedMediaUrls,
		sandboxRoot: input.sandboxRoot
	});
	mergedMediaUrls.length = 0;
	mergedMediaUrls.push(...normalizedMediaUrls);
	message = parsed.text;
	params.message = message;
	if (!params.replyTo && parsed.replyToId) params.replyTo = parsed.replyToId;
	if (!params.media) params.media = mergedMediaUrls[0] || void 0;
	message = await maybeApplyCrossContextMarker({
		cfg,
		channel,
		action,
		target: to,
		toolContext: input.toolContext,
		accountId,
		args: params,
		message,
		preferComponents: true
	});
	const mediaUrl = readStringParam(params, "media", { trim: false });
	if (channel === "whatsapp") {
		message = message.replace(/^(?:[ \t]*\r?\n)+/, "");
		if (!message.trim()) message = "";
	}
	if (!message.trim() && !mediaUrl && mergedMediaUrls.length === 0 && !hasCard && !hasComponents) throw new Error("send requires text or media");
	params.message = message;
	const gifPlayback = readBooleanParam(params, "gifPlayback") ?? false;
	const bestEffort = readBooleanParam(params, "bestEffort");
	const silent = readBooleanParam(params, "silent");
	const replyToId = readStringParam(params, "replyTo");
	const resolvedThreadId = resolveAndApplyOutboundThreadId(params, {
		channel,
		to,
		toolContext: input.toolContext,
		allowSlackAutoThread: channel === "slack" && !replyToId
	});
	const outboundRoute = agentId && !dryRun ? await resolveOutboundSessionRoute({
		cfg,
		channel,
		agentId,
		accountId,
		target: to,
		resolvedTarget,
		replyToId,
		threadId: resolvedThreadId
	}) : null;
	if (outboundRoute && agentId && !dryRun) await ensureOutboundSessionEntry({
		cfg,
		agentId,
		channel,
		accountId,
		route: outboundRoute
	});
	if (outboundRoute && !dryRun) params.__sessionKey = outboundRoute.sessionKey;
	if (agentId) params.__agentId = agentId;
	const mirrorMediaUrls = mergedMediaUrls.length > 0 ? mergedMediaUrls : mediaUrl ? [mediaUrl] : void 0;
	throwIfAborted(abortSignal);
	const send = await executeSendAction({
		ctx: {
			cfg,
			channel,
			params,
			agentId,
			accountId: accountId ?? void 0,
			gateway,
			toolContext: input.toolContext,
			deps: input.deps,
			dryRun,
			mirror: outboundRoute && !dryRun ? {
				sessionKey: outboundRoute.sessionKey,
				agentId,
				text: message,
				mediaUrls: mirrorMediaUrls
			} : void 0,
			abortSignal,
			silent: silent ?? void 0
		},
		to,
		message,
		mediaUrl: mediaUrl || void 0,
		mediaUrls: mergedMediaUrls.length ? mergedMediaUrls : void 0,
		gifPlayback,
		bestEffort: bestEffort ?? void 0,
		replyToId: replyToId ?? void 0,
		threadId: resolvedThreadId ?? void 0
	});
	return {
		kind: "send",
		channel,
		action,
		to,
		handledBy: send.handledBy,
		payload: send.payload,
		toolResult: send.toolResult,
		sendResult: send.sendResult,
		dryRun
	};
}
async function handlePollAction(ctx) {
	const { cfg, params, channel, accountId, dryRun, gateway, input, abortSignal } = ctx;
	throwIfAborted(abortSignal);
	const action = "poll";
	const to = readStringParam(params, "to", { required: true });
	const question = readStringParam(params, "pollQuestion", { required: true });
	const options = readStringArrayParam(params, "pollOption", { required: true }) ?? [];
	if (options.length < 2) throw new Error("pollOption requires at least two values");
	const silent = readBooleanParam(params, "silent");
	const allowMultiselect = readBooleanParam(params, "pollMulti") ?? false;
	const pollAnonymous = readBooleanParam(params, "pollAnonymous");
	const pollPublic = readBooleanParam(params, "pollPublic");
	if (pollAnonymous && pollPublic) throw new Error("pollAnonymous and pollPublic are mutually exclusive");
	const isAnonymous = pollAnonymous ? true : pollPublic ? false : void 0;
	const durationHours = readNumberParam(params, "pollDurationHours", { integer: true });
	const durationSeconds = readNumberParam(params, "pollDurationSeconds", { integer: true });
	const maxSelections = allowMultiselect ? Math.max(2, options.length) : 1;
	if (durationSeconds !== void 0 && channel !== "telegram") throw new Error("pollDurationSeconds is only supported for Telegram polls");
	if (isAnonymous !== void 0 && channel !== "telegram") throw new Error("pollAnonymous/pollPublic are only supported for Telegram polls");
	const resolvedThreadId = resolveAndApplyOutboundThreadId(params, {
		channel,
		to,
		toolContext: input.toolContext,
		allowSlackAutoThread: channel === "slack"
	});
	const base = typeof params.message === "string" ? params.message : "";
	await maybeApplyCrossContextMarker({
		cfg,
		channel,
		action,
		target: to,
		toolContext: input.toolContext,
		accountId,
		args: params,
		message: base,
		preferComponents: false
	});
	const poll = await executePollAction({
		ctx: {
			cfg,
			channel,
			params,
			accountId: accountId ?? void 0,
			gateway,
			toolContext: input.toolContext,
			dryRun,
			silent: silent ?? void 0
		},
		to,
		question,
		options,
		maxSelections,
		durationSeconds: durationSeconds ?? void 0,
		durationHours: durationHours ?? void 0,
		threadId: resolvedThreadId ?? void 0,
		isAnonymous
	});
	return {
		kind: "poll",
		channel,
		action,
		to,
		handledBy: poll.handledBy,
		payload: poll.payload,
		toolResult: poll.toolResult,
		pollResult: poll.pollResult,
		dryRun
	};
}
async function handlePluginAction(ctx) {
	const { cfg, params, channel, accountId, dryRun, gateway, input, abortSignal } = ctx;
	throwIfAborted(abortSignal);
	const action = input.action;
	if (dryRun) return {
		kind: "action",
		channel,
		action,
		handledBy: "dry-run",
		payload: {
			ok: true,
			dryRun: true,
			channel,
			action
		},
		dryRun: true
	};
	const handled = await dispatchChannelMessageAction({
		channel,
		action,
		cfg,
		params,
		accountId: accountId ?? void 0,
		gateway,
		toolContext: input.toolContext,
		dryRun
	});
	if (!handled) throw new Error(`Message action ${action} not supported for channel ${channel}.`);
	return {
		kind: "action",
		channel,
		action,
		handledBy: "plugin",
		payload: extractToolPayload(handled),
		toolResult: handled,
		dryRun
	};
}
async function runMessageAction(input) {
	const cfg = input.cfg;
	const params = { ...input.params };
	const resolvedAgentId = input.agentId ?? (input.sessionKey ? resolveSessionAgentId({
		sessionKey: input.sessionKey,
		config: cfg
	}) : void 0);
	parseButtonsParam(params);
	parseCardParam(params);
	parseComponentsParam(params);
	const action = input.action;
	if (action === "broadcast") return handleBroadcastAction(input, params);
	const explicitTarget = typeof params.target === "string" ? params.target.trim() : "";
	const hasLegacyTarget = typeof params.to === "string" && params.to.trim().length > 0 || typeof params.channelId === "string" && params.channelId.trim().length > 0;
	if (explicitTarget && hasLegacyTarget) {
		delete params.to;
		delete params.channelId;
	}
	if (!explicitTarget && !hasLegacyTarget && actionRequiresTarget(action) && !actionHasTarget(action, params)) {
		const inferredTarget = input.toolContext?.currentChannelId?.trim();
		if (inferredTarget) params.target = inferredTarget;
	}
	if (!explicitTarget && actionRequiresTarget(action) && hasLegacyTarget) {
		const legacyTo = typeof params.to === "string" ? params.to.trim() : "";
		const legacyChannelId = typeof params.channelId === "string" ? params.channelId.trim() : "";
		const legacyTarget = legacyTo || legacyChannelId;
		if (legacyTarget) {
			params.target = legacyTarget;
			delete params.to;
			delete params.channelId;
		}
	}
	if (!(typeof params.channel === "string" ? params.channel.trim() : "")) {
		const inferredChannel = normalizeMessageChannel(input.toolContext?.currentChannelProvider);
		if (inferredChannel && isDeliverableMessageChannel(inferredChannel)) params.channel = inferredChannel;
	}
	applyTargetToParams({
		action,
		args: params
	});
	if (actionRequiresTarget(action)) {
		if (!actionHasTarget(action, params)) throw new Error(`Action ${action} requires a target.`);
	}
	const channel = await resolveChannel(cfg, params);
	const accountId = readStringParam(params, "accountId") ?? input.defaultAccountId;
	if (accountId) params.accountId = accountId;
	const dryRun = Boolean(input.dryRun ?? readBooleanParam(params, "dryRun"));
	await normalizeSandboxMediaParams({
		args: params,
		sandboxRoot: input.sandboxRoot
	});
	await hydrateSendAttachmentParams({
		cfg,
		channel,
		accountId,
		args: params,
		action,
		dryRun
	});
	await hydrateSetGroupIconParams({
		cfg,
		channel,
		accountId,
		args: params,
		action,
		dryRun
	});
	const resolvedTarget = await resolveActionTarget({
		cfg,
		channel,
		action,
		args: params,
		accountId
	});
	enforceCrossContextPolicy({
		channel,
		action,
		args: params,
		toolContext: input.toolContext,
		cfg
	});
	const gateway = resolveGateway(input);
	if (action === "send") return handleSendAction({
		cfg,
		params,
		channel,
		accountId,
		dryRun,
		gateway,
		input,
		agentId: resolvedAgentId,
		resolvedTarget,
		abortSignal: input.abortSignal
	});
	if (action === "poll") return handlePollAction({
		cfg,
		params,
		channel,
		accountId,
		dryRun,
		gateway,
		input,
		abortSignal: input.abortSignal
	});
	return handlePluginAction({
		cfg,
		params,
		channel,
		accountId,
		dryRun,
		gateway,
		input,
		abortSignal: input.abortSignal
	});
}

//#endregion
//#region src/agents/tools/messaging/message-tool.ts
var message_tool_exports = /* @__PURE__ */ __exportAll({ createMessageTool: () => createMessageTool });
const AllMessageActions = CHANNEL_MESSAGE_ACTION_NAMES;
const EXPLICIT_TARGET_ACTIONS = new Set([
	"send",
	"sendWithEffect",
	"sendAttachment",
	"reply",
	"thread-reply",
	"broadcast"
]);
function actionNeedsExplicitTarget(action) {
	return EXPLICIT_TARGET_ACTIONS.has(action);
}
function buildRoutingSchema() {
	return {
		channel: Type.Optional(Type.String()),
		target: Type.Optional(channelTargetSchema({ description: "Target channel/user id or name." })),
		targets: Type.Optional(channelTargetsSchema()),
		accountId: Type.Optional(Type.String()),
		dryRun: Type.Optional(Type.Boolean())
	};
}
const discordComponentEmojiSchema = Type.Object({
	name: Type.String(),
	id: Type.Optional(Type.String()),
	animated: Type.Optional(Type.Boolean())
});
const discordComponentOptionSchema = Type.Object({
	label: Type.String(),
	value: Type.String(),
	description: Type.Optional(Type.String()),
	emoji: Type.Optional(discordComponentEmojiSchema),
	default: Type.Optional(Type.Boolean())
});
const discordComponentButtonSchema = Type.Object({
	label: Type.String(),
	style: Type.Optional(stringEnum([
		"primary",
		"secondary",
		"success",
		"danger",
		"link"
	])),
	url: Type.Optional(Type.String()),
	emoji: Type.Optional(discordComponentEmojiSchema),
	disabled: Type.Optional(Type.Boolean()),
	allowedUsers: Type.Optional(Type.Array(Type.String({ description: "Discord user ids or names allowed to interact with this button." })))
});
const discordComponentSelectSchema = Type.Object({
	type: Type.Optional(stringEnum([
		"string",
		"user",
		"role",
		"mentionable",
		"channel"
	])),
	placeholder: Type.Optional(Type.String()),
	minValues: Type.Optional(Type.Number()),
	maxValues: Type.Optional(Type.Number()),
	options: Type.Optional(Type.Array(discordComponentOptionSchema))
});
const discordComponentBlockSchema = Type.Object({
	type: Type.String(),
	text: Type.Optional(Type.String()),
	texts: Type.Optional(Type.Array(Type.String())),
	accessory: Type.Optional(Type.Object({
		type: Type.String(),
		url: Type.Optional(Type.String()),
		button: Type.Optional(discordComponentButtonSchema)
	})),
	spacing: Type.Optional(stringEnum(["small", "large"])),
	divider: Type.Optional(Type.Boolean()),
	buttons: Type.Optional(Type.Array(discordComponentButtonSchema)),
	select: Type.Optional(discordComponentSelectSchema),
	items: Type.Optional(Type.Array(Type.Object({
		url: Type.String(),
		description: Type.Optional(Type.String()),
		spoiler: Type.Optional(Type.Boolean())
	}))),
	file: Type.Optional(Type.String()),
	spoiler: Type.Optional(Type.Boolean())
});
const discordComponentModalFieldSchema = Type.Object({
	type: Type.String(),
	name: Type.Optional(Type.String()),
	label: Type.String(),
	description: Type.Optional(Type.String()),
	placeholder: Type.Optional(Type.String()),
	required: Type.Optional(Type.Boolean()),
	options: Type.Optional(Type.Array(discordComponentOptionSchema)),
	minValues: Type.Optional(Type.Number()),
	maxValues: Type.Optional(Type.Number()),
	minLength: Type.Optional(Type.Number()),
	maxLength: Type.Optional(Type.Number()),
	style: Type.Optional(stringEnum(["short", "paragraph"]))
});
const discordComponentModalSchema = Type.Object({
	title: Type.String(),
	triggerLabel: Type.Optional(Type.String()),
	triggerStyle: Type.Optional(stringEnum([
		"primary",
		"secondary",
		"success",
		"danger",
		"link"
	])),
	fields: Type.Array(discordComponentModalFieldSchema)
});
const discordComponentMessageSchema = Type.Object({
	text: Type.Optional(Type.String()),
	reusable: Type.Optional(Type.Boolean({ description: "Allow components to be used multiple times until they expire." })),
	container: Type.Optional(Type.Object({
		accentColor: Type.Optional(Type.String()),
		spoiler: Type.Optional(Type.Boolean())
	})),
	blocks: Type.Optional(Type.Array(discordComponentBlockSchema)),
	modal: Type.Optional(discordComponentModalSchema)
}, { description: "Discord components v2 payload. Set reusable=true to keep buttons, selects, and forms active until expiry." });
function buildSendSchema(options) {
	const props = {
		message: Type.Optional(Type.String()),
		effectId: Type.Optional(Type.String({ description: "Message effect name/id for sendWithEffect (e.g., invisible ink)." })),
		effect: Type.Optional(Type.String({ description: "Alias for effectId (e.g., invisible-ink, balloons)." })),
		media: Type.Optional(Type.String({ description: "Media URL or local path. data: URLs are not supported here, use buffer." })),
		filename: Type.Optional(Type.String()),
		buffer: Type.Optional(Type.String({ description: "Base64 payload for attachments (optionally a data: URL)." })),
		contentType: Type.Optional(Type.String()),
		mimeType: Type.Optional(Type.String()),
		caption: Type.Optional(Type.String()),
		path: Type.Optional(Type.String()),
		filePath: Type.Optional(Type.String()),
		replyTo: Type.Optional(Type.String()),
		threadId: Type.Optional(Type.String()),
		asVoice: Type.Optional(Type.Boolean()),
		silent: Type.Optional(Type.Boolean()),
		quoteText: Type.Optional(Type.String({ description: "Quote text for Telegram reply_parameters" })),
		bestEffort: Type.Optional(Type.Boolean()),
		gifPlayback: Type.Optional(Type.Boolean()),
		buttons: Type.Optional(Type.Array(Type.Array(Type.Object({
			text: Type.String(),
			callback_data: Type.String(),
			style: Type.Optional(stringEnum([
				"danger",
				"success",
				"primary"
			]))
		})), { description: "Telegram inline keyboard buttons (array of button rows)" })),
		card: Type.Optional(Type.Object({}, {
			additionalProperties: true,
			description: "Adaptive Card JSON object (when supported by the channel)"
		})),
		components: Type.Optional(discordComponentMessageSchema)
	};
	if (!options.includeButtons) delete props.buttons;
	if (!options.includeCards) delete props.card;
	if (!options.includeComponents) delete props.components;
	return props;
}
function buildReactionSchema() {
	return {
		messageId: Type.Optional(Type.String()),
		emoji: Type.Optional(Type.String()),
		remove: Type.Optional(Type.Boolean()),
		targetAuthor: Type.Optional(Type.String()),
		targetAuthorUuid: Type.Optional(Type.String()),
		groupId: Type.Optional(Type.String())
	};
}
function buildFetchSchema() {
	return {
		limit: Type.Optional(Type.Number()),
		before: Type.Optional(Type.String()),
		after: Type.Optional(Type.String()),
		around: Type.Optional(Type.String()),
		fromMe: Type.Optional(Type.Boolean()),
		includeArchived: Type.Optional(Type.Boolean())
	};
}
function buildPollSchema() {
	return {
		pollQuestion: Type.Optional(Type.String()),
		pollOption: Type.Optional(Type.Array(Type.String())),
		pollDurationHours: Type.Optional(Type.Number()),
		pollMulti: Type.Optional(Type.Boolean())
	};
}
function buildChannelTargetSchema() {
	return {
		channelId: Type.Optional(Type.String({ description: "Channel id filter (search/thread list/event create)." })),
		channelIds: Type.Optional(Type.Array(Type.String({ description: "Channel id filter (repeatable)." }))),
		guildId: Type.Optional(Type.String()),
		userId: Type.Optional(Type.String()),
		authorId: Type.Optional(Type.String()),
		authorIds: Type.Optional(Type.Array(Type.String())),
		roleId: Type.Optional(Type.String()),
		roleIds: Type.Optional(Type.Array(Type.String())),
		participant: Type.Optional(Type.String())
	};
}
function buildStickerSchema() {
	return {
		emojiName: Type.Optional(Type.String()),
		stickerId: Type.Optional(Type.Array(Type.String())),
		stickerName: Type.Optional(Type.String()),
		stickerDesc: Type.Optional(Type.String()),
		stickerTags: Type.Optional(Type.String())
	};
}
function buildThreadSchema() {
	return {
		threadName: Type.Optional(Type.String()),
		autoArchiveMin: Type.Optional(Type.Number())
	};
}
function buildEventSchema() {
	return {
		query: Type.Optional(Type.String()),
		eventName: Type.Optional(Type.String()),
		eventType: Type.Optional(Type.String()),
		startTime: Type.Optional(Type.String()),
		endTime: Type.Optional(Type.String()),
		desc: Type.Optional(Type.String()),
		location: Type.Optional(Type.String()),
		durationMin: Type.Optional(Type.Number()),
		until: Type.Optional(Type.String())
	};
}
function buildModerationSchema() {
	return {
		reason: Type.Optional(Type.String()),
		deleteDays: Type.Optional(Type.Number())
	};
}
function buildGatewaySchema() {
	return {
		gatewayUrl: Type.Optional(Type.String()),
		gatewayToken: Type.Optional(Type.String()),
		timeoutMs: Type.Optional(Type.Number())
	};
}
function buildPresenceSchema() {
	return {
		activityType: Type.Optional(Type.String({ description: "Activity type: playing, streaming, listening, watching, competing, custom." })),
		activityName: Type.Optional(Type.String({ description: "Activity name shown in sidebar (e.g. 'with fire'). Ignored for custom type." })),
		activityUrl: Type.Optional(Type.String({ description: "Streaming URL (Twitch or YouTube). Only used with streaming type; may not render for bots." })),
		activityState: Type.Optional(Type.String({ description: "State text. For custom type this is the status text; for others it shows in the flyout." })),
		status: Type.Optional(Type.String({ description: "Bot status: online, dnd, idle, invisible." }))
	};
}
function buildChannelManagementSchema() {
	return {
		name: Type.Optional(Type.String()),
		type: Type.Optional(Type.Number()),
		parentId: Type.Optional(Type.String()),
		topic: Type.Optional(Type.String()),
		position: Type.Optional(Type.Number()),
		nsfw: Type.Optional(Type.Boolean()),
		rateLimitPerUser: Type.Optional(Type.Number()),
		categoryId: Type.Optional(Type.String()),
		clearParent: Type.Optional(Type.Boolean({ description: "Clear the parent/category when supported by the provider." }))
	};
}
function buildMessageToolSchemaProps(options) {
	return {
		...buildRoutingSchema(),
		...buildSendSchema(options),
		...buildReactionSchema(),
		...buildFetchSchema(),
		...buildPollSchema(),
		...buildChannelTargetSchema(),
		...buildStickerSchema(),
		...buildThreadSchema(),
		...buildEventSchema(),
		...buildModerationSchema(),
		...buildGatewaySchema(),
		...buildChannelManagementSchema(),
		...buildPresenceSchema()
	};
}
function buildMessageToolSchemaFromActions(actions, options) {
	const props = buildMessageToolSchemaProps(options);
	return Type.Object({
		action: stringEnum(actions),
		...props
	});
}
const MessageToolSchema = buildMessageToolSchemaFromActions(AllMessageActions, {
	includeButtons: true,
	includeCards: true,
	includeComponents: true
});
function resolveMessageToolSchemaActions(params) {
	const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
	if (currentChannel) {
		const scopedActions = filterActionsForContext({
			actions: listChannelSupportedActions({
				cfg: params.cfg,
				channel: currentChannel
			}),
			channel: currentChannel,
			currentChannelId: params.currentChannelId
		});
		const withSend = new Set(["send", ...scopedActions]);
		return Array.from(withSend);
	}
	const actions = listChannelMessageActions(params.cfg);
	return actions.length > 0 ? actions : ["send"];
}
function resolveIncludeComponents(params) {
	const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
	if (currentChannel) return currentChannel === "discord";
	return listChannelSupportedActions({
		cfg: params.cfg,
		channel: "discord"
	}).length > 0;
}
function buildMessageToolSchema(params) {
	const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
	const actions = resolveMessageToolSchemaActions(params);
	const includeButtons = currentChannel ? supportsChannelMessageButtonsForChannel({
		cfg: params.cfg,
		channel: currentChannel
	}) : supportsChannelMessageButtons(params.cfg);
	const includeCards = currentChannel ? supportsChannelMessageCardsForChannel({
		cfg: params.cfg,
		channel: currentChannel
	}) : supportsChannelMessageCards(params.cfg);
	const includeComponents = resolveIncludeComponents(params);
	return buildMessageToolSchemaFromActions(actions.length > 0 ? actions : ["send"], {
		includeButtons,
		includeCards,
		includeComponents
	});
}
function resolveAgentAccountId(value) {
	const trimmed = value?.trim();
	if (!trimmed) return;
	return normalizeAccountId(trimmed);
}
function filterActionsForContext(params) {
	const channel = normalizeMessageChannel(params.channel);
	if (!channel || channel !== "bluebubbles") return params.actions;
	const currentChannelId = params.currentChannelId?.trim();
	if (!currentChannelId) return params.actions;
	const lowered = (normalizeTargetForProvider(channel, currentChannelId) ?? currentChannelId).trim().toLowerCase();
	if (lowered.startsWith("chat_guid:") || lowered.startsWith("chat_id:") || lowered.startsWith("chat_identifier:") || lowered.startsWith("group:")) return params.actions;
	return params.actions.filter((action) => !BLUEBUBBLES_GROUP_ACTIONS.has(action));
}
function buildMessageToolDescription(options) {
	const baseDescription = "Send, delete, and manage messages via channel plugins.";
	if (options?.currentChannel) {
		const channelActions = filterActionsForContext({
			actions: listChannelSupportedActions({
				cfg: options.config,
				channel: options.currentChannel
			}),
			channel: options.currentChannel,
			currentChannelId: options.currentChannelId
		});
		if (channelActions.length > 0) {
			const allActions = new Set(["send", ...channelActions]);
			const actionList = Array.from(allActions).toSorted().join(", ");
			return `${baseDescription} Current channel (${options.currentChannel}) supports: ${actionList}.`;
		}
	}
	if (options?.config) {
		const actions = listChannelMessageActions(options.config);
		if (actions.length > 0) return `${baseDescription} Supports actions: ${actions.join(", ")}.`;
	}
	return `${baseDescription} Supports actions: send, delete, react, poll, pin, threads, and more.`;
}
function createMessageTool(options) {
	const agentAccountId = resolveAgentAccountId(options?.agentAccountId);
	const schema = options?.config ? buildMessageToolSchema({
		cfg: options.config,
		currentChannelProvider: options.currentChannelProvider,
		currentChannelId: options.currentChannelId
	}) : MessageToolSchema;
	return {
		label: "Message",
		name: "message",
		description: buildMessageToolDescription({
			config: options?.config,
			currentChannel: options?.currentChannelProvider,
			currentChannelId: options?.currentChannelId
		}),
		parameters: schema,
		execute: async (_toolCallId, args, signal) => {
			if (signal?.aborted) {
				const err = /* @__PURE__ */ new Error("Message send aborted");
				err.name = "AbortError";
				throw err;
			}
			const params = { ...args };
			for (const field of [
				"text",
				"content",
				"message",
				"caption"
			]) if (typeof params[field] === "string") params[field] = stripReasoningTagsFromText(params[field]);
			const cfg = options?.config ?? loadConfig();
			const action = readStringParam(params, "action", { required: true });
			if (options?.requireExplicitTarget === true && actionNeedsExplicitTarget(action)) {
				if (!(typeof params.target === "string" && params.target.trim().length > 0 || typeof params.to === "string" && params.to.trim().length > 0 || typeof params.channelId === "string" && params.channelId.trim().length > 0 || Array.isArray(params.targets) && params.targets.some((value) => typeof value === "string" && value.trim().length > 0))) throw new Error("Explicit message target required for this run. Provide target/targets (and channel when needed).");
			}
			const accountId = readStringParam(params, "accountId") ?? agentAccountId;
			if (accountId) params.accountId = accountId;
			const gatewayResolved = resolveGatewayOptions$1({
				gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
				gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
				timeoutMs: readNumberParam(params, "timeoutMs")
			});
			const gateway = {
				url: gatewayResolved.url,
				token: gatewayResolved.token,
				timeoutMs: gatewayResolved.timeoutMs,
				clientName: GATEWAY_CLIENT_IDS.GATEWAY_CLIENT,
				clientDisplayName: "agent",
				mode: GATEWAY_CLIENT_MODES.BACKEND
			};
			const toolContext = options?.currentChannelId || options?.currentChannelProvider || options?.currentThreadTs || options?.replyToMode || options?.hasRepliedRef ? {
				currentChannelId: options?.currentChannelId,
				currentChannelProvider: options?.currentChannelProvider,
				currentThreadTs: options?.currentThreadTs,
				replyToMode: options?.replyToMode,
				hasRepliedRef: options?.hasRepliedRef,
				skipCrossContextDecoration: true
			} : void 0;
			const result = await runMessageAction({
				cfg,
				action,
				params,
				defaultAccountId: accountId ?? void 0,
				gateway,
				toolContext,
				sessionKey: options?.agentSessionKey,
				agentId: options?.agentSessionKey ? resolveSessionAgentId({
					sessionKey: options.agentSessionKey,
					config: cfg
				}) : void 0,
				sandboxRoot: options?.sandboxRoot,
				abortSignal: signal
			});
			const toolResult = getToolResult(result);
			if (toolResult) return toolResult;
			return jsonResult(result.payload);
		}
	};
}

//#endregion
export { message_tool_exports as n, createMessageTool as t };