import { c as normalizeOptionalLowercaseString } from "./string-coerce-CIXf7egm.js";
import { o as resolveSessionStorePathCore } from "./paths-DVAvlIOc.js";
import { Kt as executeSqliteQuerySync } from "./openclaw-state-db-Cb4e1jdd.js";
import { g as openOpenClawAgentDatabase } from "./openclaw-agent-db-DwEkomYQ.js";
import { W as parseStoredConversationRouteContext, z as upsertConversationIdentity } from "./session-accessor.sqlite-entry-store-B5zyYu48.js";
import { i as getSessionKysely, m as toDatabaseOptions, s as resolveSqliteReadScope } from "./session-accessor.sqlite-scope-I8SegJ8n.js";
import { D as parseSessionEntryJson } from "./session-accessor.sqlite-transcript-state-CTvnr6Is.js";
//#region src/config/sessions/conversation-registry.ts
const CONVERSATION_REF_PATTERN = /^conv_[a-f0-9]{32}$/u;
function resolveConversationRegistryScope(params) {
	const configuredStore = params.config.session?.store;
	return {
		agentId: params.agentId,
		...configuredStore ? { storePath: resolveSessionStorePathCore(configuredStore, { agentId: params.agentId }) } : {}
	};
}
function normalizeConversationRef(value) {
	const normalized = value.trim().toLowerCase();
	if (!CONVERSATION_REF_PATTERN.test(normalized)) throw new Error(`Invalid conversationRef: ${value}`);
	return normalized;
}
function mapConversationRow(row) {
	if (row.kind !== "direct" && row.kind !== "group" && row.kind !== "channel") return null;
	const role = row.role === "primary" || row.role === "participant" || row.role === "related" ? row.role : void 0;
	const hasCurrentBinding = (row.current_entry_json ? parseSessionEntryJson({ entry_json: row.current_entry_json }) : null)?.sessionId === row.current_session_id;
	const associationIsCurrent = hasCurrentBinding && row.associated_session_id === row.current_session_id;
	const routeContext = parseStoredConversationRouteContext(row.route_context_json, row.last_seen_at);
	return {
		associationIsCurrent,
		record: {
			conversationRef: row.conversation_id,
			channel: row.channel,
			accountId: row.account_id,
			kind: row.kind,
			peerId: row.peer_id,
			target: row.delivery_target,
			...row.parent_conversation_id ? { parentConversationRef: row.parent_conversation_id } : {},
			...row.thread_id ? { threadId: row.thread_id } : {},
			...row.native_channel_id ? { nativeChannelId: row.native_channel_id } : {},
			...row.native_direct_user_id ? { nativeDirectUserId: row.native_direct_user_id } : {},
			...row.label ? { label: row.label } : {},
			...role && hasCurrentBinding && row.current_session_id && row.current_session_key ? {
				sessionId: row.current_session_id,
				sessionKey: row.current_session_key,
				role
			} : {},
			...role ? { observedFromSession: true } : {},
			...routeContext ? { routeContextObserved: true } : {},
			...routeContext?.context ? { routeContext: routeContext.context } : {},
			firstSeenAt: row.first_seen_at ?? row.conversation_created_at,
			lastSeenAt: row.last_seen_at ?? row.conversation_updated_at
		}
	};
}
function selectConversationRows(scope, options = {}) {
	const database = openOpenClawAgentDatabase(toDatabaseOptions(resolveSqliteReadScope({
		agentId: scope.agentId,
		...scope.env ? { env: scope.env } : {},
		...scope.storePath ? { storePath: scope.storePath } : {}
	})));
	let query = getSessionKysely(database.db).selectFrom("conversations as c").leftJoin("session_conversations as sc", "sc.conversation_id", "c.conversation_id").leftJoin("session_windows as s", "s.session_id", "sc.session_id").leftJoin("session_nodes as sn", "sn.session_key", "s.session_key").select([
		"c.conversation_id",
		"c.channel",
		"c.account_id",
		"c.kind",
		"c.peer_id",
		"c.delivery_target",
		"c.parent_conversation_id",
		"c.thread_id",
		"c.native_channel_id",
		"c.native_direct_user_id",
		"c.label",
		"c.created_at as conversation_created_at",
		"c.updated_at as conversation_updated_at",
		"sc.role",
		"sc.route_context_json",
		"sc.first_seen_at",
		"sc.last_seen_at",
		"s.session_id as associated_session_id",
		"sn.current_session_id as current_session_id",
		"sn.entry_json as current_entry_json",
		"sn.session_key as current_session_key"
	]);
	const channel = normalizeOptionalLowercaseString(options.channel);
	if (channel) query = query.where("c.channel", "=", channel);
	if (options.conversationRef) query = query.where("c.conversation_id", "=", normalizeConversationRef(options.conversationRef));
	if (options.primarySession) query = query.where("s.session_id", "=", options.primarySession.sessionId).where("s.session_key", "=", options.primarySession.sessionKey).where("sn.current_session_id", "=", options.primarySession.sessionId).where("sn.entry_valid", "=", 1).whereRef("s.primary_conversation_id", "=", "c.conversation_id").where("sc.role", "=", "primary");
	const rows = executeSqliteQuerySync(database.db, query.orderBy((eb) => eb.fn.coalesce("sc.last_seen_at", "c.updated_at"), "desc").orderBy("sn.updated_at", "desc")).rows;
	const unique = /* @__PURE__ */ new Map();
	for (const row of rows) {
		const mapped = mapConversationRow(row);
		if (!mapped) continue;
		const existing = unique.get(mapped.record.conversationRef);
		if (!existing) {
			unique.set(mapped.record.conversationRef, mapped);
			continue;
		}
		if (!existing.associationIsCurrent && mapped.associationIsCurrent && mapped.record.sessionId && mapped.record.sessionKey && mapped.record.role) {
			const { routeContext: _staleRouteContext, routeContextObserved: _staleRouteContextObserved, ...existingRecord } = existing.record;
			unique.set(mapped.record.conversationRef, {
				associationIsCurrent: true,
				record: {
					...existingRecord,
					sessionId: mapped.record.sessionId,
					sessionKey: mapped.record.sessionKey,
					role: mapped.record.role,
					...mapped.record.routeContextObserved ? { routeContextObserved: true } : {},
					...mapped.record.routeContext ? { routeContext: mapped.record.routeContext } : {}
				}
			});
		}
	}
	const values = [...unique.values()].map(({ record }) => record);
	return options.limit === void 0 ? values : values.slice(0, options.limit);
}
/** Catalogs routable addresses without creating model-context sessions. */
function registerConversationAddresses(scope, identities, discoveredAt = Date.now()) {
	if (identities.length === 0) return;
	const database = openOpenClawAgentDatabase(toDatabaseOptions(resolveSqliteReadScope({
		agentId: scope.agentId,
		...scope.env ? { env: scope.env } : {},
		...scope.storePath ? { storePath: scope.storePath } : {}
	})));
	for (const identity of identities) upsertConversationIdentity(database, identity, discoveredAt);
}
/** Lists stable external addresses for one agent, newest activity first. */
function listConversations(scope, options = {}) {
	return selectConversationRows(scope, options);
}
/** Resolves an opaque address to one exact channel target and its context binding, when present. */
function resolveConversation(scope, conversationRef) {
	return selectConversationRows(scope, {
		conversationRef: normalizeConversationRef(conversationRef),
		limit: 1
	})[0];
}
/** Reads only the primary address bound to this exact current session window. */
function resolveCurrentSessionPrimaryConversation(scope) {
	const [conversation] = selectConversationRows(scope, { primarySession: scope });
	return conversation?.sessionId === scope.sessionId && conversation.sessionKey === scope.sessionKey ? conversation : void 0;
}
//#endregion
export { resolveCurrentSessionPrimaryConversation as a, resolveConversationRegistryScope as i, registerConversationAddresses as n, resolveConversation as r, listConversations as t };
