import { type BotState, type MessageListenerConfig } from '../bot-registry.js'; export declare const MAX_MESSAGE_LISTENER_PROMPT_BYTES: number; export type MessageListenerSenderType = 'user' | 'bot'; export interface MessageListenerMatch { name?: string; replyCardTitle?: string; prompt: string; workingDir?: string; messageText: string; messageTitle?: string; msgType: string; senderOpenId?: string; senderName?: string; senderType: MessageListenerSenderType; } export interface MessageListenerPreviewMatch extends MessageListenerMatch { messageId: string; createTime?: string; } export declare const DEFAULT_MESSAGE_LISTENER_PREVIEW_LIMIT = 5; export declare const MAX_MESSAGE_LISTENER_PREVIEW_LIMIT = 20; export declare function listenerSenderType(raw: unknown): MessageListenerSenderType; export declare function messageTypeOf(message: any): string; export declare function extractListenerMessageTitle(message: any): string | undefined; export declare function extractListenerMessageText(message: any): string; /** * Refresh a card match's observed text/title from the (now-resolved) message. * * The listener match is computed during filtering, off the SIMPLIFIED card the * WS/history API first delivers — that view drops button jump URLs and lazy * sub-card bodies. The live delivery path (daemon handleNewTopic) later runs * resolveNonsupportMessage(data), merging the card's two representations * (server-rendered + structured body.elements, incl. button open_url) into * `message.content` — the same depth the direct-@bot path uses. Re-extracting * here lets the model receive the button links, not the lossy match-time * snapshot. Only interactive cards can differ (plain text/post already carried * full content at match time). Fail-safe: a resolver miss (cross-tenant, REST * unavailable) leaves `message.content` as the simplified view and yields the * SAME text as match time, so guarding on a non-empty result never blanks a * match — it only ever upgrades. Mutates `match` in place. */ export declare function refreshListenerCardTextFromResolved(match: MessageListenerMatch, message: any): void; export declare function findMessageListenerForChat(bot: BotState, chatId: string): MessageListenerConfig | undefined; export declare function evaluateMessageListener(input: { bot: BotState; chatId: string; message: any; senderOpenId?: string; senderName?: string; senderTypeRaw?: string; /** * True when `senderOpenId` is a bot's app_id form that could NOT be resolved * to a per-app open_id (the polled history API reports bots by app_id; a * third-party bot with no cross-ref / observed mapping stays unresolved). * Such a sender defeats open_id-based exclusion, so the exclude path fails * closed. The caller resolves app_id→open_id where possible before this. */ senderIdentityUnverified?: boolean; explicitlyMentionedThisBot: boolean; }): MessageListenerMatch | undefined; /** Raw sender fields as parsed from a message (realtime event or REST history). */ export interface ListenerRawSender { senderOpenId?: string; senderName?: string; senderTypeRaw?: string; senderIdType?: string; } /** * Canonicalize a message sender to the identity domain listener configs are * keyed on (open_id). SHARED by every leg that feeds evaluateMessageListener * (30s poll backfill AND dashboard preview/run-preview) so they can never * diverge on identity handling. * * Bots are reported by app_id in the REST message-history API; a listener's * sender filters store per-app open_ids. We map app_id → open_id ONLY via an * authorization-grade map (see buildListenerBotAppIdToOpenId, which uses the * strict resolver). A bot we cannot prove stays `identityUnverified`, so the * matcher fails closed on open_id-based exclusion. Non-bot senders and bots * already carrying an open_id pass through verified. */ export declare function resolveListenerSenderIdentity(sender: ListenerRawSender, appIdToOpenId: Map): { senderOpenId?: string; identityUnverified: boolean; }; /** * Build an authorization-grade app_id → open_id map for the candidate bot * senders. Uses the STRICT resolver (three-signal agreement, never the stale * discovery stores) and only asks about CONFIGURED app_ids (the strict resolver * rejects the whole batch on any non-configured subject, and a genuine * third-party bot must stay unverified regardless). Anything unproven is absent * from the map → resolveListenerSenderIdentity marks it unverified. * * SHARED by the poll and preview legs so both resolve identity identically. */ export declare function buildListenerBotAppIdToOpenId(larkAppId: string, chatId: string, candidateAppIds: Set): Promise>; /** Collect the app_id-form bot senders present in a message batch (the only * subjects worth asking the strict resolver about). */ export declare function collectListenerBotAppIds(messages: any[], senderForMessage: (message: any) => ListenerRawSender): Set; export declare function normalizeMessageListenerPreviewLimit(raw: unknown): number; export declare function previewMessageListenerMatches(input: { bot: BotState; chatId: string; messages: any[]; limit: number; senderForMessage(message: any): ListenerRawSender; /** Authorization-grade app_id → open_id map (see buildListenerBotAppIdToOpenId). * Omitted → every bot sender in app_id form is treated as unverified. */ appIdToOpenId?: Map; explicitlyMentionedThisBot?: (message: any, senderOpenId?: string) => boolean; }): MessageListenerPreviewMatch[]; /** * Trusted operator directive for a listener match: the admin-authored name + * prompt only. Contains NO observed (untrusted) group-message bytes, so it is * safe to place in a trusted application-context block (e.g. triggerSessionTurn's * ``). The observed message must be delivered * separately through the untrusted event channel (payload / rawText). */ export declare function renderMessageListenerInstruction(match: MessageListenerMatch): string; /** * Self-contained listener prompt for callers that feed a SINGLE string to the * CLI as the whole turn (the daemon new-topic path). The trusted operator * `` and the untrusted `` are kept in separate * blocks, and the observed body is BOTH xml-escaped AND explicitly marked * `trusted="false"` so a group member cannot close `` / * `` early and forge a trusted ``. `match.prompt` * stays raw because it is operator-authored config, not attacker-controlled. */ export declare function renderMessageListenerPrompt(match: MessageListenerMatch): string; //# sourceMappingURL=message-listener.d.ts.map