import { _ as defaultRuntime } from "./entry.js";
import { m as resolveThreadSessionKeys } from "./session-key-Fto5gqqw.js";
import { s as recordSessionMetaFromInbound } from "./sessions-CFJLtXRH.js";
import { n as normalizeWhatsAppTarget, r as resolveSlackAccount, t as isWhatsAppGroupJid } from "./normalize-CRf-uoB2.js";
import { D as resolveTelegramTargetChatType, E as parseTelegramTarget, a as buildTelegramGroupPeerId } from "./helpers-C1I2eYI4.js";
import { d as resolveSignalRecipient, f as resolveSignalSender, n as normalizeAllowListLower, u as resolveSignalPeerId, v as ambiguousTargetError, y as unknownTargetError } from "./allow-list-C1QL6XM_.js";
import { r as parseSlackTarget, t as createSlackWebClient } from "./client-BrCP9C3o.js";
import { t as getChannelPlugin } from "./plugins-DMpYx7kb.js";
import { t as parseDiscordTarget } from "./targets-DvKzWjKi.js";
import { d as normalizeTargetForProvider, l as buildTargetResolverSignature, u as normalizeChannelTargetInput } from "./reply-payloads-DHkZZC3s.js";
import { c as resolveStorePath } from "./paths-CDTtU20P.js";
import { t as buildAgentSessionKey } from "./resolve-route-CjJJ3gIl.js";
import { i as parseIMessageTarget, r as normalizeIMessageHandle } from "./targets-Df3HJYGb.js";

//#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 resetDirectoryCache(params) {
	if (!params?.channel) {
		directoryCache.clear();
		return;
	}
	const channelKey = params.channel;
	const accountKey = params.accountId ?? "default";
	directoryCache.clearMatching((key) => {
		if (!key.startsWith(`${channelKey}:`)) return false;
		if (!params.accountId) return true;
		return key.startsWith(`${channelKey}:${accountKey}:`);
	});
}
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-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
export { resetDirectoryCache as a, lookupDirectoryDisplay as i, resolveOutboundSessionRoute as n, resolveChannelTarget as o, formatTargetDisplay as r, ensureOutboundSessionEntry as t };