type LarkRequestParams = Record<string, string | number | boolean | undefined>;
/**
 * Call a Feishu GET endpoint without a request body.
 *
 * The official SDK currently lets axios attach `{}` as `data` for generated
 * GET calls such as im.v1.message.list/get, im.v1.chat.get/list,
 * im.v1.chatMembers.isInChat and contact.v3.user.get. Some gateway
 * deployments reject GET-with-body and return HTTP 411 before the OpenAPI
 * handler sees the request. The SDK's generic `client.request()` contains an
 * explicit GET empty-body guard (`fix: #153`) while still using the SDK's
 * token/cache/auth plumbing, so route every read-only GET through it.
 *
 * `url` is the API path (e.g. `/open-apis/im/v1/chats/<id>`); path params must
 * already be interpolated by the caller. Returns the parsed JSON body
 * (`{ code, msg, data }`), identical to the generated method's resolved value.
 */
export declare function larkGet(c: any, url: string, params?: LarkRequestParams): Promise<any>;
/** Thrown when the target message has been withdrawn (Lark code 230011). */
export declare class MessageWithdrawnError extends Error {
    constructor(messageId: string);
}
/**
 * Thrown ONLY when a resource download genuinely needs (re-)authorization: no
 * usable User Token on disk, or the User Token was rejected as unauthorized
 * (HTTP 401). Callers gate the "/login" prompt on `instanceof` this — NOT on a
 * substring of the message — so an ordinary download failure (4xx/5xx for a
 * cross-tenant / card-image / withdrawn resource) is no longer misreported as
 * "missing User Token, please /login" even though a valid token was used.
 */
export declare class UserTokenMissingError extends Error {
    constructor(message: string);
}
/**
 * Send a message to a chat.
 *
 * `uuid` is an optional opt-in dedupe token (Feishu IM uuid field, ≤ 50
 * chars, 1-hour TTL — see spike report §1.2).  When supplied, the Feishu
 * server returns the original message_id for repeat requests within TTL,
 * making the send idempotent.  Workflow runtime passes the attempt's
 * idempotencyKey here so retries don't re-send.  Existing callers omit
 * the param and get exactly the pre-Step-6 behavior.
 */
export declare function sendMessage(larkAppId: string, chatId: string, content: string, msgType?: string, uuid?: string, hookContext?: Record<string, unknown>): Promise<string>;
/**
 * Reply to an existing message.  See {@link sendMessage} for the `uuid`
 * dedupe parameter — same semantics apply to replies (Feishu reply API
 * also accepts `uuid` and yields the same 1-hour idempotent return).  See
 * spike report §1.4 for the reply-specific test results, including the
 * cross-parent dedupe behavior that informs the inputHash design.
 */
export declare function replyMessage(larkAppId: string, messageId: string, content: string, msgType?: string, replyInThread?: boolean, uuid?: string, hookContext?: Record<string, unknown>): Promise<string>;
export declare function addReaction(larkAppId: string, messageId: string, emojiType: string): Promise<string>;
export declare function removeReaction(larkAppId: string, messageId: string, reactionId: string): Promise<void>;
/**
 * Resolve a user's tenant-stable `union_id` from their app-scoped `open_id`.
 * Used by cross-daemon owner checks (e.g. /relay --create peer migrate)
 * to compare identities across bot namespaces — open_id alone is
 * app-scoped, so two daemons looking at the same physical user see
 * different open_ids.
 *
 * Best-effort: returns null on API failure / missing scope / empty
 * response, so callers can fall back to other identity strategies
 * instead of failing the whole flow.
 */
export declare function resolveUnionIdFromOpenId(larkAppId: string, openId: string): Promise<string | null>;
/**
 * Best-effort 拉用户资料（名字 + 头像 URL）。拿不到（缺 scope / 不在可见
 * 范围 / 网络错误）返回 null，调用方自行回退占位。
 */
export declare function getUserProfile(larkAppId: string, userId: string, idType?: 'open_id' | 'union_id'): Promise<{
    name: string;
    avatarUrl?: string;
} | null>;
/**
 * Best-effort 判断一个 open_id 是否为「真人」（通讯录里查得到 user）。
 *
 * - code 0 且返回 user 对象 → 确定是真人 → true
 * - 查不到 / 报错 → false。这一类同时覆盖两种情况：①bot（应用不在通讯录，必然查不到）；
 *   ②本 app 缺 `contact:user.base:readonly` 读权限（这时真人也会查不到）。
 *
 * 用途：花名册（observed-bots-store）只应收 bot，不收真人——真人混进去会污染
 * `<available_bots>` 误导模型。调用方语义统一为「只在 NOT-confirmed-human 时登记」：
 *   - 有 contact 读权限（常态）→ 真人被准确剔除，登记得干净；
 *   - 缺权限 / 查询瞬时失败（降级）→ 一律按非真人放行登记。对 /introduce 这本就「全部登记」，
 *     无回退损失；但对 /grant 自动登记这条**新增**路径，降级时真人会被误登记——这是个新增
 *     的（窄）污染面，靠 `contact:user.base:readonly` 已是 critical scope、启动自检缺失即 DM
 *     管理员来收敛，不是「与现状等价」。若要彻底消除需区分 permission/network 与 user-not-found
 *     错误码（user-not-found 才判 bot），属后续增强。
 */
export declare function isHumanOpenId(larkAppId: string, openId: string): Promise<boolean>;
export declare function sendUserMessage(larkAppId: string, openId: string, content: string, msgType?: string): Promise<string>;
export declare function getChatInfo(larkAppId: string, chatId: string): Promise<{
    userCount: number;
    botCount: number;
}>;
/**
 * List the open_ids of a chat's (user) members, paginating until exhausted.
 * Used by the 主动开工 场景① gate to check whether any of the bot's allowedUsers
 * is a member of a chat the bot was just added to. Open_ids are app-scoped, so
 * the result is only comparable against the SAME bot's resolvedAllowedUsers.
 *
 * Throws on API failure (e.g. missing `im:chat`/member-read scope) so the
 * caller can decide how to degrade — it does NOT swallow errors, because a
 * silent empty list would look like "no allowedUser present" and wrongly
 * suppress auto-start.
 */
export declare function listChatMemberOpenIds(larkAppId: string, chatId: string): Promise<string[]>;
/**
 * Resolve a chat's display name (the user-facing group title). Returns `null`
 * on any failure (chatId is unknown to this bot, network error, bot not in
 * chat etc.) — callers should fall back to displaying the raw chatId so the
 * UI degrades gracefully rather than rendering "undefined". For p2p chats the
 * returned name may be an empty string; treat that as "no display name" and
 * also fall back. */
export declare function getChatName(larkAppId: string, chatId: string): Promise<string | null>;
export declare function getChatNameAndMode(larkAppId: string, chatId: string): Promise<{
    name: string | null;
    mode: ChatMode;
}>;
/** Lark chat-mode classification used by botmux to decide session scope:
 *   - 'topic'  → 话题群: every top-level message becomes a new thread, so
 *                botmux always uses thread-scope sessions. Two underlying
 *                Lark shapes collapse into this:
 *                  * chat_mode='topic' (rare; creation-time classification)
 *                  * group_message_type='thread' (the toggle Lark clients
 *                    expose as "话题/聊天" — flips on the fly, chat_mode stays
 *                    'group'). This is the common case for user-converted
 *                    话题群.
 *   - 'group'  → 普通群: top-level messages stay top-level, so botmux uses
 *                chat-scope by default; user-initiated threads still get
 *                their own thread-scope sessions
 *   - 'p2p'    → direct message: equivalent to 普通群 from a routing
 *                perspective (chat-scope by default) */
export type ChatMode = 'group' | 'topic' | 'p2p';
/** Resolve the conversational topology of a chat (话题群 vs 普通群 vs p2p).
 *
 *  Cached per (appId, chatId) for 5 minutes. Errors fall back to 'group' so a
 *  flaky Lark API doesn't break message routing — chat-scope is the safer
 *  default than incorrectly forcing a thread, since users can always reply
 *  in-thread to escape it.
 *
 *  Calling this with a chat that's already known to be p2p (from
 *  message.chat_type === 'p2p') is fine but wasteful — prefer skipping the
 *  call in that case. */
/**
 * Resolve a chat's mode by hitting the API directly. Returns `'unknown'` when
 * the chat type can't be confirmed (non-zero code or thrown) — it does NOT guess
 * `'group'`. Use this for privacy-critical gates that must fail closed (private
 * `/card`). Always queries the API (no cache read), but populates the shared
 * cache on success so a following {@link getChatMode} hits it.
 */
export declare function getChatModeStrict(larkAppId: string, chatId: string): Promise<ChatMode | 'unknown'>;
export declare function getCachedChatMode(larkAppId: string, chatId: string): ChatMode | undefined;
export declare function getChatMode(larkAppId: string, chatId: string, options?: {
    forceRefresh?: boolean;
}): Promise<ChatMode>;
/**
 * Recall (delete) a message. Returns `true` only when Lark confirms success,
 * `false` on SDK throw or a non-zero response code — so callers that need to
 * know whether the withdraw actually happened (e.g. grant-card withdraw) can
 * fall back instead of assuming success. Fire-and-forget callers can ignore it.
 */
export declare function deleteMessage(larkAppId: string, messageId: string): Promise<boolean>;
/** Error code Feishu returns from `ephemeral/v1/send` when the target chat is a
 *  topic / thread chat. Ephemeral cards only work in plain `group` chats (see
 *  /tmp design notes: empirically code 18053 `chat can not be thread`). */
export declare const LARK_CODE_EPHEMERAL_NOT_GROUP = 18053;
/**
 * Send a "visible-to-one-user" ephemeral card (`ephemeral/v1/send`). The card is
 * only shown to `openId`, sends no notification, and — unlike normal messages —
 * **cannot be PATCH-updated** (legacy interface). Multiple recipients require one
 * call each. Only works in plain `group` chats; topic/thread/p2p chats reject
 * with {@link LARK_CODE_EPHEMERAL_NOT_GROUP}. Returns the ephemeral message_id.
 */
export declare function sendEphemeralCard(larkAppId: string, chatId: string, openId: string, cardJson: string): Promise<string>;
export declare function updateMessage(larkAppId: string, messageId: string, cardJson: string): Promise<void>;
export declare function getMessageDetail(larkAppId: string, messageId: string, options?: {
    userCardContent?: boolean;
}): Promise<any>;
export declare function downloadMessageResource(larkAppId: string, messageId: string, fileKey: string, type: 'image' | 'file', savePath: string): Promise<void>;
export declare function uploadImage(larkAppId: string, imagePath: string): Promise<string>;
export declare function uploadFile(larkAppId: string, filePath: string, opts?: {
    duration?: number;
}): Promise<string>;
/**
 * Resolve emails to Lark open_ids via batch user lookup.
 * Accepts mixed input: items starting with "ou_" are kept as-is; everything else
 * must be a full email address (e.g. "alice@example.com") and is looked up.
 * Returns an array of open_ids (unresolvable entries are dropped with a warning).
 */
/**
 * Resolve a raw allowedUsers list (mix of `ou_*` open_ids and emails) into
 * open_ids, AND return a `raw entry → resolved open_id` map. The map lets
 * `/revoke` delete the correct raw entry (email OR open_id) from bots.json so
 * the revocation survives a restart. open_id entries map to themselves;
 * resolved emails are keyed by the EXACT raw email string from the config
 * (matched case-insensitively against the API's returned email) so the map key
 * always equals what's in `allowedUsers`. Unresolvable emails are dropped.
 */
export declare function resolveAllowedUsersWithMap(larkAppId: string, raw: string[]): Promise<{
    resolved: string[];
    map: Map<string, string>;
}>;
/**
 * Best-effort resolve a user's open_id → canonical union_id (+ display name)
 * for pairing-login. Requires `contact:user.base:readonly` scope; on failure
 * (no scope / API error) returns {} so callers degrade to open_id-only identity.
 */
export declare function resolveUserUnionId(larkAppId: string, openId: string): Promise<{
    unionId?: string;
    name?: string;
}>;
export declare function resolveAllowedUsers(larkAppId: string, raw: string[]): Promise<string[]>;
export declare function listThreadMessages(larkAppId: string, chatId: string, rootMessageId: string, pageSize?: number): Promise<any[]>;
/** List chat-container messages, most-recent first but returned chronologically
 *  (oldest → newest, capped at `pageSize`). Used by `botmux history` for
 *  chat-scope sessions (普通群整群一会话): no thread to walk, so we walk the
 *  chat itself. We page in Desc order so a long-running chat returns its TAIL,
 *  not its head — that's the context the caller wants. The caller controls
 *  how much history they get via `pageSize`. */
export declare function listChatMessages(larkAppId: string, chatId: string, pageSize?: number): Promise<any[]>;
export interface AmbientChatMessageOptions {
    /**
     * Exclude messages at/after this timestamp (Lark create_time, milliseconds as
     * a string). Used by `/t` thread sessions to fetch the chat tail that existed
     * before the thread was opened, avoiding bot cards/replies from the new
     * thread polluting the context.
     */
    beforeCreateTime?: string;
    /** Exclude the current thread root and its replies from the chat tail. */
    excludeRootMessageId?: string;
    /** How many chat-container messages to scan before filtering. */
    scanLimit?: number;
}
export declare function filterAmbientChatMessages(messages: any[], pageSize: number, options?: Pick<AmbientChatMessageOptions, 'beforeCreateTime' | 'excludeRootMessageId'>): any[];
/**
 * List recent chat-container messages as ambient context for a thread session.
 *
 * This intentionally differs from `listChatMessages`: callers want the newest
 * `pageSize` messages AFTER filtering out the current thread and (optionally)
 * messages created after the thread root. We therefore may scan more than
 * `pageSize` items and cap only after filtering.
 */
export declare function listAmbientChatMessages(larkAppId: string, chatId: string, pageSize?: number, options?: AmbientChatMessageOptions): Promise<any[]>;
/**
 * Check which bots are in a chat.
 *
 * Two-source merge:
 * 1. **configured** — bots in `bots.json` (this daemon and sibling daemons on
 *    the same host). Probed via `isInChat` per bot; only those actually in
 *    the chat are returned. open_id is corrected via the per-app cross-ref.
 * 2. **introduce** — bots discovered passively from the `/introduce`
 *    collaboration handshake, persisted per observer × chat in
 *    `observed-bots-<larkAppId>-<chatId>.json`. Critical for external bots
 *    run by other botmux daemons (or even non-botmux bots) that aren't in
 *    our bots.json but the user wants this daemon to know about. Read with
 *    the caller's `larkAppId` so open_ids match this app's perspective.
 *
 * Configured wins on open_id collision (`source: 'configured'`); observed
 * entries fill in everyone else (`source: 'introduce'`). Observed entries
 * carry `larkAppId=""` since they don't map to any local-daemon-managed bot.
 */
export type ChatBotMember = {
    larkAppId: string;
    openId: string;
    name: string;
    displayName: string;
    source: 'configured' | 'introduce';
    /** Short capability label (team-level), for roster discovery. Configured bots only. */
    capability?: string;
    /** Whether this bot has a team-level role registered. Configured bots only. */
    hasTeamRole: boolean;
    /**
     * Whether the observing app (the `larkAppId` arg) can RELIABLY @-mention this
     * member. Lark open_id is per-app scoped, so a bot's self-reported open_id is
     * not usable by another app. Reliable only when learned via cross-ref (from
     * @mention events) or via /introduce (observed, already observer-scoped).
     */
    mentionable: boolean;
    mentionSource: 'cross-ref' | 'self' | 'observed' | 'fallback';
};
export declare function listChatBotMembers(larkAppId: string, chatId: string): Promise<ChatBotMember[]>;
export {};
//# sourceMappingURL=client.d.ts.map