{"version":3,"file":"index.cjs","names":["net","DEFAULT_THROTTLE_MS","DEFAULT_THROTTLE_CHARS","LoggerProxy","LoggerLevel","defaultLogger","Client","Domain","EventDispatcher","internalCache","HttpsProxyAgent","WSClient"],"sources":["../src/chat-member-cache.ts","../src/chat-mode-cache.ts","../src/comments.ts","../src/keepalive.ts","../src/normalize/mentions.ts","../src/normalize/utils.ts","../src/normalize/converters/audio.ts","../src/normalize/converters/calendar.ts","../src/normalize/converters/fallback.ts","../src/normalize/converters/file.ts","../src/normalize/converters/folder.ts","../src/normalize/converters/hongbao.ts","../src/normalize/converters/image.ts","../src/normalize/converters/interactive/card-walker.ts","../src/normalize/converters/interactive/index.ts","../src/normalize/converters/location.ts","../src/normalize/converters/merge-forward.ts","../src/normalize/converters/post.ts","../src/normalize/converters/share.ts","../src/normalize/converters/sticker.ts","../src/normalize/converters/system.ts","../src/normalize/converters/text.ts","../src/normalize/converters/todo.ts","../src/normalize/converters/video.ts","../src/normalize/converters/video-chat.ts","../src/normalize/converters/vote.ts","../src/normalize/registry.ts","../src/normalize/bot-added.ts","../src/normalize/card-action.ts","../src/normalize/comment.ts","../src/normalize/reaction.ts","../src/normalize/index.ts","../src/types.ts","../src/outbound/errors.ts","../src/outbound/markdown/compose-mentions.ts","../src/outbound/markdown/splitter.ts","../src/outbound/markdown/optimize-style.ts","../src/outbound/markdown/to-post.ts","../src/outbound/media/duration-mp4.ts","../src/outbound/media/duration-ogg.ts","../src/outbound/media/ssrf-guard.ts","../src/outbound/media/uploader.ts","../src/outbound/retry.ts","../src/outbound/routing.ts","../src/outbound/streaming/throttle.ts","../src/outbound/streaming/update-queue.ts","../src/outbound/streaming/card-stream.ts","../src/outbound/streaming/markdown-stream.ts","../src/outbound/sender.ts","../src/outbound/markdown/resolve-mentions.ts","../src/safety/chat-pipeline.ts","../src/safety/types.ts","../src/safety/dedup-cache.ts","../src/safety/loop-guard.ts","../src/safety/policy-gate.ts","../src/safety/processing-lock.ts","../src/safety/stale-detector.ts","../src/safety/index.ts","../src/channel.ts"],"sourcesContent":["import type { ChatMember } from './types';\n\n/**\n * Per-chat roster cache backing these consumers: `getChatMembers` (source\n * 'api', authoritative users), `getChatBots` (source 'api', authoritative\n * bots), inbound mention collection (source 'mention', can carry bots),\n * `senderName` resolution and \"@name → open_id\" normalization.\n *\n * Two safety invariants:\n *   - A display name that maps to more than one openId is AMBIGUOUS and\n *     resolves to `undefined` — never last-writer-wins — so a name collision\n *     (e.g. an attacker renaming to a real bot's name) can't misroute an @.\n *     An 'api' name→openId is never silently replaced by a 'mention' one.\n *   - Entries expire after a TTL and the number of cached chats is capped, so\n *     stale or poisoned mappings don't linger.\n *\n * Clock, TTL and capacity are injectable so time and eviction are deterministic\n * in tests.\n */\nconst AMBIGUOUS = Symbol('ambiguous');\ntype NameTarget = string | typeof AMBIGUOUS;\n\ninterface Roster {\n  /** openId → display name. 'api' names win over 'mention' names for the same openId. */\n  byOpenId: Map<string, string>;\n  /** display name → single openId, or AMBIGUOUS when the name is shared. */\n  byName: Map<string, NameTarget>;\n  /** openId → which source last set its name, so 'api' isn't overwritten by 'mention'. */\n  nameSource: Map<string, MemberSource>;\n  /** last user list from `getChatMembers` — served back on a cache hit. */\n  apiMembers?: ChatMember[];\n  /** when {@link apiMembers} was fetched — its own TTL, so `mention` writes don't extend the API cache. */\n  apiFetchedAt?: number;\n  /** last bot list from `getChatBots` — cached separately so it can't clobber {@link apiMembers}. */\n  apiBots?: ChatMember[];\n  /** when {@link apiBots} was fetched — its own TTL. */\n  apiBotsFetchedAt?: number;\n  updatedAt: number;\n}\n\nexport type MemberSource = 'api' | 'mention';\n\nexport interface ChatMemberCacheOptions {\n  now?: () => number;\n  ttlMs?: number;\n  maxChats?: number;\n  maxEntriesPerChat?: number;\n}\n\nconst DEFAULT_TTL_MS = 5 * 60_000;\nconst DEFAULT_MAX_CHATS = 500;\nconst DEFAULT_MAX_ENTRIES_PER_CHAT = 1000;\n\nexport class ChatMemberCache {\n  private readonly chats = new Map<string, Roster>();\n  private readonly now: () => number;\n  private readonly ttlMs: number;\n  private readonly maxChats: number;\n  private readonly maxEntriesPerChat: number;\n\n  constructor(opts: ChatMemberCacheOptions = {}) {\n    this.now = opts.now ?? Date.now;\n    this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;\n    this.maxChats = opts.maxChats ?? DEFAULT_MAX_CHATS;\n    this.maxEntriesPerChat = opts.maxEntriesPerChat ?? DEFAULT_MAX_ENTRIES_PER_CHAT;\n  }\n\n  setMembers(chatId: string, members: ChatMember[], source: MemberSource): void {\n    const roster = this.rosterForWrite(chatId);\n    this.indexAll(roster, members, source);\n    if (source === 'api') {\n      roster.apiMembers = members;\n      roster.apiFetchedAt = this.now();\n    }\n    roster.updatedAt = this.now();\n    this.store(chatId, roster);\n  }\n\n  /** Cache an authoritative bot list (from `getChatBots`) — separate from the\n   *  user list so neither clobbers the other. Indexed as an 'api' source. */\n  setBots(chatId: string, bots: ChatMember[]): void {\n    const roster = this.rosterForWrite(chatId);\n    this.indexAll(roster, bots, 'api');\n    roster.apiBots = bots;\n    roster.apiBotsFetchedAt = this.now();\n    roster.updatedAt = this.now();\n    this.store(chatId, roster);\n  }\n\n  /**\n   * The last API user list, or `undefined` when absent or expired. Uses its\n   * own `apiFetchedAt` clock so ongoing 'mention' writes to an active chat\n   * don't keep the API cache alive past the TTL (spec §5).\n   */\n  getMembers(chatId: string): ChatMember[] | undefined {\n    return this.liveList(chatId, 'members');\n  }\n\n  /** The last API bot list, or `undefined` when absent or expired. */\n  getBots(chatId: string): ChatMember[] | undefined {\n    return this.liveList(chatId, 'bots');\n  }\n\n  private liveList(chatId: string, kind: 'members' | 'bots'): ChatMember[] | undefined {\n    const roster = this.liveRoster(chatId);\n    const list = kind === 'members' ? roster?.apiMembers : roster?.apiBots;\n    const fetchedAt = kind === 'members' ? roster?.apiFetchedAt : roster?.apiBotsFetchedAt;\n    if (!list || fetchedAt === undefined) return undefined;\n    if (this.now() - fetchedAt > this.ttlMs) return undefined;\n    return list;\n  }\n\n  private indexAll(roster: Roster, members: ChatMember[], source: MemberSource): void {\n    for (const m of members) {\n      if (!m.id || !m.name) continue;\n      this.indexMember(roster, m.id, m.name, source);\n    }\n  }\n\n  resolveName(chatId: string, openId: string): string | undefined {\n    return this.liveRoster(chatId)?.byOpenId.get(openId);\n  }\n\n  resolveOpenId(chatId: string, name: string): string | undefined {\n    const target = this.liveRoster(chatId)?.byName.get(name);\n    return typeof target === 'string' ? target : undefined;\n  }\n\n  private indexMember(roster: Roster, openId: string, name: string, source: MemberSource): void {\n    // openId → name: an 'api' name is authoritative; don't let a later\n    // 'mention' name overwrite it, but a fresh 'api' name always wins.\n    const prevSource = roster.nameSource.get(openId);\n    const prevName = roster.byOpenId.get(openId);\n    if (source === 'api' || prevSource !== 'api') {\n      roster.byOpenId.set(openId, name);\n      roster.nameSource.set(openId, source);\n      // On a rename, drop the member's previous name→openId entry so the\n      // reverse index doesn't accumulate every historical display name of a\n      // member that renames repeatedly — but only when it still uniquely\n      // pointed here (an ambiguous/shared name is left alone).\n      if (prevName !== undefined && prevName !== name && roster.byName.get(prevName) === openId) {\n        roster.byName.delete(prevName);\n      }\n    }\n\n    // name → openId: a second distinct openId for the same name makes it\n    // ambiguous (unresolvable) regardless of source.\n    const existing = roster.byName.get(name);\n    if (existing === undefined) {\n      roster.byName.set(name, openId);\n    } else if (existing !== openId) {\n      roster.byName.set(name, AMBIGUOUS);\n    }\n\n    this.capEntries(roster);\n  }\n\n  /** Per-chat hard backstop: evict oldest-inserted entries past the cap. */\n  private capEntries(roster: Roster): void {\n    evictOldest(roster.byName, this.maxEntriesPerChat);\n    evictOldest(roster.byOpenId, this.maxEntriesPerChat);\n    evictOldest(roster.nameSource, this.maxEntriesPerChat);\n  }\n\n  private rosterForWrite(chatId: string): Roster {\n    return (\n      this.liveRoster(chatId) ?? {\n        byOpenId: new Map(),\n        byName: new Map(),\n        nameSource: new Map(),\n        updatedAt: this.now(),\n      }\n    );\n  }\n\n  private liveRoster(chatId: string): Roster | undefined {\n    const roster = this.chats.get(chatId);\n    if (!roster) return undefined;\n    if (this.now() - roster.updatedAt > this.ttlMs) {\n      this.chats.delete(chatId);\n      return undefined;\n    }\n    return roster;\n  }\n\n  /** Re-insert (LRU touch) and evict the oldest chats past the cap. */\n  private store(chatId: string, roster: Roster): void {\n    this.chats.delete(chatId);\n    this.chats.set(chatId, roster);\n    evictOldest(this.chats, this.maxChats);\n  }\n}\n\n/** Drop oldest-inserted keys until the map is within `max`. */\nfunction evictOldest<K, V>(map: Map<K, V>, max: number): void {\n  while (map.size > max) {\n    const oldest = map.keys().next().value;\n    if (oldest === undefined) break;\n    map.delete(oldest);\n  }\n}\n","export type ChatMode = 'p2p' | 'group' | 'topic';\n\n/**\n * In-memory cache for chat mode lookups. Feishu omits chat mode from message\n * events, so the only way to know whether a chat is a p2p / ordinary group /\n * topic group is an extra `chat.get` — which is stable for a chat's lifetime,\n * hence cacheable by chatId.\n *\n * On lookup failure (network / permission / unknown chatId) the resolver\n * returns 'group' (the conservative default: treat as an ordinary chat) and\n * does NOT poison the cache, so a later message gets another try.\n */\nexport class ChatModeCache {\n  private readonly cache = new Map<string, ChatMode>();\n\n  async resolve(chatId: string, fetch: (chatId: string) => Promise<ChatMode>): Promise<ChatMode> {\n    const hit = this.cache.get(chatId);\n    if (hit) return hit;\n    try {\n      const mode = await fetch(chatId);\n      this.cache.set(chatId, mode);\n      return mode;\n    } catch {\n      return 'group';\n    }\n  }\n\n  invalidate(chatId: string): void {\n    this.cache.delete(chatId);\n  }\n}\n","import type { Client } from '@larksuiteoapi/node-sdk';\nimport type { Logger } from './internal';\n\n/**\n * Cloud-doc comment surface (L4 outbound). Wraps the Feishu drive-comment\n * APIs and internalizes the quirks any bot integrating doc comments would\n * otherwise hit:\n *   - wiki node → underlying obj_token resolution\n *   - `fileComment.get` returning 1069307 for some comment types → `.list`\n *     pagination fallback\n *   - in-thread reply rejected with 1069302 on whole-document comments →\n *     fresh top-level comment fallback\n *   - comment reaction add/delete being the same endpoint with an `action`\n *     field (and not returning a reaction_id)\n *\n * Business concerns stay with the caller: which reply is \"the question\",\n * prompt assembly, markdown stripping, session mapping. This surface only\n * speaks the Feishu comment protocol.\n */\n\nexport type CommentFileType = 'doc' | 'docx' | 'sheet' | 'file';\n\n/** File types the drive comment APIs support here. Others (slides, bitable,\n *  mindnote) use different APIs and are out of scope. */\nconst SUPPORTED_FILE_TYPES = new Set<string>(['doc', 'docx', 'sheet', 'file']);\n\nexport interface CommentTarget {\n  fileToken: string;\n  fileType: CommentFileType;\n}\n\nexport interface CommentReplyContentElement {\n  type: 'text_run' | 'docs_link' | 'person';\n  text_run?: { text: string };\n  docs_link?: { url: string };\n  person?: { user_id: string };\n}\n\nexport interface CommentReply {\n  reply_id?: string;\n  content?: { elements?: CommentReplyContentElement[] };\n}\n\nexport interface FetchedComment {\n  commentId: string;\n  replies: CommentReply[];\n  /** The text the user selected (inline comment); empty for whole-doc. */\n  quote?: string;\n  /** True when the comment targets the whole document rather than a selection. */\n  isWhole: boolean;\n}\n\ninterface CommentGetResponse {\n  data?: { reply_list?: { replies?: CommentReply[] }; quote?: string; is_whole?: boolean };\n}\ninterface CommentListItem {\n  comment_id?: string;\n  reply_list?: { replies?: CommentReply[] };\n  is_whole?: boolean;\n  quote?: string;\n}\ninterface CommentListResponse {\n  data?: { items?: CommentListItem[]; has_more?: boolean; page_token?: string };\n}\n\nfunction errCode(err: unknown): number | undefined {\n  return (err as { response?: { data?: { code?: number } } })?.response?.data?.code;\n}\n\nexport class CommentSurface {\n  constructor(\n    private readonly client: Client,\n    private readonly logger: Logger,\n  ) {}\n\n  /**\n   * Resolve the (fileToken, fileType) to hit for the comment APIs. If the\n   * token is a wiki node, swap to its underlying obj_token; otherwise pass\n   * through. Returns `null` when the file type is unsupported.\n   */\n  async resolveTarget(fileToken: string, fileType: string): Promise<CommentTarget | null> {\n    if (!SUPPORTED_FILE_TYPES.has(fileType)) return null;\n    const passthrough: CommentTarget = {\n      fileToken,\n      fileType: fileType as CommentFileType,\n    };\n\n    // Try wiki node lookup; non-wiki tokens throw here → fall back to passthrough.\n    try {\n      const r = (await this.client.wiki.v2.space.getNode({\n        params: { token: fileToken },\n      })) as { data?: { node?: { obj_token?: string; obj_type?: string } } };\n      const node = r?.data?.node;\n      if (node?.obj_token && node.obj_type && SUPPORTED_FILE_TYPES.has(node.obj_type)) {\n        this.logger.info?.('channel: comment wiki-resolved', {\n          objToken: node.obj_token,\n          objType: node.obj_type,\n        });\n        return {\n          fileToken: node.obj_token,\n          fileType: node.obj_type as CommentFileType,\n        };\n      }\n    } catch {\n      // not a wiki node — fall through\n    }\n    return passthrough;\n  }\n\n  /**\n   * Fetch a comment with its replies. Tries `fileComment.get`; for comment\n   * types that return 1069307 there, falls back to paginating `.list` and\n   * locating the comment by id. Returns `null` when not found.\n   */\n  async fetch(target: CommentTarget, commentId: string): Promise<FetchedComment | null> {\n    try {\n      const r = (await this.client.drive.v1.fileComment.get({\n        params: { file_type: target.fileType },\n        path: { file_token: target.fileToken, comment_id: commentId },\n      })) as CommentGetResponse;\n      return {\n        commentId,\n        replies: r?.data?.reply_list?.replies ?? [],\n        quote: r?.data?.quote || undefined,\n        isWhole: Boolean(r?.data?.is_whole),\n      };\n    } catch (err) {\n      this.logger.warn?.('channel: comment get failed, falling back to list', {\n        code: errCode(err),\n      });\n      const found = await this.findViaList(target, commentId);\n      if (!found) return null;\n      return {\n        commentId,\n        replies: found.reply_list?.replies ?? [],\n        quote: found.quote || undefined,\n        isWhole: Boolean(found.is_whole),\n      };\n    }\n  }\n\n  /**\n   * Reply to a comment in-thread. Whole-document comments reject in-thread\n   * replies with 1069302 (they have no thread, only a flat list) — in that\n   * case post a fresh top-level comment instead.\n   *\n   * When the caller already knows the comment is whole-document (e.g. from\n   * {@link FetchedComment.isWhole}), pass `{ topLevel: true }` to skip the\n   * doomed in-thread probe and post top-level directly — saving one\n   * round-trip. See {@link replyTopLevel} for the explicit form.\n   */\n  async reply(\n    target: CommentTarget,\n    commentId: string,\n    text: string,\n    opts?: { topLevel?: boolean },\n  ): Promise<void> {\n    if (opts?.topLevel) {\n      await this.replyTopLevel(target, text);\n      return;\n    }\n\n    const url =\n      `/open-apis/drive/v1/files/${encodeURIComponent(target.fileToken)}/comments/` +\n      `${encodeURIComponent(commentId)}/replies?file_type=${encodeURIComponent(target.fileType)}`;\n    try {\n      await this.client.request({\n        method: 'POST',\n        url,\n        data: { content: { elements: [{ type: 'text_run', text_run: { text } }] } },\n      });\n      this.logger.info?.('channel: comment replied', { mode: 'in-thread' });\n      return;\n    } catch (err) {\n      // 1069302: whole-document comments don't accept in-thread replies.\n      if (errCode(err) !== 1069302) throw err;\n      this.logger.warn?.('channel: comment reply rejected, posting fresh top-level', {\n        code: 1069302,\n      });\n    }\n\n    await this.replyTopLevel(target, text);\n    this.logger.info?.('channel: comment replied', { mode: 'new-top-level' });\n  }\n\n  /**\n   * Post a fresh top-level comment on the file (no in-thread probe). Use this\n   * when you already know in-thread replies won't apply — chiefly\n   * whole-document comments, where {@link reply} would otherwise waste a\n   * round-trip discovering 1069302.\n   */\n  async replyTopLevel(target: CommentTarget, text: string): Promise<void> {\n    await this.client.drive.v1.fileComment.create({\n      params: { file_type: target.fileType as 'doc' | 'docx' },\n      path: { file_token: target.fileToken },\n      data: {\n        reply_list: {\n          replies: [{ content: { elements: [{ type: 'text_run', text_run: { text } }] } }],\n        },\n      },\n    });\n  }\n\n  /**\n   * Add a reaction to a comment reply. Doc-comment reactions use a dedicated\n   * endpoint (separate from IM message reactions); add/delete are the same\n   * POST distinguished by an `action` field, and it returns no reaction_id.\n   * Returns `true` on success. Defaults to the \"Typing\" emoji.\n   */\n  async addReaction(\n    target: CommentTarget,\n    replyId: string,\n    emojiType = 'Typing',\n  ): Promise<boolean> {\n    return this.reaction(target, replyId, emojiType, 'add');\n  }\n\n  /** Remove a previously-added comment reaction. Same endpoint, action=delete. */\n  async removeReaction(\n    target: CommentTarget,\n    replyId: string,\n    emojiType = 'Typing',\n  ): Promise<void> {\n    await this.reaction(target, replyId, emojiType, 'delete');\n  }\n\n  private async reaction(\n    target: CommentTarget,\n    replyId: string,\n    emojiType: string,\n    action: 'add' | 'delete',\n  ): Promise<boolean> {\n    const url =\n      `/open-apis/drive/v2/files/${encodeURIComponent(target.fileToken)}/comments/reaction` +\n      `?file_type=${encodeURIComponent(target.fileType)}`;\n    try {\n      await this.client.request({\n        method: 'POST',\n        url,\n        data: { action, reply_id: replyId, reaction_type: emojiType },\n      });\n      this.logger.info?.(`channel: comment reaction ${action}`, {\n        fileToken: target.fileToken,\n        replyId,\n      });\n      return true;\n    } catch (err) {\n      this.logger.warn?.(`channel: comment reaction ${action} failed`, {\n        fileToken: target.fileToken,\n        replyId,\n        err: err instanceof Error ? err.message : String(err),\n      });\n      return false;\n    }\n  }\n\n  private async findViaList(\n    target: CommentTarget,\n    commentId: string,\n  ): Promise<CommentListItem | null> {\n    let pageToken: string | undefined;\n    for (let page = 0; page < 10; page++) {\n      const r = (await this.client.drive.v1.fileComment.list({\n        params: {\n          file_type: target.fileType,\n          page_size: 100,\n          ...(pageToken ? { page_token: pageToken } : {}),\n        },\n        path: { file_token: target.fileToken },\n      })) as CommentListResponse;\n      const items = r?.data?.items ?? [];\n      const hit = items.find((it) => it.comment_id === commentId);\n      if (hit) return hit;\n      if (!r?.data?.has_more || !r.data.page_token) break;\n      pageToken = r.data.page_token;\n    }\n    return null;\n  }\n}\n","import type { Logger, WSConnectionStatus } from './internal';\n\n/**\n * App-level keepalive loop — defense-in-depth against silent WS / network\n * issues the SDK's internal ping watchdog might miss. Sunk from bridge's\n * `bot/keepalive.ts`.\n *\n *  1. Independent timer (default 15s), untied from the server-pushed ping\n *     cadence, to catch issues earlier and from a different angle.\n *  2. Wake-up detection — if the timer was skipped for > SLEEP_DETECT_MS the\n *     machine likely slept; reset counters and bail for this tick.\n *  3. Timer-storm guard — on wake, multiple intervals can fire back-to-back.\n *  4. HTTP probe — before force-reconnecting, check the Feishu domain is\n *     reachable; if not it's a network outage, not a WS problem.\n *  5. Counter-based debounce — only force-reconnect after DEAD_THRESHOLD\n *     consecutive ticks confirm WS is not connected.\n *\n * The \"what to do when reconnect can't recover\" policy stays with the app via\n * `onUnrecoverable` (e.g. restart the process).\n */\n\nconst DEFAULT_INTERVAL_MS = 15_000;\nconst SLEEP_DETECT_MS = 30_000;\nconst TIMER_STORM_GUARD_MS = 5_000;\nconst HTTP_PROBE_TIMEOUT_MS = 5_000;\nconst DEAD_THRESHOLD = 3;\nconst NETWORK_DOWN_LOG_EVERY = 20; // ~ every 5 min while network is down\n\nexport interface KeepaliveDeps {\n  getConnectionStatus: () => WSConnectionStatus | undefined;\n  /** HTTP probe target (the Feishu/Lark domain base URL). */\n  domain: string;\n  /** Tear down and re-establish the WebSocket. */\n  forceReconnect: () => Promise<void>;\n  /** Called when `forceReconnect` itself throws — i.e. reconnection failed\n   *  and the app must decide (e.g. restart the process). */\n  onUnrecoverable?: (err: unknown) => void;\n  logger: Logger;\n  intervalMs?: number;\n}\n\nexport interface KeepaliveHandle {\n  stop(): void;\n}\n\nexport function startKeepalive(deps: KeepaliveDeps): KeepaliveHandle {\n  const { getConnectionStatus, domain, forceReconnect, onUnrecoverable, logger } = deps;\n  const intervalMs = deps.intervalMs ?? DEFAULT_INTERVAL_MS;\n\n  let lastTick = 0;\n  let consecutiveDown = 0;\n  let networkDownTicks = 0;\n  let stopped = false;\n\n  const tick = async (): Promise<void> => {\n    if (stopped) return;\n    const now = Date.now();\n    const sinceLast = lastTick > 0 ? now - lastTick : 0;\n\n    // (3) Timer storm — multiple intervals firing at once on wake-up.\n    if (sinceLast > 0 && sinceLast < TIMER_STORM_GUARD_MS) return;\n    // (2) Sleep detection — machine likely just woke from sleep.\n    if (sinceLast > SLEEP_DETECT_MS) {\n      logger.info?.('channel: keepalive wake-up', { sleptMs: sinceLast });\n      consecutiveDown = 0;\n      networkDownTicks = 0;\n      lastTick = now;\n      return;\n    }\n    lastTick = now;\n\n    const status = getConnectionStatus();\n    if (!status) return; // not initialized yet\n    if (status.state === 'connected') {\n      if (consecutiveDown > 0) {\n        logger.info?.('channel: keepalive recovered', { afterTicks: consecutiveDown });\n      }\n      consecutiveDown = 0;\n      networkDownTicks = 0;\n      return;\n    }\n\n    // (4) Is the network even reachable? If not, force-reconnect won't help.\n    const reachable = await httpProbe(domain);\n    if (!reachable) {\n      networkDownTicks++;\n      if (networkDownTicks === 1 || networkDownTicks % NETWORK_DOWN_LOG_EVERY === 0) {\n        logger.warn?.('channel: network unreachable', { domain, networkDownTicks });\n      }\n      consecutiveDown = 0;\n      return;\n    }\n    if (networkDownTicks > 0) {\n      logger.info?.('channel: network reachable again', { afterTicks: networkDownTicks });\n      networkDownTicks = 0;\n    }\n\n    // Network reachable but WS not connected → WS is stuck.\n    consecutiveDown++;\n    logger.warn?.('channel: keepalive ws-stuck', {\n      state: status.state,\n      reconnectAttempts: status.reconnectAttempts,\n      consecutiveDown,\n    });\n\n    // (5) Debounce — wait for DEAD_THRESHOLD ticks before force-reconnecting.\n    if (consecutiveDown >= DEAD_THRESHOLD) {\n      logger.warn?.('channel: keepalive force-reconnect', { state: status.state });\n      consecutiveDown = 0;\n      try {\n        await forceReconnect();\n      } catch (err) {\n        logger.error?.('channel: keepalive force-reconnect failed', err);\n        onUnrecoverable?.(err);\n      }\n    }\n  };\n\n  const timer = setInterval(() => {\n    void tick().catch((err) => logger.error?.('channel: keepalive tick failed', err));\n  }, intervalMs);\n  // Don't keep the event loop alive solely for the heartbeat.\n  timer.unref?.();\n\n  return {\n    stop() {\n      stopped = true;\n      clearInterval(timer);\n    },\n  };\n}\n\nasync function httpProbe(domain: string): Promise<boolean> {\n  const target = /^https?:\\/\\//i.test(domain) ? domain : 'https://open.feishu.cn';\n  try {\n    const ctrl = new AbortController();\n    const timer = setTimeout(() => ctrl.abort(), HTTP_PROBE_TIMEOUT_MS);\n    try {\n      const res = await fetch(target, { method: 'HEAD', signal: ctrl.signal });\n      // Any HTTP response (even 4xx/5xx) means the host answered → reachable.\n      return res.status > 0;\n    } finally {\n      clearTimeout(timer);\n    }\n  } catch {\n    return false;\n  }\n}\n","import type { MentionInfo } from '../types';\nimport type { ConvertContext, RawMention } from './context';\n\nexport function isMentionAll(m: RawMention): boolean {\n  return m.key === '@_all';\n}\n\nexport interface MentionExtraction {\n  mentions: Map<string, MentionInfo>; // by placeholder key\n  mentionsByOpenId: Map<string, MentionInfo>; // by open_id\n  mentionList: MentionInfo[]; // non-@all, non-bot list for public export\n  mentionAll: boolean;\n  mentionedBot: boolean;\n}\n\nexport function extractMentions(\n  raw: RawMention[] | undefined,\n  botOpenId: string | undefined,\n): MentionExtraction {\n  const mentions = new Map<string, MentionInfo>();\n  const mentionsByOpenId = new Map<string, MentionInfo>();\n  const mentionList: MentionInfo[] = [];\n  let mentionAll = false;\n  let mentionedBot = false;\n\n  for (const m of raw ?? []) {\n    if (isMentionAll(m)) {\n      mentionAll = true;\n      mentions.set(m.key, { key: m.key, name: m.name, isBot: false });\n      continue;\n    }\n    const openId = m.id?.open_id ?? '';\n    const userId = m.id?.user_id;\n    const isBot = Boolean(botOpenId && openId === botOpenId);\n    if (isBot) mentionedBot = true;\n\n    const info: MentionInfo = {\n      key: m.key,\n      openId: openId || undefined,\n      userId,\n      name: m.name,\n      isBot,\n    };\n    mentions.set(m.key, info);\n    if (openId) mentionsByOpenId.set(openId, info);\n    mentionList.push(info);\n  }\n\n  return { mentions, mentionsByOpenId, mentionList, mentionAll, mentionedBot };\n}\n\n/**\n * Second-pass: replace placeholder keys in `content` with human-readable names\n * (or strip bot mentions if configured).\n *\n * Must run AFTER all converters have done their work, because converters use\n * placeholder keys to defer resolution to this single point.\n */\nexport function resolveMentions(\n  content: string,\n  ctx: Pick<ConvertContext, 'mentions' | 'stripBotMentions'>,\n): string {\n  if (!content || ctx.mentions.size === 0) return content;\n\n  let out = content;\n  for (const [key, info] of ctx.mentions) {\n    if (info.isBot && ctx.stripBotMentions) {\n      // Remove key plus one surrounding whitespace on either side.\n      const re = new RegExp(`\\\\s?${escapeRegex(key)}\\\\s?`, 'g');\n      out = out.replace(re, ' ');\n      continue;\n    }\n    const replacement = info.name ? `@${info.name}` : key;\n    out = out.split(key).join(replacement);\n  }\n  // Collapse any double-spaces introduced by bot-mention stripping.\n  return out.replace(/[ \\t]{2,}/g, ' ').trim();\n}\n\nfunction escapeRegex(s: string): string {\n  return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","export function safeParse(raw: string): unknown | undefined {\n  if (!raw) return undefined;\n  try {\n    return JSON.parse(raw);\n  } catch {\n    return undefined;\n  }\n}\n\nexport function applyStyle(text: string, style?: string[]): string {\n  if (!style || style.length === 0) return text;\n  let out = text;\n  if (style.includes('bold')) out = `**${out}**`;\n  if (style.includes('italic')) out = `*${out}*`;\n  if (style.includes('underline')) out = `<u>${out}</u>`;\n  if (style.includes('lineThrough') || style.includes('strikethrough')) out = `~~${out}~~`;\n  if (style.includes('codeInline') || style.includes('code')) out = `\\`${out}\\``;\n  return out;\n}\n\nconst LOCALE_PRIORITY = ['zh_cn', 'en_us', 'ja_jp'] as const;\n\nexport function unwrapLocale<T = Record<string, unknown>>(\n  parsed: Record<string, unknown>,\n): T | undefined {\n  if ('title' in parsed || 'content' in parsed) {\n    return parsed as unknown as T;\n  }\n  for (const loc of LOCALE_PRIORITY) {\n    const hit = parsed[loc];\n    if (hit != null && typeof hit === 'object') return hit as T;\n  }\n  const firstKey = Object.keys(parsed)[0];\n  if (firstKey) {\n    const first = parsed[firstKey];\n    if (first != null && typeof first === 'object') return first as T;\n  }\n  return undefined;\n}\n\nexport function formatDuration(ms: number | undefined): string | undefined {\n  if (ms == null || !Number.isFinite(ms) || ms < 0) return undefined;\n  if (ms < 1000) return `${Math.round(ms)}ms`;\n  if (ms % 1000 === 0) return `${ms / 1000}s`;\n  return `${(ms / 1000).toFixed(1)}s`;\n}\n\nexport function millisToDatetime(ms: string | number | undefined): string | undefined {\n  if (ms == null) return undefined;\n  const n = typeof ms === 'string' ? parseInt(ms, 10) : ms;\n  if (!Number.isFinite(n) || n <= 0) return undefined;\n  const d = new Date(n + 8 * 3600_000);\n  const y = d.getUTCFullYear();\n  const mo = String(d.getUTCMonth() + 1).padStart(2, '0');\n  const day = String(d.getUTCDate()).padStart(2, '0');\n  const h = String(d.getUTCHours()).padStart(2, '0');\n  const mi = String(d.getUTCMinutes()).padStart(2, '0');\n  return `${y}-${mo}-${day} ${h}:${mi}`;\n}\n\nexport function formatRFC3339Beijing(ms: number): string {\n  const d = new Date(ms + 8 * 3600_000);\n  const y = d.getUTCFullYear();\n  const mo = String(d.getUTCMonth() + 1).padStart(2, '0');\n  const day = String(d.getUTCDate()).padStart(2, '0');\n  const h = String(d.getUTCHours()).padStart(2, '0');\n  const mi = String(d.getUTCMinutes()).padStart(2, '0');\n  const s = String(d.getUTCSeconds()).padStart(2, '0');\n  return `${y}-${mo}-${day}T${h}:${mi}:${s}+08:00`;\n}\n\nexport function indentLines(text: string, indent: string): string {\n  return text\n    .split('\\n')\n    .map((line) => `${indent}${line}`)\n    .join('\\n');\n}\n\nexport function escapeAttr(s: string): string {\n  return s.replace(/\"/g, '&quot;');\n}\n","import type { ResourceDescriptor } from '../../types';\nimport type { ContentConverterFn } from '../context';\nimport { formatDuration, safeParse } from '../utils';\n\nexport const convertAudio: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { file_key?: string; duration?: number } | undefined;\n  const fileKey = parsed?.file_key;\n  if (!fileKey) return { content: '[audio]', resources: [] };\n\n  const duration = parsed?.duration;\n  const durAttr = formatDuration(duration);\n  const attr = durAttr ? ` duration=\"${durAttr}\"` : '';\n  const content = `<audio key=\"${fileKey}\"${attr}/>`;\n  const resources: ResourceDescriptor[] = [{ type: 'audio', fileKey, durationMs: duration }];\n  return { content, resources };\n};\n","import type { ContentConverterFn } from '../context';\nimport { millisToDatetime, safeParse } from '../utils';\n\ninterface CalendarContent {\n  summary?: string;\n  start_time?: string | number;\n  end_time?: string | number;\n}\n\nfunction formatCalendarInner(raw: string): string {\n  const parsed = safeParse(raw) as CalendarContent | undefined;\n  if (!parsed) return '[calendar event]';\n\n  const lines: string[] = [];\n  if (parsed.summary) lines.push(`📅 ${parsed.summary}`);\n\n  const start = millisToDatetime(parsed.start_time);\n  const end = millisToDatetime(parsed.end_time);\n  if (start && end) lines.push(`🕙 ${start} ~ ${end}`);\n  else if (start) lines.push(`🕙 ${start}`);\n\n  return lines.length > 0 ? lines.join('\\n') : '[calendar event]';\n}\n\nexport const convertCalendar: ContentConverterFn = async (raw, _ctx) => ({\n  content: `<calendar_invite>\\n${formatCalendarInner(raw)}\\n</calendar_invite>`,\n  resources: [],\n});\n\nexport const convertGeneralCalendar: ContentConverterFn = async (raw, _ctx) => ({\n  content: `<calendar>\\n${formatCalendarInner(raw)}\\n</calendar>`,\n  resources: [],\n});\n\nexport const convertShareCalendarEvent: ContentConverterFn = async (raw, _ctx) => ({\n  content: `<calendar_share>\\n${formatCalendarInner(raw)}\\n</calendar_share>`,\n  resources: [],\n});\n","import type { ContentConverterFn } from '../context';\nimport { safeParse } from '../utils';\n\nexport const convertUnknown: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { text?: string } | undefined;\n  if (parsed && typeof parsed.text === 'string') {\n    return { content: parsed.text, resources: [] };\n  }\n  return { content: '[unsupported message]', resources: [] };\n};\n","import type { ResourceDescriptor } from '../../types';\nimport type { ContentConverterFn } from '../context';\nimport { escapeAttr, safeParse } from '../utils';\n\nexport const convertFile: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { file_key?: string; file_name?: string } | undefined;\n  const fileKey = parsed?.file_key;\n  if (!fileKey) return { content: '[file]', resources: [] };\n\n  const fileName = parsed?.file_name;\n  const nameAttr = fileName ? ` name=\"${escapeAttr(fileName)}\"` : '';\n  const content = `<file key=\"${fileKey}\"${nameAttr}/>`;\n  const resources: ResourceDescriptor[] = [{ type: 'file', fileKey, fileName }];\n  return { content, resources };\n};\n","import type { ContentConverterFn } from '../context';\nimport { escapeAttr, safeParse } from '../utils';\n\nexport const convertFolder: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { file_key?: string; file_name?: string } | undefined;\n  const fileKey = parsed?.file_key;\n  if (!fileKey) return { content: '[folder]', resources: [] };\n\n  const nameAttr = parsed?.file_name ? ` name=\"${escapeAttr(parsed.file_name)}\"` : '';\n  return { content: `<folder key=\"${fileKey}\"${nameAttr}/>`, resources: [] };\n};\n","import type { ContentConverterFn } from '../context';\nimport { escapeAttr, safeParse } from '../utils';\n\nexport const convertHongbao: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { text?: string } | undefined;\n  const textAttr = parsed?.text ? ` text=\"${escapeAttr(parsed.text)}\"` : '';\n  return { content: `<hongbao${textAttr}/>`, resources: [] };\n};\n","import type { ResourceDescriptor } from '../../types';\nimport type { ContentConverterFn } from '../context';\nimport { safeParse } from '../utils';\n\nexport const convertImage: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { image_key?: string } | undefined;\n  const imageKey = parsed?.image_key;\n  if (!imageKey) return { content: '[image]', resources: [] };\n  const resources: ResourceDescriptor[] = [{ type: 'image', fileKey: imageKey }];\n  return { content: `![image](${imageKey})`, resources };\n};\n","/**\n * Recursive walker that extracts human-readable text from a Feishu\n * interactive card JSON tree. Covers header titles, plain_text / lark_md\n * elements, button labels, form fields, notes, and commonly used nested\n * element types.\n */\nexport function walkCard(node: unknown): string[] {\n  const out: string[] = [];\n  visit(node, out);\n  // de-duplicate adjacent empties and collapse\n  return out.filter((s) => s && s.trim().length > 0);\n}\n\nfunction visit(node: unknown, out: string[]): void {\n  if (node == null) return;\n  if (typeof node === 'string' || typeof node === 'number' || typeof node === 'boolean') return;\n  if (Array.isArray(node)) {\n    for (const child of node) visit(child, out);\n    return;\n  }\n  if (typeof node !== 'object') return;\n\n  const obj = node as Record<string, unknown>;\n\n  // tag: plain_text / lark_md / markdown → push content\n  const tag = obj.tag;\n  if (\n    typeof tag === 'string' &&\n    (tag === 'plain_text' || tag === 'lark_md' || tag === 'markdown')\n  ) {\n    if (typeof obj.content === 'string') out.push(obj.content);\n    return;\n  }\n\n  // header.title\n  if (obj.header && typeof obj.header === 'object') {\n    const header = obj.header as Record<string, unknown>;\n    if (header.title) visit(header.title, out);\n  }\n\n  // text / content on common elements (div, button, note, etc.)\n  if (obj.text) visit(obj.text, out);\n\n  // button / select option label (typed as button)\n  if (typeof tag === 'string' && tag === 'button') {\n    const text = (obj as { text?: unknown }).text;\n    if (text) visit(text, out);\n  }\n\n  // form fields: label, placeholder, options\n  if (obj.label) visit(obj.label, out);\n  if (obj.placeholder) visit(obj.placeholder, out);\n  if (Array.isArray(obj.options)) {\n    for (const opt of obj.options) {\n      const o = opt as Record<string, unknown>;\n      if (o?.text) visit(o.text, out);\n    }\n  }\n\n  // column / row containers\n  if (Array.isArray(obj.elements)) for (const el of obj.elements) visit(el, out);\n  if (Array.isArray(obj.fields)) for (const f of obj.fields) visit(f, out);\n  if (Array.isArray(obj.actions)) for (const a of obj.actions) visit(a, out);\n  if (Array.isArray(obj.columns)) for (const c of obj.columns) visit(c, out);\n\n  // common nested shapes for v2 card body\n  if (obj.body) visit(obj.body, out);\n}\n","import type { ContentConverterFn } from '../../context';\nimport { safeParse } from '../../utils';\nimport { walkCard } from './card-walker';\n\nexport const convertInteractive: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw);\n  if (parsed == null || typeof parsed !== 'object') {\n    return { content: '[interactive card]', resources: [] };\n  }\n\n  const pieces = walkCard(parsed);\n  if (pieces.length === 0) {\n    return { content: '[interactive card]', resources: [] };\n  }\n\n  // Dedup adjacent duplicates while preserving order\n  const seen = new Set<string>();\n  const out: string[] = [];\n  for (const p of pieces) {\n    const key = p.trim();\n    if (!key || seen.has(key)) continue;\n    seen.add(key);\n    out.push(key);\n  }\n\n  return { content: out.join('\\n'), resources: [] };\n};\n","import type { ContentConverterFn } from '../context';\nimport { escapeAttr, safeParse } from '../utils';\n\nexport const convertLocation: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as\n    | { name?: string; latitude?: string; longitude?: string }\n    | undefined;\n  const name = parsed?.name;\n  const lat = parsed?.latitude;\n  const lng = parsed?.longitude;\n\n  const nameAttr = name ? ` name=\"${escapeAttr(name)}\"` : '';\n  const coordsAttr = lat && lng ? ` coords=\"lat:${lat},lng:${lng}\"` : '';\n  return { content: `<location${nameAttr}${coordsAttr}/>`, resources: [] };\n};\n","import type { ResourceDescriptor } from '../../types';\nimport type { ApiMessageItem, ContentConverterFn, ConvertContext } from '../context';\nimport { formatRFC3339Beijing, indentLines } from '../utils';\n\nconst MAX_ITEMS = 50;\n\n// Internal render result: a sub-item's rendered text plus the resources it (and\n// any descendants) carry, each already stamped with its owning message id.\ninterface RenderedItem {\n  content: string;\n  resources: ResourceDescriptor[];\n}\n\nexport const convertMergeForward: ContentConverterFn = async (_raw, ctx) => {\n  const { messageId, fetchSubMessages, dispatch } = ctx;\n\n  if (!fetchSubMessages || !dispatch) {\n    return { content: '<forwarded_messages/>', resources: [] };\n  }\n\n  let items: ApiMessageItem[];\n  try {\n    items = await fetchSubMessages(messageId);\n  } catch {\n    return { content: '<forwarded_messages/>', resources: [] };\n  }\n\n  if (!items || items.length === 0) {\n    return { content: '<forwarded_messages/>', resources: [] };\n  }\n\n  const capped = items.slice(0, MAX_ITEMS);\n  const truncated = items.length > MAX_ITEMS;\n\n  // Pre-warm sender name cache in one batch call.\n  if (ctx.batchResolveNames) {\n    const senderIds = new Set<string>();\n    for (const it of capped) {\n      const sid = it.sender?.id;\n      if (sid && it.message_id !== messageId) senderIds.add(sid);\n    }\n    if (senderIds.size > 0) {\n      try {\n        await ctx.batchResolveNames([...senderIds]);\n      } catch {\n        // best effort\n      }\n    }\n  }\n\n  const childrenMap = buildChildrenMap(capped, messageId);\n  const { content, resources } = await formatSubTree(messageId, childrenMap, ctx, truncated);\n  return { content, resources };\n};\n\nfunction buildChildrenMap(items: ApiMessageItem[], rootId: string): Map<string, ApiMessageItem[]> {\n  const map = new Map<string, ApiMessageItem[]>();\n  for (const it of items) {\n    if (it.message_id === rootId && !it.upper_message_id) continue;\n    const pid = it.upper_message_id ?? rootId;\n    let arr = map.get(pid);\n    if (!arr) {\n      arr = [];\n      map.set(pid, arr);\n    }\n    arr.push(it);\n  }\n  for (const arr of map.values()) {\n    arr.sort((a, b) => {\n      const ta = parseInt(String(a.create_time ?? '0'), 10);\n      const tb = parseInt(String(b.create_time ?? '0'), 10);\n      return ta - tb;\n    });\n  }\n  return map;\n}\n\nasync function formatSubTree(\n  parentId: string,\n  map: Map<string, ApiMessageItem[]>,\n  ctx: ConvertContext,\n  truncated = false,\n): Promise<RenderedItem> {\n  const children = map.get(parentId);\n  if (!children || children.length === 0) {\n    return { content: '<forwarded_messages/>', resources: [] };\n  }\n\n  const parts: string[] = [];\n  const resources: ResourceDescriptor[] = [];\n  for (const item of children) {\n    try {\n      const sub = await renderItem(item, map, ctx);\n      if (sub.content) parts.push(sub.content);\n      resources.push(...sub.resources);\n    } catch {\n      // skip bad item\n    }\n  }\n\n  if (parts.length === 0) return { content: '<forwarded_messages/>', resources };\n  const body = parts.join('\\n');\n  const footer = truncated ? '\\n... (truncated)' : '';\n  return { content: `<forwarded_messages>\\n${body}${footer}\\n</forwarded_messages>`, resources };\n}\n\nasync function renderItem(\n  item: ApiMessageItem,\n  map: Map<string, ApiMessageItem[]>,\n  ctx: ConvertContext,\n): Promise<RenderedItem> {\n  const msgType = item.msg_type ?? 'text';\n  const senderId = item.sender?.id ?? 'unknown';\n  const createMs = parseInt(String(item.create_time ?? '0'), 10);\n  const timestamp = createMs > 0 ? formatRFC3339Beijing(createMs) : 'unknown';\n  const displayName = ctx.resolveUserName?.(senderId) ?? senderId;\n\n  let content: string;\n  let resources: ResourceDescriptor[] = [];\n  if (msgType === 'merge_forward') {\n    // Nested forward — recurse locally without another API call. Descendant\n    // resources already carry their own (innermost) sourceMessageId.\n    const nestedId = item.message_id;\n    if (nestedId) {\n      const sub = await formatSubTree(nestedId, map, ctx);\n      content = sub.content;\n      resources = sub.resources;\n    } else {\n      content = '<forwarded_messages/>';\n    }\n  } else {\n    const rawContent = item.body?.content ?? '{}';\n    if (!ctx.dispatch) {\n      content = rawContent;\n    } else {\n      const r = await ctx.dispatch(rawContent, msgType, ctx);\n      content = r.content;\n      // Bubble the sub-message's own resources up so the caller can download\n      // them. Feishu's messageResource.get accepts the top-level merge_forward\n      // container id (= NormalizedMessage.messageId) for these — verified\n      // against a real forward — so no per-resource owning id is needed.\n      resources = r.resources;\n    }\n  }\n\n  const indented = indentLines(content, '    ');\n  return { content: `[${timestamp}] ${displayName}:\\n${indented}`, resources };\n}\n","import type { ResourceDescriptor } from '../../types';\nimport type { ContentConverterFn, ConvertContext, PostElement } from '../context';\nimport { applyStyle, safeParse, unwrapLocale } from '../utils';\n\ninterface PostBody {\n  title?: string;\n  content?: PostElement[][];\n  content_v2?: PostElement[][];\n}\n\nconst atMentionRe = /<at(\\s+)user_id(\\s*)=(\\s*)\"(.*?)\">(.*?)<\\/at>/g;\nconst imageKeyRe = /!\\[(.*?)\\]\\(([^)]+)\\)/g;\n\nexport const convertPost: ContentConverterFn = async (raw, ctx) => {\n  const rawParsed = safeParse(raw);\n  if (rawParsed == null || typeof rawParsed !== 'object') {\n    return { content: '[rich text message]', resources: [] };\n  }\n\n  const body = unwrapLocale<PostBody>(rawParsed as Record<string, unknown>);\n  if (!body) return { content: '[rich text message]', resources: [] };\n\n  // Choose source paragraphs: prefer content_v2, fallback to content.\n  const sourceParagraphs =\n    body.content_v2 && body.content_v2.length > 0 ? body.content_v2 : (body.content ?? []);\n\n  const resources: ResourceDescriptor[] = [];\n  const lines: string[] = [];\n\n  if (body.title) {\n    lines.push(`**${body.title}**`);\n    lines.push('');\n  }\n\n  for (const paragraph of sourceParagraphs) {\n    if (!Array.isArray(paragraph)) continue;\n    let line = '';\n    for (const el of paragraph) {\n      line += renderElement(el, ctx, resources);\n    }\n    lines.push(line);\n  }\n\n  const content = lines.join('\\n').trim() || '[rich text message]';\n  return { content, resources };\n};\n\n/**\n * Post-process raw markdown text from an \"md\" element.\n * Splits by fenced code block delimiters (```) and only applies\n * transformations to text outside of properly paired code blocks.\n * Unclosed fences are treated as outside-code-block text.\n */\nfunction processMdText(text: string, resources: ResourceDescriptor[]): string {\n  const parts = text.split('```');\n  const total = parts.length;\n  for (let i = 0; i < parts.length; i++) {\n    // Odd-index segments are inside code blocks, UNLESS it's the last\n    // segment of an even-length split (unclosed fence).\n    let isInside = i % 2 === 1;\n    if (isInside && total % 2 === 0 && i === total - 1) {\n      isInside = false;\n    }\n    if (!isInside) {\n      // Outside code block: apply transformations.\n      parts[i] = parts[i].replace(atMentionRe, (_match, _sp1, _sp2, _sp3, userId, name) => {\n        if (userId === 'all' || userId === 'all_members') return '@all';\n        return name ? `@${name}` : `@${userId}`;\n      });\n      // Extract image keys from ![...](key) patterns.\n      let imgMatch: RegExpExecArray | null;\n      imageKeyRe.lastIndex = 0;\n      while ((imgMatch = imageKeyRe.exec(parts[i])) !== null) {\n        if (imgMatch[2]) {\n          resources.push({ type: 'image', fileKey: imgMatch[2] });\n        }\n      }\n    }\n    // Inside code block: preserve as-is.\n  }\n  return parts.join('```');\n}\n\nfunction renderElement(\n  el: PostElement,\n  ctx: ConvertContext,\n  resources: ResourceDescriptor[],\n): string {\n  switch (el.tag) {\n    case 'text':\n      return applyStyle(el.text ?? '', el.style);\n    case 'a': {\n      const label = el.text ?? el.href ?? '';\n      return el.href ? `[${label}](${el.href})` : label;\n    }\n    case 'at': {\n      const userId = el.user_id ?? '';\n      if (userId === 'all' || userId === 'all_members') return '@all';\n      // Prefer placeholder key so resolveMentions handles it uniformly\n      const info = ctx.mentionsByOpenId.get(userId);\n      if (info) return info.key;\n      return el.user_name ? `@${el.user_name}` : `@${userId}`;\n    }\n    case 'img': {\n      if (el.image_key) {\n        resources.push({ type: 'image', fileKey: el.image_key });\n        return `![image](${el.image_key})`;\n      }\n      return '';\n    }\n    case 'media': {\n      if (el.file_key) {\n        resources.push({ type: 'file', fileKey: el.file_key });\n        return `<file key=\"${el.file_key}\"/>`;\n      }\n      return '';\n    }\n    case 'code_block': {\n      const lang = el.language ?? '';\n      const code = el.text ?? '';\n      return `\\n\\`\\`\\`${lang}\\n${code}\\n\\`\\`\\`\\n`;\n    }\n    case 'hr':\n      return '\\n---\\n';\n    case 'md':\n      return processMdText(el.text ?? '', resources);\n    default:\n      return el.text ?? '';\n  }\n}\n","import type { ContentConverterFn } from '../context';\nimport { safeParse } from '../utils';\n\nexport const convertShareChat: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { chat_id?: string } | undefined;\n  return {\n    content: `<group_card id=\"${parsed?.chat_id ?? ''}\"/>`,\n    resources: [],\n  };\n};\n\nexport const convertShareUser: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { user_id?: string } | undefined;\n  return {\n    content: `<contact_card id=\"${parsed?.user_id ?? ''}\"/>`,\n    resources: [],\n  };\n};\n","import type { ResourceDescriptor } from '../../types';\nimport type { ContentConverterFn } from '../context';\nimport { safeParse } from '../utils';\n\nexport const convertSticker: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { file_key?: string } | undefined;\n  const fileKey = parsed?.file_key;\n  if (!fileKey) return { content: '[sticker]', resources: [] };\n  const resources: ResourceDescriptor[] = [{ type: 'sticker', fileKey }];\n  return { content: `<sticker key=\"${fileKey}\"/>`, resources };\n};\n","import type { ContentConverterFn } from '../context';\nimport { safeParse } from '../utils';\n\ninterface SystemContent {\n  template?: string;\n  from_user?: string[];\n  to_chatters?: string[];\n  divider_text?: string;\n  [key: string]: unknown;\n}\n\nexport const convertSystem: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as SystemContent | undefined;\n  if (!parsed || !parsed.template) {\n    return { content: '[system message]', resources: [] };\n  }\n\n  const out = parsed.template.replace(/\\{([a-z_]+)\\}/g, (match, name) => {\n    const val = (parsed as Record<string, unknown>)[name];\n    if (Array.isArray(val)) return val.join(', ');\n    if (typeof val === 'string') return val;\n    if (val == null) return '';\n    return match;\n  });\n\n  return { content: out.trim() || '[system message]', resources: [] };\n};\n","import type { ContentConverterFn } from '../context';\nimport { safeParse } from '../utils';\n\nexport const convertText: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { text?: string } | undefined;\n  return { content: parsed?.text ?? '', resources: [] };\n};\n","import type { ContentConverterFn, PostElement } from '../context';\nimport { millisToDatetime, safeParse } from '../utils';\n\ninterface TodoContent {\n  summary?: {\n    title?: string;\n    content?: PostElement[][];\n  };\n  due_time?: string | number;\n}\n\nexport const convertTodo: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as TodoContent | undefined;\n  if (!parsed?.summary) return { content: '<todo>\\n[todo]\\n</todo>', resources: [] };\n\n  const lines: string[] = [];\n  if (parsed.summary.title) lines.push(parsed.summary.title);\n\n  const bodyText = extractPostPlainText(parsed.summary.content);\n  if (bodyText) lines.push(bodyText);\n\n  const due = millisToDatetime(parsed.due_time);\n  if (due) lines.push(`Due: ${due}`);\n\n  if (lines.length === 0) return { content: '<todo>\\n[todo]\\n</todo>', resources: [] };\n  return { content: `<todo>\\n${lines.join('\\n')}\\n</todo>`, resources: [] };\n};\n\nfunction extractPostPlainText(blocks: PostElement[][] | undefined): string {\n  if (!blocks) return '';\n  const lines: string[] = [];\n  for (const paragraph of blocks) {\n    if (!Array.isArray(paragraph)) continue;\n    const parts: string[] = [];\n    for (const el of paragraph) {\n      if (el.tag === 'text' && el.text) parts.push(el.text);\n      else if (el.tag === 'a' && el.text) parts.push(el.text);\n    }\n    if (parts.length > 0) lines.push(parts.join(''));\n  }\n  return lines.join('\\n');\n}\n","import type { ResourceDescriptor } from '../../types';\nimport type { ContentConverterFn } from '../context';\nimport { escapeAttr, formatDuration, safeParse } from '../utils';\n\nexport const convertVideo: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as\n    | {\n        file_key?: string;\n        file_name?: string;\n        duration?: number;\n        image_key?: string; // cover\n      }\n    | undefined;\n  const fileKey = parsed?.file_key;\n  if (!fileKey) return { content: '[video]', resources: [] };\n\n  const nameAttr = parsed?.file_name ? ` name=\"${escapeAttr(parsed.file_name)}\"` : '';\n  const durStr = formatDuration(parsed?.duration);\n  const durAttr = durStr ? ` duration=\"${durStr}\"` : '';\n  const content = `<video key=\"${fileKey}\"${nameAttr}${durAttr}/>`;\n  const resources: ResourceDescriptor[] = [\n    {\n      type: 'video',\n      fileKey,\n      fileName: parsed?.file_name,\n      durationMs: parsed?.duration,\n      coverImageKey: parsed?.image_key,\n    },\n  ];\n  return { content, resources };\n};\n","import type { ContentConverterFn } from '../context';\nimport { millisToDatetime, safeParse } from '../utils';\n\ninterface VideoChatContent {\n  topic?: string;\n  meet_number?: string;\n  start_time?: string | number;\n}\n\nexport const convertVideoChat: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as VideoChatContent | undefined;\n  if (!parsed) {\n    return { content: '<meeting>\\n[video chat]\\n</meeting>', resources: [] };\n  }\n\n  const lines: string[] = [];\n  if (parsed.topic) lines.push(`📹 ${parsed.topic}`);\n  if (parsed.meet_number) lines.push(`🔢 ${parsed.meet_number}`);\n  const start = millisToDatetime(parsed.start_time);\n  if (start) lines.push(`🕙 ${start}`);\n\n  const inner = lines.length > 0 ? lines.join('\\n') : '[video chat]';\n  return { content: `<meeting>\\n${inner}\\n</meeting>`, resources: [] };\n};\n","import type { ContentConverterFn } from '../context';\nimport { safeParse } from '../utils';\n\nexport const convertVote: ContentConverterFn = async (raw, _ctx) => {\n  const parsed = safeParse(raw) as { topic?: string; options?: string[] } | undefined;\n\n  if (!parsed || (!parsed.topic && !parsed.options?.length)) {\n    return { content: '<vote>\\n[vote]\\n</vote>', resources: [] };\n  }\n\n  const lines: string[] = [];\n  if (parsed.topic) lines.push(parsed.topic);\n  for (const opt of parsed.options ?? []) lines.push(`• ${opt}`);\n\n  return { content: `<vote>\\n${lines.join('\\n')}\\n</vote>`, resources: [] };\n};\n","import type { ContentConverterFn, ConvertContext, ConvertResult } from './context';\nimport { convertAudio } from './converters/audio';\nimport {\n  convertCalendar,\n  convertGeneralCalendar,\n  convertShareCalendarEvent,\n} from './converters/calendar';\nimport { convertUnknown } from './converters/fallback';\nimport { convertFile } from './converters/file';\nimport { convertFolder } from './converters/folder';\nimport { convertHongbao } from './converters/hongbao';\nimport { convertImage } from './converters/image';\nimport { convertInteractive } from './converters/interactive';\nimport { convertLocation } from './converters/location';\nimport { convertMergeForward } from './converters/merge-forward';\nimport { convertPost } from './converters/post';\nimport { convertShareChat, convertShareUser } from './converters/share';\nimport { convertSticker } from './converters/sticker';\nimport { convertSystem } from './converters/system';\nimport { convertText } from './converters/text';\nimport { convertTodo } from './converters/todo';\nimport { convertVideo } from './converters/video';\nimport { convertVideoChat } from './converters/video-chat';\nimport { convertVote } from './converters/vote';\n\nexport const converters: ReadonlyMap<string, ContentConverterFn> = new Map<\n  string,\n  ContentConverterFn\n>([\n  ['text', convertText],\n  ['post', convertPost],\n  ['image', convertImage],\n  ['file', convertFile],\n  ['audio', convertAudio],\n  ['video', convertVideo],\n  ['media', convertVideo],\n  ['sticker', convertSticker],\n  ['interactive', convertInteractive],\n  ['merge_forward', convertMergeForward],\n  ['share_chat', convertShareChat],\n  ['share_user', convertShareUser],\n  ['location', convertLocation],\n  ['system', convertSystem],\n  ['vote', convertVote],\n  ['todo', convertTodo],\n  ['calendar', convertCalendar],\n  ['general_calendar', convertGeneralCalendar],\n  ['share_calendar_event', convertShareCalendarEvent],\n  ['folder', convertFolder],\n  ['hongbao', convertHongbao],\n  ['video_chat', convertVideoChat],\n]);\n\n/**\n * Dispatch a message content to the matching converter, with uniform error\n * containment — any thrown error is trapped and the fallback converter is\n * invoked instead so that normalization never fails catastrophically.\n */\nexport async function dispatchConvert(\n  raw: string,\n  msgType: string,\n  ctx: ConvertContext,\n): Promise<ConvertResult> {\n  const fn = converters.get(msgType) ?? convertUnknown;\n  try {\n    return await fn(raw, ctx);\n  } catch {\n    return convertUnknown(raw, ctx);\n  }\n}\n","import type { BotAddedEvent } from '../types';\n\nexport interface RawBotAddedEvent {\n  chat_id?: string;\n  operator_id?: {\n    open_id?: string;\n    user_id?: string | null;\n    union_id?: string;\n  };\n  external?: boolean;\n  /** The bot's name (NOT the chat's name). */\n  name?: string;\n  /** The bot's localized names. */\n  i18n_names?: {\n    zh_cn?: string;\n    en_us?: string;\n    ja_jp?: string;\n  };\n}\n\nexport function normalizeBotAdded(\n  event: RawBotAddedEvent,\n  opts?: { includeRaw?: boolean },\n): BotAddedEvent | null {\n  const chatId = event.chat_id;\n  const operatorOpenId = event.operator_id?.open_id;\n\n  if (!chatId || !operatorOpenId) return null;\n\n  const botName =\n    event.name ?? event.i18n_names?.zh_cn ?? event.i18n_names?.en_us ?? event.i18n_names?.ja_jp;\n\n  return {\n    chatId,\n    operator: {\n      openId: operatorOpenId,\n      userId: event.operator_id?.user_id ?? undefined,\n    },\n    botName,\n    external: event.external,\n    raw: opts?.includeRaw ? event : undefined,\n  };\n}\n","import type { CardActionEvent } from '../types';\n\nexport interface RawCardActionEvent {\n  /**\n   * Current shape (observed from `card.action.trigger` v2): message/chat ids\n   * are nested under `context`. Top-level variants kept as fallback in case\n   * older or alternate surfaces (webhook vs WS, older schema) still deliver\n   * them at the root.\n   */\n  context?: {\n    open_message_id?: string;\n    open_chat_id?: string;\n  };\n  open_message_id?: string;\n  open_chat_id?: string;\n  token?: string;\n  operator?: {\n    open_id?: string;\n    user_id?: string;\n    union_id?: string;\n    name?: string;\n  };\n  action?: {\n    value?: unknown;\n    tag?: string;\n    name?: string;\n    option?: string;\n    timezone?: string;\n    /** CardKit 2.0 form submission values, keyed by element name. */\n    form_value?: Record<string, unknown>;\n  };\n}\n\nexport function normalizeCardAction(\n  event: RawCardActionEvent,\n  opts?: { includeRaw?: boolean },\n): CardActionEvent | null {\n  const messageId = event.context?.open_message_id ?? event.open_message_id;\n  const chatId = event.context?.open_chat_id ?? event.open_chat_id;\n  const operatorOpenId = event.operator?.open_id;\n\n  if (!messageId || !chatId || !operatorOpenId) return null;\n\n  return {\n    messageId,\n    chatId,\n    operator: {\n      openId: operatorOpenId,\n      userId: event.operator?.user_id,\n      name: event.operator?.name,\n    },\n    action: {\n      value: event.action?.value,\n      tag: event.action?.tag ?? 'unknown',\n      name: event.action?.name,\n      option: event.action?.option,\n      formValue: event.action?.form_value,\n    },\n    raw: opts?.includeRaw ? event : undefined,\n  };\n}\n","import type { CommentEvent } from '../types';\n\nexport interface RawCommentEvent {\n  app_id?: string;\n  file_token?: string;\n  file_type?: string;\n  comment_id?: string;\n  reply_id?: string;\n  /** Whether the bot was mentioned. Top-level in current payload. */\n  is_mentioned?: boolean;\n  /** Millisecond timestamp of the event. */\n  create_time?: string;\n  notice_meta?: {\n    from_user_id?: {\n      open_id?: string;\n      user_id?: string | null;\n      union_id?: string;\n    };\n    to_user_id?: {\n      open_id?: string;\n      user_id?: string | null;\n      union_id?: string;\n    };\n    file_token?: string;\n    file_type?: string;\n    timestamp?: string;\n    is_mentioned?: boolean;\n    notice_type?: string;\n  };\n  // Fallback top-level fields (legacy SDK flattening)\n  is_mention?: boolean;\n  user_id?: {\n    open_id?: string;\n    user_id?: string | null;\n    union_id?: string;\n  };\n  action_time?: string;\n}\n\nexport function normalizeComment(\n  event: RawCommentEvent,\n  opts?: { includeRaw?: boolean },\n): CommentEvent | null {\n  const fileToken = event.file_token ?? event.notice_meta?.file_token;\n  const fileType = event.file_type ?? event.notice_meta?.file_type;\n  const commentId = event.comment_id;\n\n  const userId = event.notice_meta?.from_user_id ?? event.user_id;\n  const operatorOpenId = userId?.open_id;\n\n  if (!fileToken || !fileType || !commentId || !operatorOpenId) return null;\n\n  const tsStr = event.create_time ?? event.notice_meta?.timestamp ?? event.action_time;\n  const timestamp = tsStr ? parseInt(tsStr, 10) : Date.now();\n\n  return {\n    fileToken,\n    fileType,\n    commentId,\n    replyId: event.reply_id,\n    operator: {\n      openId: operatorOpenId,\n      userId: userId?.user_id ?? undefined,\n      unionId: userId?.union_id,\n    },\n    mentionedBot: Boolean(\n      event.is_mentioned ?? event.notice_meta?.is_mentioned ?? event.is_mention,\n    ),\n    timestamp: Number.isFinite(timestamp) ? timestamp : Date.now(),\n    raw: opts?.includeRaw ? event : undefined,\n  };\n}\n","import type { ReactionEvent } from '../types';\n\nexport interface RawReactionEvent {\n  message_id?: string;\n  reaction_type?: { emoji_type?: string };\n  operator_type?: string;\n  user_id?: {\n    open_id?: string;\n    user_id?: string | null;\n    union_id?: string;\n  };\n  action_time?: string;\n}\n\nexport function normalizeReaction(\n  event: RawReactionEvent,\n  action: 'added' | 'removed',\n  opts?: { includeRaw?: boolean },\n): ReactionEvent | null {\n  const messageId = event.message_id;\n  const emojiType = event.reaction_type?.emoji_type;\n  const operatorOpenId = event.user_id?.open_id;\n\n  if (!messageId || !emojiType || !operatorOpenId) return null;\n\n  const actionTimeStr = event.action_time;\n  const actionTime = actionTimeStr ? parseInt(actionTimeStr, 10) : undefined;\n\n  return {\n    messageId,\n    operator: {\n      openId: operatorOpenId,\n      userId: event.user_id?.user_id ?? undefined,\n    },\n    emojiType,\n    action,\n    actionTime: actionTime != null && Number.isFinite(actionTime) ? actionTime : undefined,\n    raw: opts?.includeRaw ? event : undefined,\n  };\n}\n","import type { BotIdentity, NormalizedMessage } from '../types';\nimport type { ApiMessageItem, ConvertContext, RawMessageEvent } from './context';\nimport { extractMentions, resolveMentions } from './mentions';\nimport { dispatchConvert } from './registry';\n\nexport type { RawBotAddedEvent } from './bot-added';\nexport { normalizeBotAdded } from './bot-added';\nexport type { RawCardActionEvent } from './card-action';\nexport { normalizeCardAction } from './card-action';\nexport type { RawCommentEvent } from './comment';\nexport { normalizeComment } from './comment';\nexport type {\n  ApiMessageItem,\n  ContentConverterFn,\n  ConvertContext,\n  ConvertResult,\n  RawMessageEvent,\n} from './context';\nexport { extractMentions, resolveMentions } from './mentions';\nexport type { RawReactionEvent } from './reaction';\nexport { normalizeReaction } from './reaction';\n\nexport interface NormalizeOptions {\n  botIdentity: BotIdentity;\n  stripBotMentions?: boolean;\n  includeRaw?: boolean;\n  fetchSubMessages?: (messageId: string) => Promise<ApiMessageItem[]>;\n  resolveUserName?: (openId: string) => string | undefined;\n  resolveSenderName?: (openId: string) => string | undefined;\n  batchResolveNames?: (openIds: string[]) => Promise<void>;\n}\n\n/**\n * Normalize a raw Feishu message event into a NormalizedMessage.\n *\n * Pipeline:\n *   1. Extract mentions → build key/openId maps + bot detection\n *   2. For `interactive` type, fetch full v2 card content if capability available\n *   3. Build ConvertContext with injected capabilities\n *   4. Dispatch to the matching converter (uniform error containment inside)\n *   5. Run resolveMentions second pass — replace placeholders with @name\n *   6. Assemble and return NormalizedMessage\n */\nexport async function normalize(\n  event: RawMessageEvent,\n  opts: NormalizeOptions,\n): Promise<NormalizedMessage> {\n  const msg = event.message;\n  const botOpenId = opts.botIdentity?.openId;\n\n  const {\n    mentions,\n    mentionsByOpenId,\n    mentionList,\n    mentionAll: mentionAllFromRaw,\n    mentionedBot,\n  } = extractMentions(msg.mentions, botOpenId);\n\n  // Feishu frequently omits the `mentions` array even for an @-all mention —\n  // the placeholder `@_all` stays inline in content.\n  // Fall back to a content-level scan so policy gating (respondToMentionAll)\n  // and downstream consumers see a truthy mentionAll in that case.\n  const mentionAll = mentionAllFromRaw || detectMentionAllInContent(msg.content);\n\n  const ctx: ConvertContext = {\n    messageId: msg.message_id,\n    botOpenId,\n    mentions,\n    mentionsByOpenId,\n    stripBotMentions: opts.stripBotMentions ?? true,\n    fetchSubMessages: opts.fetchSubMessages,\n    resolveUserName: opts.resolveUserName,\n    batchResolveNames: opts.batchResolveNames,\n    dispatch: dispatchConvert,\n  };\n\n  const { content: rawContent, resources } = await dispatchConvert(\n    msg.content,\n    msg.message_type,\n    ctx,\n  );\n\n  const content = resolveMentions(rawContent, ctx);\n\n  const senderOpenId = event.sender.sender_id.open_id;\n  const senderFallbackId = event.sender.sender_id.user_id ?? event.sender.sender_id.union_id ?? '';\n  const senderId = senderOpenId ?? senderFallbackId;\n  const senderName = senderOpenId ? opts.resolveSenderName?.(senderOpenId) : undefined;\n\n  // Pass through the raw sender kind (dropped until now). `senderIsBot` stays\n  // undefined when the kind is absent, so a missing signal is never read as\n  // \"not a bot\".\n  const senderType = event.sender.sender_type;\n  const senderIsBot = senderType === undefined ? undefined : senderType === 'bot';\n\n  const createMs = msg.create_time ? parseInt(msg.create_time, 10) : 0;\n\n  return {\n    messageId: msg.message_id,\n    chatId: msg.chat_id,\n    chatType: msg.chat_type as NormalizedMessage['chatType'],\n    senderId,\n    senderName,\n    senderType,\n    senderIsBot,\n    content,\n    rawContentType: msg.message_type,\n    resources,\n    mentions: mentionList,\n    mentionAll,\n    mentionedBot,\n    rootId: msg.root_id,\n    threadId: msg.thread_id,\n    replyToMessageId: msg.parent_id,\n    createTime: Number.isFinite(createMs) ? createMs : 0,\n    raw: opts.includeRaw ? event : undefined,\n  };\n}\n\n/**\n * Detect `@_all` placeholder inside a raw Feishu content JSON string without\n * parsing. We deliberately search the serialized form (not the parsed text),\n * because the placeholder can appear in a `text` field (text/post) or inside\n * nested content arrays (post). The placeholder is bounded by non-word chars\n * on the right (whitespace, quote, punctuation) — on the left `@` is already\n * a non-word char so no explicit boundary is needed.\n */\nfunction detectMentionAllInContent(content: string | undefined): boolean {\n  if (!content) return false;\n  return /@_all\\b/.test(content);\n}\n","import type { Cache, Domain, HttpInstance, LoggerLevel } from '@larksuiteoapi/node-sdk';\nimport type { Logger, WSConfigOverrides } from './internal';\n\n// ─────────────────────────────────────────────────────────────\n// Normalized inbound message — the core output of the channel\n// ─────────────────────────────────────────────────────────────\n\nexport type ChatType = 'p2p' | 'group';\n\nexport interface NormalizedMessage {\n  messageId: string;\n  chatId: string;\n  chatType: ChatType;\n  /**\n   * Finer-grained chat mode than {@link chatType}: distinguishes a topic\n   * group ('topic') from an ordinary group ('group'). Only populated when\n   * the channel is created with `resolveChatMode: true` — Feishu omits chat\n   * mode from message events, so resolving it costs one cached `chat.get`\n   * per chat. `undefined` when resolution is disabled or failed.\n   */\n  chatMode?: 'p2p' | 'group' | 'topic';\n  senderId: string;\n  senderName?: string;\n  /**\n   * Sender kind, passed through from the raw event's `sender.sender_type`\n   * (`'user' | 'bot' | 'system' | 'anonymous'`; other values are possible, so\n   * the type is a plain string). `undefined` when the event omits it — the kind\n   * cannot be inferred, so treat `undefined` as \"unknown\", not \"not a bot\".\n   */\n  senderType?: string;\n  /**\n   * Convenience derived from {@link senderType}: `true` when it is `'bot'`,\n   * `false` for any other present kind, `undefined` when `senderType` is absent\n   * (so a missing signal is never mistaken for \"not a bot\").\n   */\n  senderIsBot?: boolean;\n  content: string;\n  rawContentType: string;\n  resources: ResourceDescriptor[];\n  mentions: MentionInfo[];\n  mentionAll: boolean;\n  mentionedBot: boolean;\n  rootId?: string;\n  threadId?: string;\n  replyToMessageId?: string;\n  createTime: number;\n  raw?: unknown;\n}\n\nexport interface ResourceDescriptor {\n  type: 'image' | 'file' | 'audio' | 'video' | 'sticker';\n  fileKey: string;\n  fileName?: string;\n  durationMs?: number;\n  coverImageKey?: string;\n}\n\nexport interface MentionInfo {\n  key: string;\n  openId?: string;\n  userId?: string;\n  name?: string;\n  isBot?: boolean;\n}\n\nexport interface BotIdentity {\n  openId: string;\n  userId?: string;\n  name: string;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Outbound send / stream\n// ─────────────────────────────────────────────────────────────\n\nexport type SendInput =\n  | { markdown: string }\n  | { text: string }\n  | { post: object }\n  | { image: { source: string | Buffer } }\n  | { file: { source: string | Buffer; fileName: string } }\n  | { audio: { source: string | Buffer; duration?: number } }\n  | { video: { source: string | Buffer; duration?: number; coverImageKey?: string } }\n  | { card: object }\n  | { cardId: string }\n  | { shareChat: { chatId: string } }\n  | { shareUser: { userId: string } }\n  | { sticker: { fileKey: string } };\n\nexport interface MediaSource {\n  source: string | Buffer;\n}\n\nexport interface SendOptions {\n  replyTo?: string;\n  replyInThread?: boolean;\n  mentions?: MentionInfo[];\n  /**\n   * Rewrite plaintext `@<name>` tokens in a `{ text }` / `{ markdown }` body\n   * into real `<at>` mentions, resolving each name against the target chat's\n   * member roster. Unknown or ambiguous names are left as plaintext — an\n   * unresolved `@xxx` is never turned into a mention. Off by default.\n   */\n  resolveMentionsInText?: boolean;\n}\n\nexport interface SendResult {\n  messageId: string;\n  chunkIds?: string[];\n}\n\nexport type StreamInput =\n  | { markdown: MarkdownStreamProducer }\n  | { card: { initial: object; producer: CardStreamProducer } };\n\nexport type MarkdownStreamProducer = (controller: MarkdownStreamController) => Promise<void>;\nexport type CardStreamProducer = (controller: CardStreamController) => Promise<void>;\n\nexport interface MarkdownStreamController {\n  append(chunk: string): Promise<void>;\n  setContent(full: string): Promise<void>;\n  readonly messageId: string;\n}\n\nexport interface CardStreamController {\n  update(next: object | ((current: object) => object)): Promise<void>;\n  readonly messageId: string;\n  readonly current: object;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Events\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Response a `cardAction` handler may return to give the clicking user\n * native, immediate feedback (a toast, or an in-place card update). When a\n * handler returns one, the SDK passes it back to Feishu/Lark as the callback\n * response for that button click / form submit. Returning `undefined` (or\n * nothing) means \"no immediate response\" — the original, pre-existing\n * behavior.\n *\n * The shape is deliberately loose (passed through verbatim, not validated)\n * because Feishu's card-callback schema is broad and evolves. Common shapes:\n *\n *   // toast\n *   { toast: { type: 'success' | 'info' | 'error' | 'warning' | 'loading',\n *              content: string, i18n?: Record<string, string> } }\n *\n *   // update the card in place\n *   { card: { type: 'raw', data: { ... } } }\n *\n * Two caveats: the returned object is sent to Feishu as-is, so do **not** put\n * internal secrets / PII in it; and it must be JSON-serializable (cyclic\n * references / BigInt will throw when the response is encoded).\n */\nexport type CardActionResponse = Record<string, unknown>;\n\nexport interface EventMap {\n  message: (msg: NormalizedMessage) => void | Promise<void>;\n  reject: (evt: RejectEvent) => void;\n  cardAction: (\n    evt: CardActionEvent,\n  ) => void | CardActionResponse | Promise<void | CardActionResponse>;\n  reaction: (evt: ReactionEvent) => void;\n  botAdded: (evt: BotAddedEvent) => void;\n  comment: (evt: CommentEvent) => void | Promise<void>;\n  error: (err: LarkChannelError) => void;\n  reconnecting: () => void;\n  reconnected: () => void;\n}\n\nexport type EventName = keyof EventMap;\n\n/**\n * Reason for a {@link RejectEvent}. These are the set of policy-level\n * decisions that deliberately reject a message and inform the caller.\n *\n * Internal defenses (duplicate dedup, stale/expired timestamps, in-flight\n * processing lock) silently drop their targets — they are not reject\n * reasons, because the caller cannot act on them meaningfully.\n *\n * `bot_loop` is emitted only by the opt-in {@link PolicyConfig.botLoopGuard}\n * when configured with `onTrip: 'reject'` (the default `'drop'` mode drops\n * silently, like the internal defenses).\n */\nexport type RejectReason =\n  | 'group_not_allowed'\n  | 'sender_not_allowed'\n  | 'no_mention'\n  | 'dm_disabled'\n  | 'mention_all_blocked'\n  | 'bot_loop';\n\nexport interface RejectEvent {\n  messageId: string;\n  chatId: string;\n  senderId: string;\n  reason: RejectReason;\n}\n\nexport interface CardActionEvent {\n  messageId: string;\n  chatId: string;\n  operator: { openId: string; userId?: string; name?: string };\n  action: {\n    value: unknown;\n    tag: string;\n    name?: string;\n    option?: string;\n    /**\n     * CardKit 2.0 form submission values, keyed by element name. Present\n     * only on form-submit actions; `undefined` for plain button clicks.\n     * Sunk from bridge, which previously had to enable `includeRawEvent`\n     * just to read `action.form_value`.\n     */\n    formValue?: Record<string, unknown>;\n  };\n  raw?: unknown;\n}\n\nexport interface ReactionEvent {\n  messageId: string;\n  operator: { openId: string; userId?: string };\n  emojiType: string;\n  action: 'added' | 'removed';\n  actionTime?: number;\n  raw?: unknown;\n}\n\nexport interface BotAddedEvent {\n  chatId: string;\n  operator: { openId: string; userId?: string };\n  /**\n   * The bot's own name as carried in the `name` field of the Feishu event.\n   * Not the chat's name — that requires a separate `getChatInfo(chatId)`\n   * call, which callers can do on demand.\n   */\n  botName?: string;\n  external?: boolean;\n  raw?: unknown;\n}\n\nexport interface CommentEvent {\n  fileToken: string;\n  fileType: string;\n  commentId: string;\n  replyId?: string;\n  operator: { openId: string; userId?: string; unionId?: string };\n  mentionedBot: boolean;\n  timestamp: number;\n  raw?: unknown;\n}\n\nexport type LarkChannelErrorCode =\n  | 'format_error'\n  | 'target_revoked'\n  | 'rate_limited'\n  | 'permission_denied'\n  | 'upload_failed'\n  | 'ssrf_blocked'\n  | 'send_timeout'\n  | 'not_connected'\n  | 'unknown';\n\nexport class LarkChannelError extends Error {\n  code: LarkChannelErrorCode;\n\n  cause?: unknown;\n\n  context?: { to?: string; messageId?: string; attempt?: number };\n\n  constructor(\n    code: LarkChannelErrorCode,\n    message: string,\n    opts?: { cause?: unknown; context?: LarkChannelError['context'] },\n  ) {\n    super(message);\n    this.name = 'LarkChannelError';\n    this.code = code;\n    this.cause = opts?.cause;\n    this.context = opts?.context;\n  }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Channel configuration\n// ─────────────────────────────────────────────────────────────\n\nexport interface LarkChannelOptions {\n  appId: string;\n  appSecret: string;\n\n  transport?: 'websocket' | 'webhook';\n  webhook?: WebhookOptions;\n\n  safety?: SafetyConfig;\n  policy?: PolicyConfig;\n  outbound?: OutboundConfig;\n\n  logger?: Logger;\n  loggerLevel?: LoggerLevel;\n  cache?: Cache;\n  domain?: Domain | string;\n  httpInstance?: HttpInstance;\n\n  /** Caller tag appended to User-Agent as `source/<name>`. */\n  source?: string;\n\n  /**\n   * Client-only WebSocket settings (currently `pingTimeout`). Forwarded\n   * to the underlying WSClient. Server-pushed values like ping cadence,\n   * reconnect interval / count are not exposed here — they stay\n   * server-authoritative.\n   */\n  wsConfig?: WSConfigOverrides;\n\n  /**\n   * Maximum time (ms) to wait for the WebSocket handshake (`open` /\n   * `error`) before aborting the attempt and letting the retry loop try\n   * again. When unset, no timeout is enforced — the handshake can hang\n   * indefinitely on stuck DNS / proxy / NAT paths.\n   */\n  handshakeTimeoutMs?: number;\n\n  /**\n   * Optional Node http(s) agent forwarded to the underlying WSClient for\n   * the WebSocket transport. Useful for routing the WS through an HTTP(S)\n   * proxy or for customizing TLS / keepalive.\n   */\n  agent?: any;\n\n  /**\n   * Attach the raw Feishu event body on every normalized event\n   * (`message`, `cardAction`, `reaction`, `botAdded`, `comment`) as\n   * `evt.raw`. Useful when a handler needs fields that the normalizer\n   * dropped (e.g. `tenant_key`, `host`, `event_id`, vendor-specific\n   * extensions). Off by default — payloads are smaller and stricter.\n   */\n  includeRawEvent?: boolean;\n\n  /** @deprecated Use `includeRawEvent` instead. Retained for backward compatibility. */\n  includeRawInMessage?: boolean;\n\n  /**\n   * Populate {@link NormalizedMessage.chatMode} on every inbound message by\n   * resolving the chat's mode (p2p / group / topic) — which Feishu omits\n   * from message events. Costs one cached `chat.get` per chat (best-effort;\n   * falls back to 'group' on failure). Off by default to avoid the extra\n   * API call for callers that don't need topic-group awareness.\n   */\n  resolveChatMode?: boolean;\n\n  /**\n   * Populate {@link NormalizedMessage.senderName} on inbound messages by\n   * resolving the sender's display name from the chat's member roster (warmed\n   * via a cached `getChatMembers`). Costs one cached members lookup per chat\n   * (best-effort; degrades to `undefined` on failure). Off by default to avoid\n   * the extra API call for callers that don't need names.\n   */\n  resolveSenderNames?: boolean;\n\n  /**\n   * Override how {@link LarkChannel.getChatMembers} obtains a chat's roster.\n   * When provided and it returns a member array, that array is used and the\n   * Feishu `im.v1.chatMembers.get` call is skipped (useful when the app already\n   * has its own directory / cache). Return `undefined` to fall back to the API.\n   * Results still flow through the internal roster cache.\n   */\n  resolveChatMembers?: (\n    chatId: string,\n  ) => ChatMember[] | undefined | Promise<ChatMember[] | undefined>;\n\n  /**\n   * App-level keepalive watchdog (defense-in-depth above the SDK's internal\n   * ping). When enabled, an independent timer probes the connection and\n   * force-reconnects the WebSocket if it looks stuck while the network is\n   * reachable. `onUnrecoverable` fires when even a forced reconnect fails,\n   * so the app can decide what to do (e.g. restart the process). WebSocket\n   * transport only. Off by default.\n   */\n  keepalive?: {\n    enabled?: boolean;\n    onUnrecoverable?: (err: unknown) => void;\n    /** Heartbeat interval in ms (default 15000). */\n    intervalMs?: number;\n  };\n\n  /**\n   * Per-request timeout (ms) for outbound REST calls. Without it a hung\n   * Feishu API can block the bot indefinitely. Applied to node-sdk's shared\n   * `defaultHttpInstance` (a process-wide singleton, so it also affects other\n   * Clients using the default). Ignored when you supply your own\n   * {@link httpInstance} — configure that instance yourself. Unset = no\n   * client-side REST timeout.\n   */\n  httpTimeoutMs?: number;\n\n  /**\n   * Read `HTTPS_PROXY` / `HTTP_PROXY` from the environment and route traffic\n   * through it: the WebSocket transport (via the WS `agent`, unless an\n   * explicit {@link agent} is given) and outbound REST calls (via the shared\n   * `defaultHttpInstance`, unless a custom {@link httpInstance} is supplied).\n   * Off by default.\n   */\n  respectProxyEnv?: boolean;\n}\n\nexport interface WebhookOptions {\n  verificationToken?: string;\n  encryptKey?: string;\n  adapter?: 'express' | 'koa' | 'koa-router';\n}\n\nexport interface SafetyConfig {\n  dedup?: {\n    ttl?: number;\n    maxEntries?: number;\n    sweepIntervalMs?: number;\n  };\n  chatQueue?: {\n    enabled?: boolean;\n    /**\n     * While a chat's handler is in-flight, accumulate every newly-arrived\n     * message and deliver them as a single merged batch the moment the\n     * in-flight handler drains — instead of letting the debounce window\n     * queue up multiple sequential batches. The `message` callback still\n     * receives one merged {@link NormalizedMessage} (delivery shape\n     * unchanged). Sunk from bridge's `pending-queue.ts`. Off by default.\n     */\n    mergeWhileBusy?: boolean;\n  };\n  batch?: {\n    text?: {\n      delayMs?: number;\n      longThresholdChars?: number;\n      longDelayMs?: number;\n      maxMessages?: number;\n      maxChars?: number;\n    };\n    media?: {\n      delayMs?: number;\n      maxItems?: number;\n    };\n  };\n  staleMessageWindowMs?: number;\n}\n\nexport interface PolicyConfig {\n  /**\n   * Chat allowlist for group messages — entries are **chat ids** (`oc_…`).\n   * When non-empty, only listed chats are processed. Do **not** put an app id\n   * (`cli_…`) here: it is not a chat id, so it silently matches nothing (the\n   * SDK logs a warning if it sees one). This doubles as a lightweight\n   * \"allowFrom\" for bot-at-bot: pair it with `requireMention` to scope the bot\n   * to specific rooms without needing every sender's open_id.\n   */\n  groupAllowlist?: string[];\n  dmMode?: 'open' | 'allowlist' | 'pair' | 'disabled';\n  /**\n   * Sender allowlist for DMs when `dmMode: 'allowlist'` — entries are **sender\n   * ids** matching {@link NormalizedMessage.senderId}: an `open_id` (`ou_…`),\n   * `user_id`, or `union_id`. Do **not** put an app id (`cli_…`) here: a real\n   * sender is never a `cli_` id, so a `cli_` entry grants access to no one (the\n   * SDK logs a warning if it sees one).\n   */\n  dmAllowlist?: string[];\n  requireMention?: boolean;\n  respondToMentionAll?: boolean;\n  /**\n   * Opt-in heuristic guard against two bots @-ing each other in an endless\n   * ping-pong. Off by default. See {@link BotLoopGuardConfig} and the README:\n   * the default `onTrip: 'drop'` silently mutes the bot, so prefer `'reject'`\n   * when the app needs to know, and tune `windowMs` / `maxBotMentions` to the\n   * expected collaboration tempo.\n   */\n  botLoopGuard?: BotLoopGuardConfig;\n}\n\n/** Configuration for the opt-in bot ping-pong guard ({@link PolicyConfig.botLoopGuard}). */\nexport interface BotLoopGuardConfig {\n  /** Enable the guard. Default `false`. */\n  enabled?: boolean;\n  /** Sliding-window width in ms. Default `60000`. */\n  windowMs?: number;\n  /** Trip once this many \"another bot @'d me\" messages fall inside the window. Default `5`. */\n  maxBotMentions?: number;\n  /** Count per chat, or per (chat, sender bot). Default `'chat'`. */\n  scope?: 'chat' | 'chat+sender';\n  /**\n   * On trip: `'drop'` silently drops the message (debug log + one warn on the\n   * first trip), `'reject'` emits a `reject` event with `reason: 'bot_loop'`.\n   * Default `'drop'`.\n   */\n  onTrip?: 'drop' | 'reject';\n}\n\nexport interface OutboundConfig {\n  textChunkLimit?: number;\n  markdownConverter?: 'builtin' | ((md: string) => object);\n  streamThrottleMs?: number;\n  streamThrottleChars?: number;\n  streamInitialText?: string;\n  /**\n   * Maximum character count of a single streaming card's markdown element\n   * before the controller rolls over to a new card. Feishu enforces a\n   * per-element size limit on `cardkit.cardElement.content` updates; once\n   * cumulative AI output approaches that limit, further updates are\n   * rejected with `code: 230099 / ErrCode: 11310 \"element exceeds the\n   * limit\"`. The controller pre-emptively splits and creates a follow-up\n   * card so generation can continue without interruption.\n   *\n   * Default: 30000.\n   */\n  streamMaxElementChars?: number;\n  ssrfGuard?: boolean | { allowlist?: string[] };\n  /**\n   * Allowlist of directories that a **local file** media `source` may be read\n   * from. **Required for local file sources: when unset (or empty), local file\n   * paths are rejected outright** — only `Buffer` and `http(s)` URL sources\n   * work without it. This default-deny avoids reading arbitrary files\n   * (`~/.ssh/id_rsa`, `.env`, …) when `source` is attacker-influenced. When\n   * set, every path must resolve inside one of these directories, a POSIX\n   * blocklist (`/etc/`, `/proc/`, `/sys/`, `/dev/`) still applies, and symlink\n   * targets are re-checked after `realpath`.\n   */\n  allowedFileDirs?: string[];\n  retry?: {\n    maxAttempts?: number;\n    baseDelayMs?: number;\n  };\n}\n\n// ─────────────────────────────────────────────────────────────\n// Low-level return types\n// ─────────────────────────────────────────────────────────────\n\nexport interface ChatInfo {\n  chatId: string;\n  name?: string;\n  description?: string;\n  chatType: 'p2p' | 'group';\n  ownerId?: string;\n  memberCount?: number;\n}\n\n/** One member from {@link LarkChannel.getChatMembers}. */\nexport interface ChatMember {\n  /** Member id in the requested {@link idType} (default `open_id`). */\n  id: string;\n  idType?: IdType;\n  name?: string;\n  tenantKey?: string;\n  /**\n   * Whether the member is a bot. Members returned by `getChatMembers` are\n   * always **users** — Feishu's chat-members API filters bots out — so this is\n   * `false`/`undefined` there. It exists for roster entries harvested from\n   * other sources (e.g. inbound mentions) that can carry bots.\n   */\n  isBot?: boolean;\n}\n\nexport type IdType = 'open_id' | 'user_id' | 'union_id';\n\nexport interface CreateChatOptions {\n  name: string;\n  description?: string;\n  /** Users to seed the new chat with. Interpreted per {@link userIdType}. */\n  inviteUserIds?: string[];\n  /** ID convention for {@link inviteUserIds}. Defaults to `'open_id'`. */\n  userIdType?: IdType;\n  /** Defaults to `'group'`. */\n  chatMode?: 'group';\n  /** Visibility — `'private'` (default) or `'public'`. */\n  chatType?: 'private' | 'public';\n}\n\n/** One entry from {@link LarkChannel.listChats}. */\nexport interface ChatSummary {\n  id: string;\n  name: string;\n}\n\n/** Subset of `application.v6.application.get` the channel surfaces. */\nexport interface AppInfo {\n  /** open_id (or the requested id type) of the app's owner/admin. */\n  ownerId?: string;\n  appName?: string;\n}\n\nexport type ResourceType = 'image' | 'file';\n","import { LarkChannelError, type LarkChannelErrorCode } from '../types';\n\n/**\n * Classify a raw error (typically from axios/fetch or a Feishu API response)\n * into a LarkChannelError with a stable code.\n */\nexport function classifyError(\n  err: unknown,\n  context?: { to?: string; messageId?: string; attempt?: number },\n): LarkChannelError {\n  if (err instanceof LarkChannelError) return err;\n\n  const code = inferCode(err);\n  const message = extractMessage(err);\n  return new LarkChannelError(code, message, { cause: err, context });\n}\n\nfunction inferCode(err: unknown): LarkChannelErrorCode {\n  const raw = err as any;\n  const status = raw?.response?.status ?? raw?.status;\n  const feishuCode = raw?.response?.data?.code ?? raw?.data?.code ?? raw?.code;\n  const msg = String(raw?.message ?? '').toLowerCase();\n\n  if (typeof feishuCode === 'number') {\n    if (feishuCode === 230020 || feishuCode === 230017) return 'target_revoked';\n    if (feishuCode === 99991400 || feishuCode === 99991401) return 'permission_denied';\n    if (feishuCode === 230002 || feishuCode === 230001) return 'format_error';\n  }\n\n  if (status === 429) return 'rate_limited';\n  if (status === 401 || status === 403) return 'permission_denied';\n  if (status === 400) return 'format_error';\n  if (status === 404) return 'target_revoked';\n\n  if (msg.startsWith('ssrf_blocked')) return 'ssrf_blocked';\n  if (msg.includes('timeout') || raw?.code === 'ETIMEDOUT' || raw?.code === 'ECONNABORTED') {\n    return 'send_timeout';\n  }\n\n  return 'unknown';\n}\n\nfunction extractMessage(err: unknown): string {\n  const raw = err as any;\n  return raw?.response?.data?.msg || raw?.response?.data?.message || raw?.message || String(err);\n}\n\nexport function isRetryable(err: LarkChannelError): boolean {\n  return err.code === 'rate_limited' || err.code === 'unknown';\n}\n\nexport function isFormatError(err: LarkChannelError): boolean {\n  return err.code === 'format_error';\n}\n\nexport function isReplyTargetGone(err: LarkChannelError): boolean {\n  return err.code === 'target_revoked';\n}\n","import type { MentionInfo } from '../../types';\n\nconst OPEN_ID = /^(ou_|on_)[A-Za-z0-9_-]+$/;\n\n/** A well-formed Feishu open_id / union_id for use in an `<at user_id=\"…\">`. */\nexport function isValidOpenId(id: string | undefined): id is string {\n  return !!id && OPEN_ID.test(id);\n}\n\n/**\n * Neutralize a display name before it lands in an `<at>` tag body / `user_name`.\n * Display names are attacker-influenced (any group member sets their own), and\n * Feishu renders the `<at>` sink without escaping — a name containing `<`, `>`,\n * `\"`, or a `</at>` / `<at` sequence could inject a second, forged mention (or\n * post markup) in the bot's own voice. We strip those characters rather than\n * HTML-encode, because the tag body is plain text to Feishu and encoded\n * entities would render literally.\n */\nexport function escapeAtName(name: string): string {\n  return name.replace(/[<>\"]/g, '');\n}\n\n/**\n * Build a text prefix that renders as real Feishu mentions when prepended\n * to a text-type outbound message (the <at …> tag form).\n *\n * For post-type messages, the mentions should be injected as `at` elements\n * at the beginning of the post body — use `composePostMentionElements`\n * instead.\n */\nexport function composeMentionsTextPrefix(mentions: MentionInfo[]): string {\n  if (!mentions?.length) return '';\n  const parts: string[] = [];\n  for (const m of mentions) {\n    if (!isValidOpenId(m.openId)) continue;\n    parts.push(`<at user_id=\"${m.openId}\">${escapeAtName(m.name ?? '')}</at>`);\n  }\n  return parts.length > 0 ? parts.join(' ') + ' ' : '';\n}\n\nexport interface PostAtElement {\n  tag: 'at';\n  user_id: string;\n  user_name?: string;\n}\n\n/**\n * Produce `at` elements to prepend to the first paragraph of a post body.\n */\nexport function composePostMentionElements(mentions: MentionInfo[]): PostAtElement[] {\n  if (!mentions?.length) return [];\n  const out: PostAtElement[] = [];\n  for (const m of mentions) {\n    if (!isValidOpenId(m.openId)) continue;\n    out.push({\n      tag: 'at',\n      user_id: m.openId,\n      user_name: m.name ? escapeAtName(m.name) : undefined,\n    });\n  }\n  return out;\n}\n","/**\n * Split a long markdown string into chunks under `limit` characters.\n *\n * Preserves code block integrity — if a chunk boundary would fall inside a\n * fenced code block, the current chunk closes the fence and the next chunk\n * reopens it with the same language tag.\n *\n * Prefers breaking before headings when possible.\n */\nexport function splitWithCodeFences(text: string, limit: number): string[] {\n  if (text.length <= limit) return [text];\n\n  const lines = text.split('\\n');\n  const out: string[] = [];\n  let buf: string[] = [];\n  let bufLen = 0;\n  let fenceLang: string | null = null;\n\n  const flush = () => {\n    if (buf.length === 0) return;\n    let chunk = buf.join('\\n');\n    if (fenceLang !== null) chunk += '\\n```';\n    out.push(chunk);\n    buf = [];\n    bufLen = 0;\n    if (fenceLang !== null) {\n      // reopen fence in the next chunk\n      buf.push('```' + fenceLang);\n      bufLen = buf[0].length;\n    }\n  };\n\n  for (const line of lines) {\n    const m = line.match(/^```(\\w*)$/);\n    const lineLen = line.length + (buf.length > 0 ? 1 : 0); // +1 for \\n\n\n    // If this line is a heading and we already have content that's near\n    // the limit, prefer breaking here.\n    const isHeading = /^#{1,6}\\s/.test(line);\n    const nearFull = bufLen > limit * 0.75;\n\n    if (bufLen + lineLen > limit || (isHeading && nearFull && buf.length > 0)) {\n      flush();\n    }\n\n    buf.push(line);\n    bufLen += lineLen;\n\n    if (m) {\n      // entering or leaving a fence\n      fenceLang = fenceLang === null ? m[1] || '' : null;\n    }\n  }\n  flush();\n  return out;\n}\n","/**\n * Pre-processing for Markdown text rendered through Feishu's native `tag: 'md'`\n * post element. Without this, AI-produced markdown looks visually crammed —\n * H1 headings render at huge size, code blocks butt against surrounding text,\n * tables have no breathing room.\n *\n * Ported from openclaw-lark / src/card/markdown-style.ts. The two cardVersion\n * branches preserve the original behavior:\n *   - cardVersion=1 (Feishu post): heading demotion + blank-line compression\n *   - cardVersion=2 (CardKit v2):  same plus <br> insertion around tables /\n *     code blocks / consecutive headings\n *\n * channel currently only sends posts (msg_type='post'), so callers should\n * pass cardVersion=1. The v2 path is kept for parity in case the channel\n * grows native CardKit support later.\n */\nexport function optimizeMarkdownStyle(text: string, cardVersion = 1): string {\n  try {\n    return _optimize(text, cardVersion);\n  } catch {\n    return text;\n  }\n}\n\nfunction _optimize(text: string, cardVersion: number): string {\n  // 1. Protect fenced code blocks behind placeholders so subsequent regexes\n  //    don't mangle their `#`, `-`, `|` characters.\n  const MARK = '___CB_';\n  const codeBlocks: string[] = [];\n  let r = text.replace(/(^|\\n)(`{3,})([^\\n]*)\\n[\\s\\S]*?\\n\\2(?=\\n|$)/g, (m, prefix = '') => {\n    const block = m.slice(String(prefix).length);\n    return `${prefix}${MARK}${codeBlocks.push(block) - 1}___`;\n  });\n\n  // 2. Heading demotion. H1/H2 render too large in Feishu post — push\n  //    everything down to H4/H5 so the visual hierarchy holds together.\n  //    Order matters: H2~H6 first, then H1, otherwise H1→H4 would be\n  //    re-matched by the H2~H6 rule.\n  const hasH1toH3 = /^#{1,3} /m.test(text);\n  if (hasH1toH3) {\n    r = r.replace(/^#{2,6} (.+)$/gm, '##### $1');\n    r = r.replace(/^# (.+)$/gm, '#### $1');\n  }\n\n  if (cardVersion >= 2) {\n    // 3. <br> between consecutive headings — prevents two headings\n    //    rendering as one chunk.\n    r = r.replace(/^(#{4,5} .+)\\n{1,2}(#{4,5} )/gm, '$1\\n<br>\\n$2');\n\n    // 4a-c. Add breathing room around tables.\n    r = r.replace(/^([^|\\n].*)\\n(\\|.+\\|)/gm, '$1\\n\\n$2');\n    r = r.replace(/\\n\\n((?:\\|.+\\|[^\\S\\n]*\\n?)+)/g, '\\n\\n<br>\\n\\n$1');\n    r = r.replace(/((?:^\\|.+\\|[^\\S\\n]*\\n?)+)/gm, (m, _table, offset) => {\n      const after = r.slice(offset + m.length).replace(/^\\n+/, '');\n      if (!after || /^(---|#{4,5} |\\*\\*)/.test(after)) return m;\n      return m + '\\n<br>\\n';\n    });\n    // 4d-e. Tighten the blank line that's adjacent to the inserted <br>\n    //       so the final output isn't visually too sparse.\n    r = r.replace(/^((?!#{4,5} )(?!\\*\\*).+)\\n\\n(<br>)\\n\\n(\\|)/gm, '$1\\n$2\\n$3');\n    r = r.replace(/^(\\*\\*.+)\\n\\n(<br>)\\n\\n(\\|)/gm, '$1\\n$2\\n\\n$3');\n    r = r.replace(/(\\|[^\\n]*\\n)\\n(<br>\\n)((?!#{4,5} )(?!\\*\\*))/gm, '$1$2$3');\n\n    // 5. Restore code blocks with <br> bookends so they aren't visually\n    //    glued to surrounding text.\n    codeBlocks.forEach((block, i) => {\n      r = r.replace(`${MARK}${i}___`, `\\n<br>\\n${block}\\n<br>\\n`);\n    });\n  } else {\n    // 5. Restore code blocks without <br> for v1 / post.\n    codeBlocks.forEach((block, i) => {\n      r = r.replace(`${MARK}${i}___`, block);\n    });\n  }\n\n  // 6. Compress 3+ consecutive newlines to 2 — keeps the output tidy.\n  r = r.replace(/\\n{3,}/g, '\\n\\n');\n\n  return r;\n}\n","import type { MentionInfo } from '../../types';\nimport { composeMentionsTextPrefix } from './compose-mentions';\nimport { optimizeMarkdownStyle } from './optimize-style';\n\n/**\n * Convert a Markdown string to Feishu post JSON.\n *\n * The post body is a single `tag: 'md'` element whose `text` is the original\n * markdown — Feishu's native renderer handles bold / italic / code / links /\n * headings / lists / blockquotes / `<at>` mentions and code fences. The\n * markdown is run through `optimizeMarkdownStyle` first to fix common visual\n * issues (oversized H1/H2, excess blank lines, etc.).\n *\n * Mentions are rendered as `<at user_id=\"ou_xxx\">name</at>` tokens prepended\n * to the markdown, which the `md` element renders inline.\n */\nexport function markdownToPost(\n  md: string,\n  opts?: { mentions?: MentionInfo[]; title?: string },\n): object {\n  const prefix = composeMentionsTextPrefix(opts?.mentions ?? []);\n  const text = optimizeMarkdownStyle(prefix + md, 1);\n  return {\n    zh_cn: {\n      title: opts?.title ?? '',\n      content: [[{ tag: 'md', text }]],\n    },\n  };\n}\n\ninterface PostElement {\n  tag: 'text' | 'a' | 'at' | 'img' | 'md';\n  text?: string;\n  href?: string;\n  user_id?: string;\n  user_name?: string;\n  image_key?: string;\n  style?: string[];\n  un_escape?: boolean;\n}\n\n/**\n * Extract the plain text body from a post JSON. Used as a fallback when a\n * post send is rejected with a format error and we need to re-send as a\n * plain text message.\n */\nexport function postToPlainText(post: unknown): string {\n  const body = (post as { zh_cn?: { content?: PostElement[][] } })?.zh_cn;\n  if (!body?.content) return '';\n  const lines: string[] = [];\n  for (const paragraph of body.content) {\n    if (!Array.isArray(paragraph)) continue;\n    const parts: string[] = [];\n    for (const el of paragraph) {\n      switch (el.tag) {\n        case 'md':\n        case 'text':\n        case 'a':\n          parts.push(el.text ?? '');\n          break;\n        case 'at':\n          parts.push(el.user_name ? `@${el.user_name}` : '');\n          break;\n        case 'img':\n          parts.push(el.image_key ? `[image]` : '');\n          break;\n      }\n    }\n    lines.push(parts.join(''));\n  }\n  return lines.join('\\n').trim();\n}\n","/**\n * Parse MP4 video duration (ms) from a buffer by walking the ISO BMFF box\n * hierarchy and finding `moov → mvhd`.\n *\n * The `mvhd` box version determines field widths:\n * - version 0: creation/modification/timescale/duration = 4 bytes each\n * - version 1: creation/modification = 8 bytes, timescale = 4 bytes,\n *              duration = 8 bytes\n *\n * Returns undefined if the stream is not parseable or duration is unknown.\n */\nexport function parseMp4Duration(buf: Buffer): number | undefined {\n  if (!buf || buf.length < 16) return undefined;\n\n  const moov = findBoxPayload(buf, 0, buf.length, 'moov');\n  if (!moov) return undefined;\n\n  const mvhd = findBoxPayload(buf, moov.start, moov.end, 'mvhd');\n  if (!mvhd) return undefined;\n\n  const p = mvhd.start;\n  if (p + 4 > buf.length) return undefined;\n\n  const version = buf.readUInt8(p);\n  // skip flags (3 bytes)\n  const base = p + 4;\n\n  let timescale: number;\n  let duration: number;\n\n  if (version === 1) {\n    // creation(8) + modification(8) + timescale(4) + duration(8)\n    if (base + 28 > buf.length) return undefined;\n    timescale = buf.readUInt32BE(base + 16);\n    duration = Number(buf.readBigUInt64BE(base + 20));\n  } else {\n    // creation(4) + modification(4) + timescale(4) + duration(4)\n    if (base + 16 > buf.length) return undefined;\n    timescale = buf.readUInt32BE(base + 8);\n    duration = buf.readUInt32BE(base + 12);\n  }\n\n  if (!timescale || !Number.isFinite(timescale) || !Number.isFinite(duration)) {\n    return undefined;\n  }\n  return Math.round((duration / timescale) * 1000);\n}\n\n/**\n * Walk the boxes from [begin, end) looking for one whose type matches\n * `name`. Returns the payload range (excluding the 8-byte header).\n */\nfunction findBoxPayload(\n  buf: Buffer,\n  begin: number,\n  end: number,\n  name: string,\n): { start: number; end: number } | undefined {\n  let p = begin;\n  while (p + 8 <= end && p + 8 <= buf.length) {\n    const size = buf.readUInt32BE(p);\n    const type = buf.slice(p + 4, p + 8).toString('ascii');\n    const boxEnd =\n      size === 1\n        ? p + Number(buf.readBigUInt64BE(p + 8))\n        : size === 0\n          ? end // box extends to end-of-file\n          : p + size;\n    if (boxEnd <= p || boxEnd > end) return undefined;\n\n    if (type === name) {\n      const payloadStart = size === 1 ? p + 16 : p + 8;\n      return { start: payloadStart, end: boxEnd };\n    }\n    p = boxEnd;\n  }\n  return undefined;\n}\n","/**\n * Parse Opus/OGG audio duration (ms) from a buffer.\n *\n * Scans backward for the last \"OggS\" page capture pattern (0x4f676753) and\n * reads its granule_position (64-bit LE at offset +6 from magic). For Opus,\n * granule_position is the number of decoded samples at 48 kHz — divide by\n * 48 (samples per ms) to get milliseconds.\n *\n * Returns undefined if the buffer doesn't contain a valid Ogg stream or if\n * the granule position is obviously invalid.\n */\nexport function parseOpusDuration(buf: Buffer): number | undefined {\n  if (!buf || buf.length < 27) return undefined;\n\n  for (let i = buf.length - 27; i >= 0; i--) {\n    // \"OggS\" = 0x4f 67 67 53\n    if (buf[i] === 0x4f && buf[i + 1] === 0x67 && buf[i + 2] === 0x67 && buf[i + 3] === 0x53) {\n      const granule = buf.readBigInt64LE(i + 6);\n      if (granule < BigInt(0)) return undefined;\n      const ms = Number(granule) / 48;\n      if (!Number.isFinite(ms) || ms < 0) return undefined;\n      return Math.round(ms);\n    }\n  }\n  return undefined;\n}\n","import { promises as dns } from 'dns';\nimport { isIP } from 'net';\n\n/**\n * CIDR blocks that must not be reached from a URL fetch triggered by the\n * SDK. Covers loopback, link-local, private ranges, CGNAT, documentation.\n */\nconst BLOCKED_V4: Array<[number, number]> = [\n  [0x00000000, 8], // 0.0.0.0/8\n  [0x0a000000, 8], // 10.0.0.0/8\n  [0x7f000000, 8], // 127.0.0.0/8\n  [0xa9fe0000, 16], // 169.254.0.0/16\n  [0xac100000, 12], // 172.16.0.0/12\n  [0xc0a80000, 16], // 192.168.0.0/16\n  [0x64400000, 10], // 100.64.0.0/10 (CGNAT)\n  [0xc0000000, 24], // 192.0.0.0/24\n  [0xc0000200, 24], // 192.0.2.0/24\n  [0xc6120000, 15], // 198.18.0.0/15\n  [0xc6336400, 24], // 198.51.100.0/24\n  [0xcb007100, 24], // 203.0.113.0/24\n  [0xe0000000, 4], // 224.0.0.0/4 (multicast)\n  [0xf0000000, 4], // 240.0.0.0/4 (reserved)\n];\n\nexport interface SsrfGuardOptions {\n  allowlist?: string[]; // hostnames exempt from public-IP checks\n}\n\n/**\n * Result of a successful SSRF validation. Callers should use `resolvedIp`\n * to pin the connection (e.g. via a custom `lookup`), and preserve the\n * original URL's hostname on the wire so TLS SNI and certificate\n * verification continue to work.\n */\nexport interface SsrfValidation {\n  resolvedIp: string;\n  originalHost: string;\n}\n\n/**\n * Validate that `url` does not resolve to an internal / reserved IP, and\n * return the single IP that downstream fetching should pin to. Pinning is\n * essential to close the DNS-rebinding TOCTOU gap: the attacker's DNS\n * server could otherwise return a public IP to this check and a private\n * IP (e.g. cloud metadata `169.254.169.254`) to the subsequent fetch.\n *\n * Allowlisted hostnames skip the public-IP check but still pin — pinning\n * is only about consistency between check-time and use-time, and is\n * harmless when the caller trusts the host.\n */\nexport async function assertPublicUrl(\n  url: string,\n  opts: SsrfGuardOptions = {},\n): Promise<SsrfValidation> {\n  const u = new URL(url);\n  if (u.protocol !== 'http:' && u.protocol !== 'https:') {\n    throw new Error(`ssrf_blocked: protocol ${u.protocol}`);\n  }\n\n  // URL keeps IPv6 literals wrapped in brackets (`[::1]`) — strip them so\n  // `isIP()` recognizes the literal and we don't fall through to DNS.\n  const rawHost = u.hostname;\n  const host = rawHost.startsWith('[') && rawHost.endsWith(']') ? rawHost.slice(1, -1) : rawHost;\n  const allowlisted = opts.allowlist?.includes(host) ?? false;\n\n  let resolvedIp: string;\n  if (isIP(host)) {\n    resolvedIp = host;\n    if (!allowlisted) assertIpPublic(resolvedIp);\n  } else {\n    const records = await dns.lookup(host, { all: true });\n    if (records.length === 0) {\n      throw new Error(`ssrf_blocked: no DNS records for ${host}`);\n    }\n    if (!allowlisted) {\n      // Reject if ANY record is private — we can't trust DNS\n      // round-robin to \"win the lottery\" and only return public IPs.\n      for (const r of records) assertIpPublic(r.address);\n    }\n    resolvedIp = records[0].address;\n  }\n\n  return { resolvedIp, originalHost: host };\n}\n\nfunction assertIpPublic(ip: string): void {\n  const v = isIP(ip);\n  if (v === 4 && ipv4Blocked(ip)) {\n    throw new Error(`ssrf_blocked: ${ip}`);\n  }\n  if (v === 6 && ipv6Blocked(ip)) {\n    throw new Error(`ssrf_blocked: ${ip}`);\n  }\n  if (v === 0) {\n    throw new Error(`ssrf_blocked: not a valid IP: ${ip}`);\n  }\n}\n\nfunction ipv4Blocked(ip: string): boolean {\n  const parts = ip.split('.').map(Number);\n  if (parts.length !== 4 || parts.some((p) => p < 0 || p > 255)) return true;\n  const n = ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0;\n  return BLOCKED_V4.some(([net, bits]) => {\n    const mask = bits === 0 ? 0 : (-1 << (32 - bits)) >>> 0;\n    return (n & mask) === (net & mask);\n  });\n}\n\n/**\n * Hardcoded IPv6 CIDR blocks rejected outright (not delegated to v4).\n * Each tuple is [network address as 128-bit bigint, prefix length].\n *\n * Note: `BigInt('0x...')` is used (instead of `0x...n` literals) so the\n * code compiles under TypeScript `target: es6` — bigint literals need\n * es2020. Semantically identical.\n */\nconst BLOCKED_V6: Array<[bigint, number]> = [\n  [BigInt('0xfe800000000000000000000000000000'), 10], // fe80::/10      link-local\n  [BigInt('0xfc000000000000000000000000000000'), 7], // fc00::/7       ULA\n  [BigInt('0xff000000000000000000000000000000'), 8], // ff00::/8       multicast\n  [BigInt('0x01000000000000000000000000000000'), 64], // 100::/64       discard-only\n  [BigInt('0x20010db8000000000000000000000000'), 32], // 2001:db8::/32  documentation\n  [BigInt('0x20010000000000000000000000000000'), 32], // 2001::/32      Teredo\n];\n\n// Top-96-bit prefixes that identify \"an IPv4 address carried inside IPv6\".\n// When these match, the low 32 bits are the actual IPv4 and we delegate\n// the decision to `ipv4Blocked` — so e.g. NAT64 around a public IPv4\n// passes, NAT64 around a private IPv4 is rejected.\nconst HIGH96_MASK = BigInt('0xffffffffffffffffffffffff00000000');\nconst PREFIX_V4_MAPPED = BigInt('0x00000000000000000000ffff00000000'); // ::ffff:0:0/96\nconst PREFIX_V4_COMPAT = BigInt(0); // ::/96\nconst PREFIX_NAT64 = BigInt('0x0064ff9b000000000000000000000000'); // 64:ff9b::/96\nconst LOW32_MASK = BigInt('0xffffffff');\n\nfunction ipv6Blocked(ip: string): boolean {\n  let n: bigint;\n  try {\n    n = parseIPv6(ip);\n  } catch {\n    // Unparseable — fail-closed: treat as internal / suspicious.\n    return true;\n  }\n\n  // If the address carries an IPv4 in its low 32 bits, check that IPv4.\n  const high96 = n & HIGH96_MASK;\n  if (high96 === PREFIX_V4_MAPPED || high96 === PREFIX_NAT64 || high96 === PREFIX_V4_COMPAT) {\n    const v4 = Number(n & LOW32_MASK);\n    const v4Str = [(v4 >>> 24) & 0xff, (v4 >>> 16) & 0xff, (v4 >>> 8) & 0xff, v4 & 0xff].join('.');\n    return ipv4Blocked(v4Str);\n  }\n\n  // Standalone blocked CIDRs via numeric comparison — handles all\n  // equivalent string representations (compressed, expanded, mixed).\n  return BLOCKED_V6.some(([net, bits]) => {\n    const shift = BigInt(128 - bits);\n    const allOnes = (BigInt(1) << BigInt(128)) - BigInt(1);\n    const mask = (allOnes >> shift) << shift;\n    return (n & mask) === (net & mask);\n  });\n}\n\n/**\n * Parse any representation of an IPv6 address into a 128-bit bigint.\n * Handles:\n *   - `::` compression (`::1`, `fe80::1`, `::`)\n *   - fully expanded form (`0:0:0:0:0:0:0:1`)\n *   - IPv4-in-IPv6 mixed notation (`::ffff:127.0.0.1`, `64:ff9b::10.0.0.1`)\n *   - Zone ID suffix (`fe80::1%eth0` — the suffix is ignored)\n * Throws on any malformed input; the caller treats a throw as\n * fail-closed.\n */\nfunction parseIPv6(ip: string): bigint {\n  let addr = ip.split('%')[0].toLowerCase(); // strip Zone ID\n\n  // Convert IPv4 suffix (dotted decimal) to two 16-bit hex groups so the\n  // rest of the parser only sees hex groups.\n  const v4Suffix = addr.match(/:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/);\n  if (v4Suffix) {\n    const parts = v4Suffix[1].split('.').map(Number);\n    if (parts.length !== 4 || parts.some((p) => !Number.isFinite(p) || p < 0 || p > 255)) {\n      throw new Error('bad v4 suffix');\n    }\n    const hex1 = ((parts[0] << 8) | parts[1]).toString(16);\n    const hex2 = ((parts[2] << 8) | parts[3]).toString(16);\n    addr = addr.slice(0, addr.length - v4Suffix[1].length) + `${hex1}:${hex2}`;\n  }\n\n  // Expand `::`.\n  const halves = addr.split('::');\n  if (halves.length > 2) throw new Error('multiple \"::\"');\n\n  let groups: string[];\n  if (halves.length === 2) {\n    const left = halves[0] ? halves[0].split(':') : [];\n    const right = halves[1] ? halves[1].split(':') : [];\n    const fill = 8 - left.length - right.length;\n    if (fill < 0) throw new Error('too many groups');\n    groups = [...left, ...Array(fill).fill('0'), ...right];\n  } else {\n    groups = addr.split(':');\n  }\n\n  if (groups.length !== 8) throw new Error('wrong group count');\n\n  let n = BigInt(0);\n  for (const g of groups) {\n    if (!/^[0-9a-f]{0,4}$/.test(g)) throw new Error('bad group');\n    n = (n << BigInt(16)) | BigInt(`0x${g || '0'}`);\n  }\n  return n;\n}\n","import type { Client } from '@larksuiteoapi/node-sdk';\nimport fs from 'fs';\nimport http from 'http';\nimport https from 'https';\nimport type { LookupFunction } from 'net';\nimport path from 'path';\nimport type { OutboundConfig } from '../../types';\nimport { LarkChannelError } from '../../types';\nimport { parseMp4Duration } from './duration-mp4';\nimport { parseOpusDuration } from './duration-ogg';\nimport { assertPublicUrl, type SsrfGuardOptions } from './ssrf-guard';\n\n/**\n * POSIX directory prefixes that can never be a legitimate media source.\n * Skipped on Windows (no equivalent single-prefix set exists there; a\n * downstream allowlist is the right tool for Windows deployments).\n */\nconst POSIX_BLOCKED_PREFIXES = ['/etc/', '/proc/', '/sys/', '/dev/'];\n\n/** Max bytes accepted from a URL-based media source. Protects against\n *  memory DoS when a malicious URL tries to return gigabytes. */\nconst URL_MAX_BYTES = 50 * 1024 * 1024;\n\nexport type MediaKind = 'image' | 'file' | 'audio' | 'video';\n\nexport interface UploadInput {\n  kind: MediaKind;\n  source: string | Buffer;\n  fileName?: string;\n  duration?: number; // explicit caller-provided duration (ms)\n  coverImageKey?: string;\n}\n\nexport interface UploadResult {\n  kind: MediaKind;\n  fileKey: string; // image_key for images, file_key otherwise\n  durationMs?: number;\n}\n\nexport class MediaUploader {\n  constructor(\n    private client: Client,\n    private config?: OutboundConfig,\n  ) {}\n\n  async upload(input: UploadInput): Promise<UploadResult> {\n    const buffer = await this.toBuffer(input.source);\n\n    if (input.kind === 'image') {\n      return this.uploadImage(buffer);\n    }\n    if (input.kind === 'audio') {\n      const duration = this.resolveDuration(input, buffer);\n      return this.uploadFile(buffer, 'opus', input.fileName ?? 'voice.opus', duration);\n    }\n    if (input.kind === 'video') {\n      const duration = this.resolveDuration(input, buffer);\n      return this.uploadFile(buffer, 'mp4', input.fileName ?? 'video.mp4', duration);\n    }\n    // generic file — no duration required\n    return this.uploadFile(buffer, 'stream', input.fileName ?? 'upload.bin');\n  }\n\n  /**\n   * Materialize `source` into a Buffer. A string is treated as:\n   *   • `http://` / `https://` URL — fetch with SSRF guard\n   *   • anything else — local filesystem path, read directly\n   */\n  private async toBuffer(source: string | Buffer): Promise<Buffer> {\n    if (Buffer.isBuffer(source)) return source;\n\n    if (!/^https?:\\/\\//i.test(source)) {\n      const allowedDirs = this.config?.allowedFileDirs;\n\n      // Default-deny: reading a local file path requires an explicit allowlist.\n      // Without it, an attacker-influenced `source` (common in AI-agent \"send\n      // the file at path X\" flows) could read arbitrary files — `~/.ssh/id_rsa`,\n      // `.env`, `~/.aws/credentials`, etc. Buffer and URL sources are\n      // unaffected; only path-based local reads are gated.\n      if (!allowedDirs || allowedDirs.length === 0) {\n        throw new LarkChannelError(\n          'upload_failed',\n          'local file source requires `outbound.allowedFileDirs` to be configured',\n        );\n      }\n\n      const resolved = path.resolve(source);\n\n      // Pre-I/O POSIX blocklist check: catches `/etc/passwd` etc. before\n      // touching the filesystem. Path-traversal variants like\n      // `/tmp/../etc/passwd` are collapsed by `path.resolve`.\n      this.assertNotOnPosixBlocklist(resolved);\n\n      try {\n        // Follow symlinks so that a symlink inside an allowed dir pointing\n        // outside is caught (containment is checked on the real target), and\n        // macOS `/etc` → `/private/etc` aliases are blocked too.\n        const realPath = await fs.promises.realpath(resolved);\n        this.assertNotOnPosixBlocklist(realPath);\n\n        // Realpath the allowed dirs too — otherwise macOS `/var` →\n        // `/private/var` aliasing would reject all tmpdir-based paths.\n        const canonicalDirs = await Promise.all(\n          allowedDirs.map(async (d) => {\n            const r = path.resolve(d);\n            try {\n              return await fs.promises.realpath(r);\n            } catch {\n              return r;\n            }\n          }),\n        );\n        const inAllowed = canonicalDirs.some(\n          (d) => realPath === d || realPath.startsWith(d + path.sep),\n        );\n        if (!inAllowed) {\n          throw new LarkChannelError(\n            'upload_failed',\n            `file path is outside allowed directories: ${realPath}`,\n          );\n        }\n\n        // TOCTOU-safe read: open the validated real path, confirm via fstat\n        // that it's a regular file, then read from the fd (not the path) — so\n        // a symlink swap after the checks can't redirect the read elsewhere.\n        const fh = await fs.promises.open(realPath, 'r');\n        try {\n          const st = await fh.stat();\n          if (!st.isFile()) {\n            throw new LarkChannelError('upload_failed', `not a regular file: ${realPath}`);\n          }\n          return await fh.readFile();\n        } finally {\n          await fh.close();\n        }\n      } catch (e) {\n        if (e instanceof LarkChannelError) throw e;\n        throw new LarkChannelError(\n          'upload_failed',\n          `source is neither an http(s) URL nor a readable local file: ${source}`,\n          { cause: e },\n        );\n      }\n    }\n\n    const ssrf = this.config?.ssrfGuard;\n    const guardEnabled = ssrf !== false;\n    let resolvedIp: string | undefined;\n    if (guardEnabled) {\n      const ssrfOpts: SsrfGuardOptions =\n        typeof ssrf === 'object' && ssrf ? { allowlist: ssrf.allowlist } : {};\n      try {\n        ({ resolvedIp } = await assertPublicUrl(source, ssrfOpts));\n      } catch (e) {\n        throw new LarkChannelError('ssrf_blocked', `URL blocked: ${String(e)}`, {\n          cause: e,\n        });\n      }\n    }\n    try {\n      // Pin the TCP connect target to the IP we just validated so the\n      // attacker can't swap in a private IP between the DNS check and\n      // the fetch (DNS rebinding / TOCTOU). We inject a custom `lookup`\n      // into a per-request agent; the URL itself stays intact so TLS\n      // SNI and certificate verification continue to use the original\n      // hostname.\n      const requestOpts: Record<string, unknown> = {\n        url: source,\n        method: 'GET',\n        responseType: 'arraybuffer',\n        timeout: 15_000,\n        maxContentLength: URL_MAX_BYTES,\n        maxBodyLength: URL_MAX_BYTES,\n        // Do NOT auto-follow redirects: a 3xx to an internal IP / cloud IMDS\n        // (e.g. 169.254.169.254) would bypass the pinned-IP agent and defeat\n        // the SSRF guard. A redirect now surfaces as a failed fetch instead.\n        maxRedirects: 0,\n      };\n      if (resolvedIp) {\n        const agent = makePinnedAgent(source, resolvedIp);\n        requestOpts.httpAgent = agent;\n        requestOpts.httpsAgent = agent;\n      }\n      const res = await this.client.httpInstance.request(requestOpts);\n      return Buffer.from(res as ArrayBuffer);\n    } catch (e) {\n      throw new LarkChannelError('upload_failed', `fetch source URL failed`, {\n        cause: e,\n      });\n    }\n  }\n\n  /**\n   * Reject paths pointing at POSIX system directories (`/etc`, `/proc`,\n   * `/sys`, `/dev`). Called twice by the caller — once on the resolved\n   * path pre-I/O, again on the realpath — so both direct hits\n   * (`/etc/passwd`) and macOS alias hits (`/etc` → `/private/etc`) are\n   * caught.\n   */\n  private assertNotOnPosixBlocklist(p: string): void {\n    if (process.platform === 'win32') return;\n    if (POSIX_BLOCKED_PREFIXES.some((pre) => p === pre.slice(0, -1) || p.startsWith(pre))) {\n      throw new LarkChannelError('upload_failed', `file path is not allowed: ${p}`);\n    }\n  }\n\n  private resolveDuration(input: UploadInput, buffer: Buffer): number {\n    if (input.duration != null && input.duration > 0) return input.duration;\n    const parsed =\n      input.kind === 'audio'\n        ? parseOpusDuration(buffer)\n        : input.kind === 'video'\n          ? parseMp4Duration(buffer)\n          : undefined;\n    if (parsed != null) return parsed;\n    throw new LarkChannelError(\n      'upload_failed',\n      `duration could not be determined for ${input.kind}; pass it explicitly`,\n    );\n  }\n\n  private async uploadImage(buffer: Buffer): Promise<UploadResult> {\n    try {\n      const r = await this.client.im.v1.image.create({\n        data: { image_type: 'message', image: buffer } as never,\n      });\n      // The code-gen client already strips the outer envelope and returns\n      // `res?.data` directly, so `image_key` sits at the top level. Keep\n      // the nested `.data.image_key` path as a defensive fallback in case\n      // a caller plugs in a different HttpInstance that preserves the\n      // envelope.\n      const key =\n        (r as { image_key?: string } | null)?.image_key ??\n        (r as { data?: { image_key?: string } } | null)?.data?.image_key;\n      if (!key) throw new Error('image_key missing in upload response');\n      return { kind: 'image', fileKey: key };\n    } catch (e) {\n      throw new LarkChannelError('upload_failed', `image upload failed`, {\n        cause: e,\n      });\n    }\n  }\n\n  private async uploadFile(\n    buffer: Buffer,\n    fileType: 'opus' | 'mp4' | 'stream' | 'pdf' | 'doc' | 'xls' | 'ppt',\n    fileName: string,\n    durationMs?: number,\n  ): Promise<UploadResult> {\n    try {\n      const data: Record<string, unknown> = {\n        file_type: fileType,\n        file_name: fileName,\n        file: buffer,\n      };\n      if (durationMs != null) data.duration = durationMs;\n      const r = await this.client.im.v1.file.create({\n        data: data as never,\n      });\n      const key =\n        (r as { file_key?: string } | null)?.file_key ??\n        (r as { data?: { file_key?: string } } | null)?.data?.file_key;\n      if (!key) throw new Error('file_key missing in upload response');\n      const kind: MediaKind = fileType === 'opus' ? 'audio' : fileType === 'mp4' ? 'video' : 'file';\n      return { kind, fileKey: key, durationMs };\n    } catch (e) {\n      if (e instanceof LarkChannelError) throw e;\n      throw new LarkChannelError('upload_failed', `file upload failed`, {\n        cause: e,\n      });\n    }\n  }\n}\n\n/**\n * Build a per-request http(s) Agent whose DNS lookup always returns\n * `pinnedIp`, regardless of what hostname Node would otherwise resolve.\n * The URL's original hostname is preserved on the wire, which keeps TLS\n * SNI and certificate verification working. This closes the window where\n * a malicious DNS server could return a different (private) IP on the\n * second resolution triggered by the actual fetch.\n */\nfunction makePinnedAgent(url: string, pinnedIp: string): http.Agent | https.Agent {\n  const AgentClass = url.startsWith('https:') ? https.Agent : http.Agent;\n  const agent = new AgentClass();\n\n  const family = pinnedIp.includes(':') ? 6 : 4;\n  const lookup: LookupFunction = (_hostname, _opts, cb) => {\n    cb(null, pinnedIp, family);\n  };\n\n  // Node's Agent doesn't accept `lookup` in its constructor options, so we\n  // wrap `createConnection` to fold `lookup` into every outgoing socket.\n  const origCreateConnection = agent.createConnection.bind(agent);\n  (\n    agent as unknown as {\n      createConnection: (opts: unknown, cb: unknown) => unknown;\n    }\n  ).createConnection = (opts, cb) =>\n    origCreateConnection({ ...(opts as object), lookup }, cb as never);\n\n  return agent;\n}\n","import type { LarkChannelError } from '../types';\nimport { classifyError, isRetryable } from './errors';\n\nexport interface RetryOptions {\n  maxAttempts?: number; // default 3\n  baseDelayMs?: number; // default 500\n}\n\n/**\n * Execute `op` with exponential backoff. Only retries errors classified as\n * retryable (rate_limited / unknown). Business errors (format / revoked /\n * permission / timeout) fail fast and bubble up.\n */\nexport async function retry<T>(\n  op: (attempt: number) => Promise<T>,\n  opts: RetryOptions = {},\n): Promise<T> {\n  const max = opts.maxAttempts ?? 3;\n  const base = opts.baseDelayMs ?? 500;\n\n  let lastErr: LarkChannelError | undefined;\n  for (let attempt = 1; attempt <= max; attempt++) {\n    try {\n      return await op(attempt);\n    } catch (raw) {\n      const err = classifyError(raw, { attempt });\n      lastErr = err;\n      if (attempt >= max || !isRetryable(err)) {\n        throw err;\n      }\n      const delay = base * 3 ** (attempt - 1);\n      await sleep(delay);\n    }\n  }\n  throw lastErr!;\n}\n\nfunction sleep(ms: number): Promise<void> {\n  return new Promise((r) => setTimeout(r, ms));\n}\n","export type ReceiveIdType = 'chat_id' | 'open_id' | 'user_id' | 'union_id' | 'email';\n\n/**\n * Infer Feishu's `receive_id_type` from the prefix of a target id.\n *\n *   oc_*          → chat_id\n *   ou_*          → open_id\n *   on_*          → union_id\n *   contains '@'  → email\n *   fallback      → user_id\n */\nexport function detectReceiveIdType(to: string): ReceiveIdType {\n  if (!to) throw new Error('empty receive_id');\n  if (to.startsWith('oc_')) return 'chat_id';\n  if (to.startsWith('ou_')) return 'open_id';\n  if (to.startsWith('on_')) return 'union_id';\n  if (to.includes('@')) return 'email';\n  return 'user_id';\n}\n","/**\n * Dual-threshold throttle: fires `flush` either when `ms` have elapsed\n * since the last fire OR when `chars` characters have accumulated.\n *\n * Usage inside a stream controller:\n *   const t = new Throttle({ ms: 100, chars: 50 }, () => doPatch(buffer));\n *   await t.note(deltaLen);       // may or may not fire, schedule the rest\n *   await t.flushNow();           // end-of-stream force flush\n */\nexport class Throttle {\n  private pendingChars = 0;\n  private timer?: NodeJS.Timeout;\n  /** Tracks the in-flight fire as a Promise so `flushNow` can await it. */\n  private inFlight?: Promise<void>;\n  private lastFireAt = 0;\n\n  constructor(\n    private opts: { ms: number; chars: number },\n    private fire: () => Promise<void>,\n  ) {}\n\n  /**\n   * Accumulate bytes and decide whether to fire now, schedule a timer,\n   * or do nothing (a fire is already scheduled).\n   */\n  note(deltaChars: number): void {\n    this.pendingChars += deltaChars;\n    if (this.pendingChars >= this.opts.chars) {\n      this.fireSoon(0);\n      return;\n    }\n    if (!this.timer) {\n      const elapsed = Date.now() - this.lastFireAt;\n      const wait = Math.max(0, this.opts.ms - elapsed);\n      this.fireSoon(wait);\n    }\n  }\n\n  private fireSoon(delay: number): void {\n    if (this.timer) clearTimeout(this.timer);\n    this.timer = setTimeout(() => {\n      this.timer = undefined;\n      void this.doFire();\n    }, delay);\n  }\n\n  /**\n   * Force-flush everything accumulated so far, including any content that\n   * arrived during an in-flight fire. Waits for the in-flight fire to\n   * complete, then fires once more to capture the final state — without\n   * this second fire, the last chunk of appended content would be missed\n   * (the in-flight fire captured a snapshot BEFORE those chunks arrived).\n   */\n  async flushNow(): Promise<void> {\n    if (this.timer) {\n      clearTimeout(this.timer);\n      this.timer = undefined;\n    }\n    if (this.inFlight) {\n      await this.inFlight;\n    }\n    await this.doFire();\n  }\n\n  private async doFire(): Promise<void> {\n    if (this.inFlight) {\n      // Someone else is firing; schedule a follow-up so the latest\n      // accumulated chars get captured after the in-flight fire ends.\n      this.fireSoon(this.opts.ms);\n      return;\n    }\n    const p = (async () => {\n      this.pendingChars = 0;\n      this.lastFireAt = Date.now();\n      await this.fire();\n    })();\n    this.inFlight = p;\n    try {\n      await p;\n    } finally {\n      this.inFlight = undefined;\n    }\n  }\n\n  dispose(): void {\n    if (this.timer) {\n      clearTimeout(this.timer);\n      this.timer = undefined;\n    }\n  }\n}\n","/**\n * A per-stream FIFO queue that serializes async updates, so that concurrent\n * calls to `append()` / `update()` always result in PATCH operations\n * happening in submission order.\n */\nexport class UpdateQueue {\n  private tail: Promise<void> = Promise.resolve();\n\n  enqueue<T>(task: () => Promise<T>): Promise<T> {\n    const next = this.tail.then(task, task);\n    this.tail = next.then(\n      () => undefined,\n      () => undefined,\n    );\n    return next;\n  }\n\n  drain(): Promise<void> {\n    return this.tail;\n  }\n}\n","import type {\n  CardStreamController as CardStreamControllerPublic,\n  CardStreamProducer,\n  SendOptions,\n  SendResult,\n} from '../../types';\nimport type { ReceiveIdType } from '../routing';\nimport type { OutboundSender } from '../sender';\nimport { Throttle } from './throttle';\nimport { UpdateQueue } from './update-queue';\n\nconst DEFAULT_THROTTLE_MS = 100;\nconst DEFAULT_THROTTLE_CHARS = 50;\n\n/**\n * Streaming card reply — the caller provides an initial card JSON and a\n * producer that drives incremental `update()` calls with full or partial\n * new card state.\n *\n * Updates are throttled + serialized. On producer exception, the last\n * known state is kept and an error footer element is appended.\n */\nexport class CardStreamControllerImpl implements CardStreamControllerPublic {\n  private _current: object;\n  private _messageId = '';\n  private throttle: Throttle;\n  private queue = new UpdateQueue();\n\n  constructor(\n    private sender: OutboundSender,\n    private to: string,\n    private idType: ReceiveIdType,\n    private opts: SendOptions,\n    initial: object,\n  ) {\n    this._current = initial;\n    const cfg = sender.config;\n    this.throttle = new Throttle(\n      {\n        ms: cfg.streamThrottleMs ?? DEFAULT_THROTTLE_MS,\n        chars: cfg.streamThrottleChars ?? DEFAULT_THROTTLE_CHARS,\n      },\n      () => this.patch(),\n    );\n  }\n\n  get messageId(): string {\n    return this._messageId;\n  }\n  get current(): object {\n    return this._current;\n  }\n\n  async update(next: object | ((current: object) => object)): Promise<void> {\n    const nextCard =\n      typeof next === 'function' ? (next as (c: object) => object)(this._current) : next;\n    this._current = nextCard;\n    this.throttle.note(JSON.stringify(nextCard).length);\n  }\n\n  async run(producer: CardStreamProducer): Promise<SendResult> {\n    await this.sendInitial();\n    try {\n      await producer(this);\n    } catch (e) {\n      await this.failTerminal(e);\n      throw e;\n    }\n    await this.completeTerminal();\n    return { messageId: this._messageId };\n  }\n\n  // ─── internals ─────────────────────────────────────────\n\n  private async sendInitial(): Promise<void> {\n    const id = await this.sender.sendOneWithFallback({\n      to: this.to,\n      idType: this.idType,\n      msgType: 'interactive',\n      content: this._current,\n      replyTo: this.opts.replyTo,\n      replyInThread: this.opts.replyInThread,\n    });\n    this._messageId = id;\n  }\n\n  private async patch(): Promise<void> {\n    if (!this._messageId) return;\n    const snapshot = this._current;\n    await this.queue.enqueue(async () => {\n      await this.sender.patchCard(this._messageId, snapshot);\n    });\n  }\n\n  private async completeTerminal(): Promise<void> {\n    await this.throttle.flushNow();\n    await this.queue.drain();\n  }\n\n  private async failTerminal(_err: unknown): Promise<void> {\n    this.throttle.dispose();\n    const withFooter = appendErrorFooter(this._current);\n    await this.queue.enqueue(async () => {\n      try {\n        await this.sender.patchCard(this._messageId, withFooter);\n      } catch {\n        // best effort\n      }\n    });\n    await this.queue.drain();\n  }\n}\n\nfunction appendErrorFooter(card: unknown): object {\n  const cardObj = card as { elements?: unknown[] };\n  const elements = Array.isArray(cardObj?.elements) ? [...cardObj.elements] : [];\n  elements.push({\n    tag: 'note',\n    elements: [{ tag: 'plain_text', content: '⚠️ 生成中断' }],\n  });\n  return { ...(card as object), elements };\n}\n\nexport class CardStreamController {\n  private impl: CardStreamControllerImpl;\n  constructor(\n    sender: OutboundSender,\n    to: string,\n    idType: ReceiveIdType,\n    opts: SendOptions,\n    initial: object,\n  ) {\n    this.impl = new CardStreamControllerImpl(sender, to, idType, opts, initial);\n  }\n  run(producer: CardStreamProducer): Promise<SendResult> {\n    return this.impl.run(producer);\n  }\n}\n","import type {\n  MarkdownStreamController as MarkdownStreamControllerPublic,\n  MarkdownStreamProducer,\n  SendOptions,\n  SendResult,\n} from '../../types';\nimport { splitWithCodeFences } from '../markdown/splitter';\nimport type { ReceiveIdType } from '../routing';\nimport type { OutboundSender } from '../sender';\nimport { Throttle } from './throttle';\nimport { UpdateQueue } from './update-queue';\n\nconst DEFAULT_THROTTLE_MS = 100;\nconst DEFAULT_THROTTLE_CHARS = 50;\nconst DEFAULT_INITIAL = 'Thinking...';\nconst DEFAULT_EMPTY = '(no content)';\nconst INITIAL_SUMMARY = '[Generating...]';\nconst SUMMARY_MAX_CHARS = 50;\nconst ERROR_FOOTER = '\\n\\n— _(Generation interrupted)_';\nconst DEFAULT_MAX_ELEMENT_CHARS = 30000;\n\nconst ELEMENT_ID = 'stream_md';\n\n/**\n * Shorten a markdown content string into a single-line preview suitable for\n * the card's `summary.content` — shown in chat lists / message previews.\n */\nfunction truncateSummary(text: string, max = SUMMARY_MAX_CHARS): string {\n  if (!text) return '';\n  const cleaned = text.replace(/\\s+/g, ' ').trim();\n  return cleaned.length <= max ? cleaned : cleaned.slice(0, max - 1) + '…';\n}\n\n/**\n * Build the initial card JSON for a streaming markdown reply.\n *\n * `streaming_mode: true` tells Feishu client to render incremental updates\n * (via the cardElement.content API) as a native typewriter animation.\n *\n * `print_strategy: 'fast'` means: when a new full-content update arrives,\n * immediately show any already-buffered-but-not-yet-animated text, so the\n * display doesn't lag behind the upstream token rate.\n */\nfunction buildStreamingCard(initialText: string): object {\n  return {\n    schema: '2.0',\n    config: {\n      streaming_mode: true,\n      summary: { content: INITIAL_SUMMARY },\n      streaming_config: {\n        print_frequency_ms: { default: 70 },\n        print_step: { default: 1 },\n        print_strategy: 'fast',\n      },\n    },\n    body: {\n      elements: [\n        {\n          tag: 'markdown',\n          element_id: ELEMENT_ID,\n          content: initialText,\n        },\n      ],\n    },\n  };\n}\n\n/**\n * Streaming markdown reply that uses Feishu's native typewriter effect\n * (cardkit.v1.cardElement.content).\n *\n * Flow:\n *   1. ensureStarted(): create a card instance with streaming_mode=true,\n *      send it as an interactive message referencing the card_id.\n *   2. append(chunk) / setContent(full): accumulate locally, throttle.\n *   3. throttle fires: updateCardElementContent(content, sequence++)\n *      Feishu client renders typewriter animation based on the diff.\n *   4. completeTerminal(): drain queue + finishStreamingCard to disable\n *      streaming_mode (removes the typing cursor).\n *   5. producer throws → append ERROR_FOOTER + finish stream → rethrow.\n *\n * Rollover: when the current card's element content exceeds\n * `streamMaxElementChars` (Feishu rejects oversized elements with code\n * 230099), the controller splits the content at a safe boundary, finalizes\n * the current card with the head, and creates a fresh streaming card to\n * continue with the tail. Multiple rollovers within one stream are\n * supported. The head card's messageId is what `messageId` / `run()`\n * return; follow-up cards are tracked internally.\n */\nexport class MarkdownStreamControllerImpl implements MarkdownStreamControllerPublic {\n  /** Content of the current (latest) card's markdown element. */\n  private content = '';\n  private _messageId = '';\n  private cardId = '';\n  private sequence = 0;\n\n  private throttle: Throttle;\n  private queue = new UpdateQueue();\n  private started = false;\n  /**\n   * Set when an unrecoverable update error occurs; subsequent pushes /\n   * rollovers are skipped to avoid spamming the API. Terminal cleanup\n   * still runs on a best-effort basis.\n   */\n  private streamingFailed = false;\n  /** messageIds of every rollover card created after the head. */\n  private rolloverMessageIds: string[] = [];\n\n  private readonly maxChars: number;\n\n  constructor(\n    private sender: OutboundSender,\n    private to: string,\n    private idType: ReceiveIdType,\n    private opts: SendOptions,\n  ) {\n    const cfg = this.sender.config;\n    this.throttle = new Throttle(\n      {\n        ms: cfg.streamThrottleMs ?? DEFAULT_THROTTLE_MS,\n        chars: cfg.streamThrottleChars ?? DEFAULT_THROTTLE_CHARS,\n      },\n      () => this.pushContent(),\n    );\n    this.maxChars = cfg.streamMaxElementChars ?? DEFAULT_MAX_ELEMENT_CHARS;\n  }\n\n  get messageId(): string {\n    return this._messageId;\n  }\n\n  async append(chunk: string): Promise<void> {\n    if (!chunk) return;\n    await this.ensureStarted();\n    // `chunk` is a delta: append it verbatim. Accumulated/full-content\n    // producers must use setContent — auto-detecting the two modes is\n    // ambiguous and silently drops legitimate repeated boundary chars\n    // (e.g. '共 3' + '3 条' must render '共 33 条', not '共 3 条').\n    this.content += chunk;\n    this.throttle.note(chunk.length);\n  }\n\n  async setContent(full: string): Promise<void> {\n    await this.ensureStarted();\n    this.content = full ?? '';\n    this.throttle.note(Number.MAX_SAFE_INTEGER);\n  }\n\n  async run(producer: MarkdownStreamProducer): Promise<SendResult> {\n    // Eagerly send the placeholder card so `run()` always returns a real\n    // messageId, and so that failTerminal / completeTerminal have a card\n    // to PATCH even if the producer never appends anything.\n    await this.ensureStarted();\n    try {\n      await producer(this);\n    } catch (e) {\n      await this.failTerminal(e);\n      throw e;\n    }\n    await this.completeTerminal();\n    return { messageId: this._messageId };\n  }\n\n  // ─── internals ─────────────────────────────────────────\n\n  private async ensureStarted(): Promise<void> {\n    if (this.started) return;\n    this.started = true;\n\n    const initialText = this.sender.config.streamInitialText ?? DEFAULT_INITIAL;\n    const cardSpec = buildStreamingCard(initialText || '...');\n\n    this.cardId = await this.sender.createCardInstance(cardSpec);\n    this._messageId = await this.sender.sendCardByReference(\n      this.to,\n      this.idType,\n      this.cardId,\n      this.opts,\n    );\n  }\n\n  private async pushContent(): Promise<void> {\n    if (!this.cardId || this.streamingFailed) return;\n\n    await this.queue.enqueue(async () => {\n      if (this.streamingFailed) return;\n      try {\n        await this.pushSnapshot();\n      } catch (e) {\n        this.streamingFailed = true;\n        this.sender.logger.warn?.('[stream] update failed', e);\n      }\n    });\n  }\n\n  /**\n   * Roll over until content fits and then PATCH the latest card. Caller\n   * must already be inside a queue task — no enqueue here. Used by\n   * pushContent and the terminal helpers so they share rollover behavior.\n   */\n  private async pushSnapshot(): Promise<void> {\n    while (this.content.length > this.maxChars) {\n      await this.rollover();\n    }\n    const snapshot = this.content || '...';\n    await this.sender.updateCardElementContent(this.cardId, ELEMENT_ID, snapshot, ++this.sequence);\n  }\n\n  /**\n   * Finalize the current card with as much head content as fits and start\n   * a fresh streaming card for the tail. Called from inside the queue task\n   * so rollover steps are serialized with regular updates.\n   */\n  private async rollover(): Promise<void> {\n    const chunks = splitWithCodeFences(this.content, this.maxChars);\n    if (chunks.length < 2) {\n      // Splitter couldn't split below the cap (single token over limit?).\n      // Caller's outer catch will mark streamingFailed.\n      throw new Error('rollover: content not splittable below limit');\n    }\n\n    const head = chunks[0];\n    const tail = chunks.slice(1).join('\\n');\n\n    // 1. Pin the old card's element to the head content.\n    await this.sender.updateCardElementContent(this.cardId, ELEMENT_ID, head, ++this.sequence);\n\n    // 2. Disable streaming on the old card. Best-effort — Feishu auto-\n    //    closes after 10min, so a transient failure here is recoverable.\n    try {\n      await this.sender.finishStreamingCard(this.cardId, ++this.sequence, truncateSummary(head));\n    } catch {\n      // best effort\n    }\n\n    // 3. Create a fresh streaming card seeded with the tail. Replies use\n    //    the original opts so every rollover card threads to the same\n    //    original target message.\n    const cardSpec = buildStreamingCard(tail || '...');\n    const newCardId = await this.sender.createCardInstance(cardSpec);\n    const newMessageId = await this.sender.sendCardByReference(\n      this.to,\n      this.idType,\n      newCardId,\n      this.opts,\n    );\n\n    // 4. Switch state to the new card. Sequence is per-element, restart\n    //    from 0 for the fresh element.\n    this.cardId = newCardId;\n    this.content = tail;\n    this.sequence = 0;\n    this.rolloverMessageIds.push(newMessageId);\n  }\n\n  private async completeTerminal(): Promise<void> {\n    await this.throttle.flushNow();\n    await this.queue.drain();\n    if (!this.cardId) return;\n\n    // If the producer never appended anything, PATCH the card to a\n    // neutral terminal state so the user sees the response is finalized\n    // rather than a stuck \"Thinking...\" placeholder.\n    if (!this.content && !this.streamingFailed) {\n      await this.queue.enqueue(async () => {\n        try {\n          await this.sender.updateCardElementContent(\n            this.cardId,\n            ELEMENT_ID,\n            DEFAULT_EMPTY,\n            ++this.sequence,\n          );\n        } catch {\n          // best effort\n        }\n      });\n      await this.queue.drain();\n    }\n\n    try {\n      await this.sender.finishStreamingCard(\n        this.cardId,\n        ++this.sequence,\n        truncateSummary(this.content || DEFAULT_EMPTY),\n      );\n    } catch (e) {\n      // best effort — Feishu auto-closes after 10min anyway\n      this.sender.logger.warn?.('[stream] finishStreamingCard failed', e);\n    }\n  }\n\n  private async failTerminal(_err: unknown): Promise<void> {\n    this.throttle.dispose();\n    if (!this.cardId) return;\n\n    this.content = (this.content || '') + ERROR_FOOTER;\n    await this.queue.enqueue(async () => {\n      try {\n        await this.pushSnapshot();\n      } catch {\n        // best effort\n      }\n    });\n    await this.queue.drain();\n    try {\n      await this.sender.finishStreamingCard(\n        this.cardId,\n        ++this.sequence,\n        truncateSummary(this.content),\n      );\n    } catch {\n      // best effort\n    }\n  }\n}\n\nexport class MarkdownStreamController {\n  private impl: MarkdownStreamControllerImpl;\n  constructor(sender: OutboundSender, to: string, idType: ReceiveIdType, opts: SendOptions) {\n    this.impl = new MarkdownStreamControllerImpl(sender, to, idType, opts);\n  }\n  run(producer: MarkdownStreamProducer): Promise<SendResult> {\n    return this.impl.run(producer);\n  }\n}\n","import type { Client } from '@larksuiteoapi/node-sdk';\nimport type { Logger } from '../internal';\nimport {\n  LarkChannelError,\n  type OutboundConfig,\n  type SendInput,\n  type SendOptions,\n  type SendResult,\n  type StreamInput,\n} from '../types';\nimport { classifyError, isFormatError, isReplyTargetGone } from './errors';\nimport { composeMentionsTextPrefix } from './markdown/compose-mentions';\nimport { splitWithCodeFences } from './markdown/splitter';\nimport { markdownToPost, postToPlainText } from './markdown/to-post';\nimport { MediaUploader } from './media/uploader';\nimport { retry } from './retry';\nimport { detectReceiveIdType, type ReceiveIdType } from './routing';\nimport { CardStreamController } from './streaming/card-stream';\nimport { MarkdownStreamController } from './streaming/markdown-stream';\n\nconst DEFAULT_CHUNK_LIMIT = 3500;\n\ninterface RawSendArgs {\n  to: string;\n  idType: ReceiveIdType;\n  msgType: string;\n  content: object; // parsed; will be JSON.stringify'd at the edge\n  replyTo?: string;\n  replyInThread?: boolean;\n}\n\nexport class OutboundSender {\n  public readonly uploader: MediaUploader;\n  private chunkLimit: number;\n\n  constructor(\n    public readonly client: Client,\n    public readonly config: OutboundConfig,\n    public readonly logger: Logger,\n  ) {\n    this.uploader = new MediaUploader(client, config);\n    this.chunkLimit = config.textChunkLimit ?? DEFAULT_CHUNK_LIMIT;\n  }\n\n  async send(to: string, input: SendInput, opts: SendOptions = {}): Promise<SendResult> {\n    const idType = detectReceiveIdType(to);\n\n    if ('markdown' in input) return this.sendMarkdown(to, idType, input.markdown, opts);\n    if ('text' in input) return this.sendText(to, idType, input.text, opts);\n    if ('post' in input) return this.sendPost(to, idType, input.post, opts);\n    if ('image' in input) return this.sendImage(to, idType, input.image, opts);\n    if ('file' in input) return this.sendFile(to, idType, input.file, opts);\n    if ('audio' in input) return this.sendAudio(to, idType, input.audio, opts);\n    if ('video' in input) return this.sendVideo(to, idType, input.video, opts);\n    if ('card' in input) return this.sendCard(to, idType, input.card, opts);\n    if ('cardId' in input) {\n      const id = await this.sendCardByReference(to, idType, input.cardId, opts);\n      return this.makeResult([id]);\n    }\n    if ('shareChat' in input) return this.sendShareChat(to, idType, input.shareChat.chatId, opts);\n    if ('shareUser' in input) return this.sendShareUser(to, idType, input.shareUser.userId, opts);\n    if ('sticker' in input) return this.sendSticker(to, idType, input.sticker.fileKey, opts);\n    throw new LarkChannelError('format_error', 'unrecognized SendInput shape');\n  }\n\n  async stream(to: string, input: StreamInput, opts: SendOptions = {}): Promise<SendResult> {\n    const idType = detectReceiveIdType(to);\n    if ('markdown' in input) {\n      const controller = new MarkdownStreamController(this, to, idType, opts);\n      return controller.run(input.markdown);\n    }\n    if ('card' in input) {\n      const controller = new CardStreamController(this, to, idType, opts, input.card.initial);\n      return controller.run(input.card.producer);\n    }\n    throw new LarkChannelError('format_error', 'unrecognized StreamInput shape');\n  }\n\n  // ─── text / markdown / post ───────────────────────────\n\n  private async sendMarkdown(\n    to: string,\n    idType: ReceiveIdType,\n    md: string,\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const chunks = splitWithCodeFences(md, this.chunkLimit);\n    const ids: string[] = [];\n    for (let i = 0; i < chunks.length; i++) {\n      const post = this.convertMarkdown(chunks[i], i === 0 ? opts.mentions : undefined);\n      const id = await this.sendOneWithFallback({\n        to,\n        idType,\n        msgType: 'post',\n        content: post,\n        replyTo: replyTargetForChunk(i, opts, ids),\n        replyInThread: opts.replyInThread,\n      });\n      ids.push(id);\n    }\n    return this.makeResult(ids);\n  }\n\n  private async sendText(\n    to: string,\n    idType: ReceiveIdType,\n    text: string,\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const prefix = composeMentionsTextPrefix(opts.mentions ?? []);\n    const body = prefix + text;\n    const chunks = splitPlain(body, this.chunkLimit);\n    const ids: string[] = [];\n    for (let i = 0; i < chunks.length; i++) {\n      const id = await this.sendOneWithFallback({\n        to,\n        idType,\n        msgType: 'text',\n        content: { text: chunks[i] },\n        replyTo: replyTargetForChunk(i, opts, ids),\n        replyInThread: opts.replyInThread,\n      });\n      ids.push(id);\n    }\n    return this.makeResult(ids);\n  }\n\n  private async sendPost(\n    to: string,\n    idType: ReceiveIdType,\n    post: object,\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const id = await this.sendOneWithFallback({\n      to,\n      idType,\n      msgType: 'post',\n      content: post,\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n    return { messageId: id };\n  }\n\n  private convertMarkdown(md: string, mentions?: SendOptions['mentions']): object {\n    const conv = this.config.markdownConverter;\n    if (typeof conv === 'function') return conv(md);\n    return markdownToPost(md, { mentions });\n  }\n\n  // ─── media ────────────────────────────────────────────\n\n  private async sendImage(\n    to: string,\n    idType: ReceiveIdType,\n    input: { source: string | Buffer },\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const up = await this.uploader.upload({ kind: 'image', source: input.source });\n    const id = await this.sendOneWithFallback({\n      to,\n      idType,\n      msgType: 'image',\n      content: { image_key: up.fileKey },\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n    return { messageId: id };\n  }\n\n  private async sendFile(\n    to: string,\n    idType: ReceiveIdType,\n    input: { source: string | Buffer; fileName: string },\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const up = await this.uploader.upload({\n      kind: 'file',\n      source: input.source,\n      fileName: input.fileName,\n    });\n    const id = await this.sendOneWithFallback({\n      to,\n      idType,\n      msgType: 'file',\n      content: { file_key: up.fileKey },\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n    return { messageId: id };\n  }\n\n  private async sendAudio(\n    to: string,\n    idType: ReceiveIdType,\n    input: { source: string | Buffer; duration?: number },\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const up = await this.uploader.upload({\n      kind: 'audio',\n      source: input.source,\n      duration: input.duration,\n    });\n    const id = await this.sendOneWithFallback({\n      to,\n      idType,\n      msgType: 'audio',\n      content: { file_key: up.fileKey },\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n    return { messageId: id };\n  }\n\n  private async sendVideo(\n    to: string,\n    idType: ReceiveIdType,\n    input: { source: string | Buffer; duration?: number; coverImageKey?: string },\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const up = await this.uploader.upload({\n      kind: 'video',\n      source: input.source,\n      duration: input.duration,\n    });\n    const content: Record<string, unknown> = { file_key: up.fileKey };\n    if (input.coverImageKey) content.image_key = input.coverImageKey;\n    const id = await this.sendOneWithFallback({\n      to,\n      idType,\n      msgType: 'media',\n      content,\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n    return { messageId: id };\n  }\n\n  // ─── card ─────────────────────────────────────────────\n\n  private async sendCard(\n    to: string,\n    idType: ReceiveIdType,\n    card: object,\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const id = await this.sendOneWithFallback({\n      to,\n      idType,\n      msgType: 'interactive',\n      content: card,\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n    return { messageId: id };\n  }\n\n  // ─── share / sticker ──────────────────────────────────\n\n  private async sendShareChat(\n    to: string,\n    idType: ReceiveIdType,\n    chatId: string,\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const id = await this.sendOneWithFallback({\n      to,\n      idType,\n      msgType: 'share_chat',\n      content: { chat_id: chatId },\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n    return { messageId: id };\n  }\n\n  private async sendShareUser(\n    to: string,\n    idType: ReceiveIdType,\n    userId: string,\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const id = await this.sendOneWithFallback({\n      to,\n      idType,\n      msgType: 'share_user',\n      content: { user_id: userId },\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n    return { messageId: id };\n  }\n\n  private async sendSticker(\n    to: string,\n    idType: ReceiveIdType,\n    fileKey: string,\n    opts: SendOptions,\n  ): Promise<SendResult> {\n    const id = await this.sendOneWithFallback({\n      to,\n      idType,\n      msgType: 'sticker',\n      content: { file_key: fileKey },\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n    return { messageId: id };\n  }\n\n  // ─── low-level raw send with fallback & retry ─────────\n\n  /**\n   * Send once with fallback for format errors (post → text) and for\n   * vanished reply targets (reply → create).\n   */\n  async sendOneWithFallback(args: RawSendArgs): Promise<string> {\n    try {\n      return await this.rawSendWithRetry(args);\n    } catch (e) {\n      const err = classifyError(e, { to: args.to });\n      if (isReplyTargetGone(err) && args.replyTo) {\n        return this.rawSendWithRetry({ ...args, replyTo: undefined });\n      }\n      if (isFormatError(err) && args.msgType === 'post') {\n        const plain = postToPlainText(args.content);\n        return this.rawSendWithRetry({\n          ...args,\n          msgType: 'text',\n          content: { text: plain || '[message]' },\n        });\n      }\n      throw err;\n    }\n  }\n\n  async rawSendWithRetry(args: RawSendArgs): Promise<string> {\n    return retry(() => this.rawSend(args), this.config.retry);\n  }\n\n  private async rawSend(args: RawSendArgs): Promise<string> {\n    const payload = {\n      receive_id: args.to,\n      msg_type: args.msgType,\n      content: JSON.stringify(args.content),\n    };\n    if (args.replyTo) {\n      const r = await this.client.im.v1.message.reply({\n        path: { message_id: args.replyTo },\n        data: {\n          content: payload.content,\n          msg_type: args.msgType,\n          reply_in_thread: args.replyInThread,\n        } as never,\n      });\n      const id = (r as { data?: { message_id?: string } }).data?.message_id;\n      if (!id) throw new LarkChannelError('unknown', 'message_id missing from reply response');\n      return id;\n    }\n    const r = await this.client.im.v1.message.create({\n      params: { receive_id_type: args.idType } as never,\n      data: payload as never,\n    });\n    const id = (r as { data?: { message_id?: string } }).data?.message_id;\n    if (!id) throw new LarkChannelError('unknown', 'message_id missing from create response');\n    return id;\n  }\n\n  // ─── helpers used by streaming ────────────────────────\n\n  /**\n   * Full card replace via im.v1.message.patch — used by CardStreamController\n   * and the public `channel.updateCard()` low-level API. Not suitable for\n   * text streaming (use native cardkit streaming below for that).\n   */\n  async patchCard(messageId: string, card: object): Promise<void> {\n    await this.client.im.v1.message.patch({\n      path: { message_id: messageId },\n      data: { content: JSON.stringify(card) } as never,\n    });\n  }\n\n  /**\n   * Create a card instance via cardkit.v1.card.create. Used to get a\n   * `card_id` that can be referenced from messages AND updated with\n   * native streaming APIs (cardElement.content for typewriter effect).\n   */\n  async createCardInstance(spec: object): Promise<string> {\n    const r = await this.client.cardkit.v1.card.create({\n      data: { type: 'card_json', data: JSON.stringify(spec) } as never,\n    });\n    const cardId = (r as { data?: { card_id?: string } }).data?.card_id;\n    if (!cardId) {\n      throw new LarkChannelError('unknown', 'cardkit.card.create returned no card_id');\n    }\n    return cardId;\n  }\n\n  /**\n   * Send an interactive message that references a pre-created card instance\n   * by card_id. Returns the resulting message_id.\n   */\n  async sendCardByReference(\n    to: string,\n    idType: ReceiveIdType,\n    cardId: string,\n    opts: SendOptions,\n  ): Promise<string> {\n    return this.rawSendWithRetry({\n      to,\n      idType,\n      msgType: 'interactive',\n      content: { type: 'card', data: { card_id: cardId } },\n      replyTo: opts.replyTo,\n      replyInThread: opts.replyInThread,\n    });\n  }\n\n  /**\n   * Full card replace by `card_id` via cardkit.v1.card.update — the entity-API\n   * counterpart to {@link patchCard} (which targets a message_id). `sequence`\n   * must increase monotonically per card so out-of-order updates are rejected\n   * server-side rather than silently clobbering newer content. `uuid` is\n   * derived from cardId + sequence for idempotency.\n   */\n  async updateCardFull(cardId: string, cardJson: object, sequence: number): Promise<void> {\n    await this.client.cardkit.v1.card.update({\n      path: { card_id: cardId },\n      data: {\n        card: { type: 'card_json', data: JSON.stringify(cardJson) },\n        sequence,\n        uuid: `u_${cardId}_${sequence}`,\n      } as never,\n    });\n  }\n\n  /**\n   * Stream update: replace a card element's content and let Feishu render\n   * the incremental diff as a typewriter animation. `sequence` must be\n   * monotonically increasing per card (duplicates/out-of-order → rejected\n   * server-side). `uuid` is required for idempotency — we compose it from\n   * cardId + sequence so two concurrent calls with the same seq would\n   * dedupe rather than both take effect.\n   */\n  async updateCardElementContent(\n    cardId: string,\n    elementId: string,\n    content: string,\n    sequence: number,\n  ): Promise<void> {\n    await this.client.cardkit.v1.cardElement.content({\n      path: { card_id: cardId, element_id: elementId },\n      data: {\n        content,\n        sequence,\n        uuid: `c_${cardId}_${sequence}`,\n      } as never,\n    });\n  }\n\n  /**\n   * Switch a streaming card to finalized state (streaming_mode: false).\n   * Feishu auto-closes after 10min regardless, but callers should close\n   * explicitly when producer completes.\n   *\n   * `summary` is optional; when provided, also updates the card's preview\n   * text (the one shown in message lists / chat previews). Without it, the\n   * preview stays at whatever was set during streaming (typically the\n   * default \"[Generating...]\"), which looks stuck.\n   */\n  async finishStreamingCard(cardId: string, sequence: number, summary?: string): Promise<void> {\n    const config: Record<string, unknown> = { streaming_mode: false };\n    if (summary !== undefined) {\n      config.summary = { content: summary };\n    }\n    await this.client.cardkit.v1.card.settings({\n      path: { card_id: cardId },\n      data: {\n        settings: JSON.stringify({ config }),\n        sequence,\n        uuid: `s_${cardId}_${sequence}`,\n      } as never,\n    });\n  }\n\n  private makeResult(ids: string[]): SendResult {\n    return {\n      messageId: ids[0],\n      chunkIds: ids.length > 1 ? ids : undefined,\n    };\n  }\n}\n\n/**\n * Pick the reply target for chunk `i` of a split message.\n *\n * The first chunk uses the caller's `replyTo` verbatim. Continuation chunks\n * chain off the previous chunk's id so a long reply stays in one thread / reply\n * line instead of breaking out as top-level messages (issue #190) — but only\n * when the send is \"anchored\" (a reply target or a thread). A plain fresh send\n * keeps its continuation chunks as independent top-level messages.\n */\nfunction replyTargetForChunk(i: number, opts: SendOptions, ids: string[]): string | undefined {\n  if (i === 0) return opts.replyTo;\n  const anchored = opts.replyTo != null || opts.replyInThread === true;\n  return anchored ? ids[i - 1] : undefined;\n}\n\nfunction splitPlain(text: string, limit: number): string[] {\n  if (text.length <= limit) return [text];\n  const out: string[] = [];\n  for (let i = 0; i < text.length; i += limit) {\n    out.push(text.slice(i, i + limit));\n  }\n  return out;\n}\n","import type { MentionInfo } from '../../types';\nimport { escapeAtName, isValidOpenId } from './compose-mentions';\n\n/**\n * Resolve a display name to an openId, or `undefined` when the name is unknown\n * or ambiguous. Backed by the chat's roster at the call site.\n */\nexport type MentionLookup = (name: string) => string | undefined;\n\n/**\n * Fill `openId` on name-only structured mentions from the roster. Entries that\n * already carry an openId pass through untouched; name-only entries that don't\n * resolve (unknown / ambiguous) are dropped rather than sent with a wrong or\n * missing id.\n */\nexport function resolveNameMentions(mentions: MentionInfo[], lookup: MentionLookup): MentionInfo[] {\n  const out: MentionInfo[] = [];\n  for (const m of mentions) {\n    if (m.openId) {\n      out.push(m);\n      continue;\n    }\n    if (!m.name) continue;\n    const openId = lookup(m.name);\n    if (openId) out.push({ ...m, openId });\n  }\n  return out;\n}\n\n// Longest candidate name to try after an `@`: bounded words and characters so\n// the scan stays linear on hostile input — a 50k-char token yields one\n// bounded window, not a quadratic prefix walk.\nconst MAX_NAME_WORDS = 5;\nconst MAX_NAME_CHARS = 64;\nconst TRAILING_PUNCTUATION = /[.,!?;:)\\]}]+$/;\n\ninterface NameMatch {\n  name: string;\n  openId: string;\n  /** Length of the consumed name text (excluding the leading `@`). */\n  length: number;\n}\n\n/**\n * Rewrite plaintext `@<name>` tokens into real `<at>` tags when the name\n * resolves against the roster. Unknown or ambiguous names are left verbatim\n * (syntax fallback) — an `@xxx` that doesn't resolve is never turned into a\n * mention. Resolution is longest-match-first (so a multi-word `@John Smith`\n * resolves ahead of `@John`), using per-candidate Map lookups rather than a\n * roster-name-derived regex, so it stays linear even on pathological input.\n */\nexport function resolveMentionsInText(text: string, lookup: MentionLookup): string {\n  if (!text.includes('@')) return text;\n  let out = '';\n  let i = 0;\n  while (i < text.length) {\n    const at = text.indexOf('@', i);\n    if (at === -1) {\n      out += text.slice(i);\n      break;\n    }\n    out += text.slice(i, at);\n    // An `@` mid-word (e.g. inside an email `a@b`) is not a mention.\n    const startsToken = at === 0 || /\\s/.test(text[at - 1]);\n    const match = startsToken ? matchNameAt(text, at + 1, lookup) : undefined;\n    if (match) {\n      out += `<at user_id=\"${match.openId}\">${escapeAtName(match.name)}</at>`;\n      i = at + 1 + match.length;\n    } else {\n      out += '@';\n      i = at + 1;\n    }\n  }\n  return out;\n}\n\n/** Longest resolvable name starting at `start`, or `undefined`. */\nfunction matchNameAt(text: string, start: number, lookup: MentionLookup): NameMatch | undefined {\n  const window = text.slice(start, start + MAX_NAME_CHARS);\n  for (const candidate of candidatePrefixes(window)) {\n    const direct = lookup(candidate);\n    if (isValidOpenId(direct)) return { name: candidate, openId: direct, length: candidate.length };\n    const trimmed = candidate.replace(TRAILING_PUNCTUATION, '');\n    if (trimmed !== candidate) {\n      const t = lookup(trimmed);\n      if (isValidOpenId(t)) return { name: trimmed, openId: t, length: trimmed.length };\n    }\n  }\n  return undefined;\n}\n\n/** Word-boundary prefixes of `window`, longest first (up to MAX_NAME_WORDS). */\nfunction candidatePrefixes(window: string): string[] {\n  if (!window || /^\\s/.test(window)) return [];\n  const wordEnds: number[] = [];\n  let inWord = false;\n  for (let k = 0; k < window.length; k++) {\n    const isSpace = /\\s/.test(window[k]);\n    if (!isSpace) {\n      inWord = true;\n    } else if (inWord) {\n      wordEnds.push(k);\n      inWord = false;\n      if (wordEnds.length >= MAX_NAME_WORDS) break;\n    }\n  }\n  if (inWord && wordEnds.length < MAX_NAME_WORDS) wordEnds.push(window.length);\n  return wordEnds.map((end) => window.slice(0, end)).reverse();\n}\n","import type { MentionInfo, NormalizedMessage, ResourceDescriptor } from '../types';\nimport type { BatchConfig, BatchedDispatch } from './types';\n\ntype FlushHandler = (batch: BatchedDispatch) => Promise<void>;\n\n/**\n * Per-scope pipeline that does two things at once:\n *   - Debounce-based batch aggregation (for IM messages)\n *   - Strict serialization of all work (both batched flushes and direct\n *     `run()` tasks) via a promise chain\n *\n * Supports two entry points:\n *   - push(msg, handler):  enqueue for batched dispatch\n *   - run(task):           run a one-shot async task serialized after any\n *                          pending batch and previous tasks\n */\nexport class ChatPipeline {\n  private buffer: NormalizedMessage[] = [];\n  private bufferChars = 0;\n  private timer?: NodeJS.Timeout;\n  private tail: Promise<void> = Promise.resolve();\n  private pendingHandler?: FlushHandler;\n  /** True while a flush is enqueued/in-flight. Used by mergeWhileBusy. */\n  private busy = false;\n\n  constructor(\n    private config: BatchConfig,\n    private serialOnly: boolean,\n  ) {}\n\n  push(msg: NormalizedMessage, handler: FlushHandler): void {\n    this.buffer.push(msg);\n    this.bufferChars += msg.content.length;\n    this.pendingHandler ??= handler;\n\n    // mergeWhileBusy: while a flush is in-flight, just accumulate — the\n    // settle hook will emit everything buffered as one batch when it drains.\n    // Avoids the debounce window queueing up multiple sequential batches.\n    if (this.config.mergeWhileBusy && this.busy) {\n      return;\n    }\n\n    // Force flush when caps reached.\n    if (this.buffer.length >= this.config.maxMessages || this.bufferChars >= this.config.maxChars) {\n      this.clearTimer();\n      this.enqueueFlush();\n      return;\n    }\n\n    // Pure-serial mode (debounce disabled).\n    if (this.config.delayMs <= 0 || this.serialOnly) {\n      this.clearTimer();\n      this.enqueueFlush();\n      return;\n    }\n\n    // Debounced flush.\n    this.clearTimer();\n    const delay =\n      this.bufferChars >= this.config.longThresholdChars\n        ? this.config.longDelayMs\n        : this.config.delayMs;\n    this.timer = setTimeout(() => {\n      this.timer = undefined;\n      this.enqueueFlush();\n    }, delay);\n  }\n\n  run<T>(task: () => Promise<T>): Promise<T> {\n    // Any pending batch flushes first so a follow-on action-like task\n    // runs after its chat's in-flight message work.\n    if (this.buffer.length > 0) {\n      this.clearTimer();\n      this.enqueueFlush();\n    }\n    const next = this.tail.then(task, task);\n    this.tail = next.then(\n      () => undefined,\n      () => undefined,\n    );\n    return next as Promise<T>;\n  }\n\n  async flushNow(): Promise<void> {\n    if (this.buffer.length > 0) {\n      this.clearTimer();\n      this.enqueueFlush();\n    }\n    await this.tail;\n  }\n\n  isIdle(): boolean {\n    return this.buffer.length === 0 && !this.timer;\n  }\n\n  dispose(): void {\n    this.clearTimer();\n    this.buffer = [];\n    this.pendingHandler = undefined;\n  }\n\n  private clearTimer(): void {\n    if (this.timer) {\n      clearTimeout(this.timer);\n      this.timer = undefined;\n    }\n  }\n\n  private enqueueFlush(): void {\n    if (this.buffer.length === 0) return;\n    const batch = this.buffer;\n    const handler = this.pendingHandler;\n    this.buffer = [];\n    this.bufferChars = 0;\n    this.pendingHandler = undefined;\n\n    if (!handler) return;\n\n    const dispatch: BatchedDispatch = {\n      message: mergeBatch(batch),\n      sourceIds: batch.map((m) => m.messageId),\n    };\n\n    this.busy = true;\n    const task = () => handler(dispatch);\n    const next = this.tail.then(task, task);\n    this.tail = next.then(\n      () => this.onFlushSettled(),\n      () => this.onFlushSettled(),\n    );\n  }\n\n  /**\n   * Called when a flush task settles. Clears the busy flag and, under\n   * mergeWhileBusy, immediately re-flushes anything that accumulated while\n   * the handler was running — as one merged batch.\n   */\n  private onFlushSettled(): void {\n    this.busy = false;\n    if (this.config.mergeWhileBusy && this.buffer.length > 0) {\n      this.clearTimer();\n      this.enqueueFlush();\n    }\n  }\n}\n\nexport class ChatPipelineManager {\n  private pipelines = new Map<string, ChatPipeline>();\n\n  constructor(private config: BatchConfig) {}\n\n  push(scope: string, msg: NormalizedMessage, handler: FlushHandler): void {\n    this.getOrCreate(scope, false).push(msg, handler);\n  }\n\n  run<T>(scope: string, task: () => Promise<T>): Promise<T> {\n    return this.getOrCreate(scope, true).run(task);\n  }\n\n  private getOrCreate(scope: string, serialOnly: boolean): ChatPipeline {\n    let p = this.pipelines.get(scope);\n    if (!p) {\n      p = new ChatPipeline(this.config, serialOnly);\n      this.pipelines.set(scope, p);\n    }\n    return p;\n  }\n\n  async flushAll(): Promise<void> {\n    await Promise.all([...this.pipelines.values()].map((p) => p.flushNow()));\n  }\n\n  async dispose(): Promise<void> {\n    await this.flushAll();\n    for (const p of this.pipelines.values()) p.dispose();\n    this.pipelines.clear();\n  }\n}\n\n/**\n * Merge a batch of NormalizedMessages (all from the same chat) into a\n * single representative message. Keeps the latest-arrival metadata and\n * unions content / resources / mentions.\n */\nfunction mergeBatch(batch: NormalizedMessage[]): NormalizedMessage {\n  if (batch.length === 1) return batch[0];\n  const last = batch[batch.length - 1];\n\n  const content = batch\n    .map((m) => m.content)\n    .filter((c) => c && c.length > 0)\n    .join('\\n\\n');\n\n  const resources = dedupBy(\n    batch.flatMap((m) => m.resources),\n    (r: ResourceDescriptor) => r.fileKey,\n  );\n  const mentions = dedupBy(\n    batch.flatMap((m) => m.mentions),\n    (m: MentionInfo) => m.openId ?? m.key,\n  );\n\n  return {\n    ...last,\n    content,\n    resources,\n    mentions,\n    mentionAll: batch.some((m) => m.mentionAll),\n    mentionedBot: batch.some((m) => m.mentionedBot),\n  };\n}\n\nfunction dedupBy<T>(items: T[], key: (t: T) => string | undefined): T[] {\n  const seen = new Set<string>();\n  const out: T[] = [];\n  for (const item of items) {\n    const k = key(item);\n    if (k == null) {\n      out.push(item);\n      continue;\n    }\n    if (seen.has(k)) continue;\n    seen.add(k);\n    out.push(item);\n  }\n  return out;\n}\n","import type { NormalizedMessage, RejectEvent, SafetyConfig } from '../types';\n\nexport interface BatchConfig {\n  delayMs: number;\n  longThresholdChars: number;\n  longDelayMs: number;\n  maxMessages: number;\n  maxChars: number;\n  /** Accumulate messages arriving while a flush is in-flight and emit them\n   *  as one batch when it drains. See SafetyConfig.chatQueue.mergeWhileBusy. */\n  mergeWhileBusy: boolean;\n}\n\nexport const DEFAULT_BATCH: BatchConfig = {\n  delayMs: 600,\n  longThresholdChars: 1000,\n  longDelayMs: 2000,\n  maxMessages: 8,\n  maxChars: 4000,\n  mergeWhileBusy: false,\n};\n\nexport const DEFAULT_DEDUP = {\n  ttl: 12 * 3600_000,\n  maxEntries: 5000,\n  sweepIntervalMs: 5 * 60_000,\n  namespace: 'channel:seen',\n} as const;\n\nexport const DEFAULT_STALE_MS = 30 * 60_000;\nexport const DEFAULT_LOCK_TTL_MS = 5 * 60_000;\n\nexport interface BatchedDispatch {\n  message: NormalizedMessage;\n  sourceIds: string[];\n}\n\nexport type OnReject = (evt: RejectEvent) => void;\nexport type OnMessageDispatch = (merged: NormalizedMessage) => Promise<void>;\n\nexport function resolveBatchConfig(cfg?: SafetyConfig): BatchConfig {\n  const t = cfg?.batch?.text ?? {};\n  return {\n    delayMs: t.delayMs ?? DEFAULT_BATCH.delayMs,\n    longThresholdChars: t.longThresholdChars ?? DEFAULT_BATCH.longThresholdChars,\n    longDelayMs: t.longDelayMs ?? DEFAULT_BATCH.longDelayMs,\n    maxMessages: t.maxMessages ?? DEFAULT_BATCH.maxMessages,\n    maxChars: t.maxChars ?? DEFAULT_BATCH.maxChars,\n    mergeWhileBusy: cfg?.chatQueue?.mergeWhileBusy ?? DEFAULT_BATCH.mergeWhileBusy,\n  };\n}\n","import type { Cache } from '@larksuiteoapi/node-sdk';\nimport { DEFAULT_DEDUP } from './types';\n\nexport interface DedupOptions {\n  ttlMs?: number; // default 12h\n  maxMemEntries?: number; // default 5000\n  sweepMs?: number; // default 5min\n  namespace?: string; // default 'channel:seen'\n}\n\n/**\n * Two-tier dedup cache: hot in-memory LRU + injectable long-term Cache.\n * Memory tier serves 99% of queries sub-millisecond; long-term tier\n * survives process restarts when a persistent Cache implementation is\n * provided. Default node-sdk `internalCache` is memory-only — callers\n * wanting cross-restart persistence inject their own Cache (e.g., Redis).\n */\nexport class SeenCache {\n  private memory = new Map<string, number>(); // id → expireAt\n  private sweeper: NodeJS.Timeout;\n  private ttlMs: number;\n  private maxMem: number;\n  private ns: string;\n\n  constructor(\n    private cache: Cache,\n    opts: DedupOptions = {},\n  ) {\n    this.ttlMs = opts.ttlMs ?? DEFAULT_DEDUP.ttl;\n    this.maxMem = opts.maxMemEntries ?? DEFAULT_DEDUP.maxEntries;\n    this.ns = opts.namespace ?? DEFAULT_DEDUP.namespace;\n\n    const sweepMs = opts.sweepMs ?? DEFAULT_DEDUP.sweepIntervalMs;\n    this.sweeper = setInterval(() => this.sweep(), sweepMs);\n    this.sweeper.unref?.();\n  }\n\n  async has(id: string): Promise<boolean> {\n    const now = Date.now();\n    const exp = this.memory.get(id);\n    if (exp && exp > now) {\n      // refresh LRU position\n      this.memory.delete(id);\n      this.memory.set(id, exp);\n      return true;\n    }\n    // fall through to long-term tier\n    const hit = await this.cache.get(id, { namespace: this.ns });\n    if (hit) {\n      this.memory.set(id, now + this.ttlMs);\n      this.evictIfNeeded();\n      return true;\n    }\n    return false;\n  }\n\n  async add(id: string): Promise<void> {\n    const expireAt = Date.now() + this.ttlMs;\n    this.memory.set(id, expireAt);\n    this.evictIfNeeded();\n    // best-effort write to long-term; ignore failures\n    try {\n      await this.cache.set(id, '1', expireAt, { namespace: this.ns });\n    } catch {\n      // tolerated — memory tier remains authoritative during the session\n    }\n  }\n\n  private evictIfNeeded(): void {\n    while (this.memory.size > this.maxMem) {\n      const first = this.memory.keys().next().value;\n      if (first === undefined) break;\n      this.memory.delete(first);\n    }\n  }\n\n  private sweep(): void {\n    const now = Date.now();\n    for (const [k, v] of this.memory) {\n      if (v <= now) this.memory.delete(k);\n    }\n  }\n\n  dispose(): void {\n    clearInterval(this.sweeper);\n    this.memory.clear();\n  }\n}\n","import type { Logger } from '../internal';\nimport type { BotLoopGuardConfig, NormalizedMessage } from '../types';\n\ninterface WindowEntry {\n  messageId: string;\n  time: number;\n}\n\ninterface KeyState {\n  entries: WindowEntry[];\n  warned: boolean;\n}\n\nconst DEFAULTS = {\n  windowMs: 60_000,\n  maxBotMentions: 5,\n  scope: 'chat' as const,\n  onTrip: 'drop' as const,\n};\n\n/** Hard cap on tracked keys so the state map can't grow unbounded. */\nconst MAX_KEYS = 5000;\n\n/**\n * Heuristic guard against two bots @-ing each other forever (opt-in, default\n * off). Only \"another bot @'d me\" messages count; a human message resets the\n * count; when the count reaches the threshold inside a sliding window the key\n * is tripped. `msg.createTime` is the clock, so counting is deterministic\n * (and, being event-supplied, only a best-effort — noted in the spec, not a\n * protocol-level defense).\n */\nexport class LoopGuard {\n  readonly enabled: boolean;\n  readonly onTrip: 'drop' | 'reject';\n  private readonly windowMs: number;\n  private readonly threshold: number;\n  private readonly scope: 'chat' | 'chat+sender';\n  private readonly states = new Map<string, KeyState>();\n\n  constructor(\n    cfg: BotLoopGuardConfig | undefined,\n    private readonly logger: Logger,\n  ) {\n    this.enabled = cfg?.enabled ?? false;\n    this.windowMs = cfg?.windowMs ?? DEFAULTS.windowMs;\n    this.threshold = cfg?.maxBotMentions ?? DEFAULTS.maxBotMentions;\n    this.scope = cfg?.scope ?? DEFAULTS.scope;\n    this.onTrip = cfg?.onTrip ?? DEFAULTS.onTrip;\n  }\n\n  /**\n   * Record a message; return whether its key is now tripped. A human message\n   * resets the key and never trips; messages that aren't \"another bot @'d me\"\n   * don't count. A re-delivered `messageId` already inside the window is\n   * counted once. The first trip of a key emits exactly one warn.\n   */\n  record(msg: NormalizedMessage): boolean {\n    if (!this.enabled) return false;\n    const key = this.keyFor(msg);\n\n    if (msg.senderType === 'user') {\n      this.states.delete(key);\n      return false;\n    }\n    if (!(msg.senderType === 'bot' && msg.mentionedBot)) return false;\n\n    const state = this.states.get(key) ?? { entries: [], warned: false };\n    const cutoff = msg.createTime - this.windowMs;\n    state.entries = state.entries.filter((e) => e.time >= cutoff);\n    if (!state.entries.some((e) => e.messageId === msg.messageId)) {\n      state.entries.push({ messageId: msg.messageId, time: msg.createTime });\n    }\n\n    const tripped = state.entries.length >= this.threshold;\n    if (tripped && !state.warned) {\n      this.logger.warn?.(\n        `channel: botLoopGuard tripped for ${key} — >=${this.threshold} bot @-mentions within ${this.windowMs}ms (onTrip=${this.onTrip})`,\n      );\n      state.warned = true;\n    } else if (!tripped) {\n      // Re-arm the one-time warn once the window has drained below threshold.\n      state.warned = false;\n    }\n\n    this.remember(key, state);\n    return tripped;\n  }\n\n  /** Store the key's state (LRU touch) and cap the number of tracked keys. */\n  private remember(key: string, state: KeyState): void {\n    this.states.delete(key);\n    this.states.set(key, state);\n    while (this.states.size > MAX_KEYS) {\n      const oldest = this.states.keys().next().value;\n      if (oldest === undefined) break;\n      this.states.delete(oldest);\n    }\n  }\n\n  private keyFor(msg: NormalizedMessage): string {\n    return this.scope === 'chat+sender' ? `${msg.chatId}::${msg.senderId}` : msg.chatId;\n  }\n}\n","import type { Logger } from '../internal';\nimport type { BotIdentity, NormalizedMessage, PolicyConfig, RejectReason } from '../types';\n\nexport interface PolicyDecision {\n  allowed: boolean;\n  reason?: RejectReason;\n}\n\nexport class PolicyGate {\n  private cfg: PolicyConfig;\n\n  private bot?: BotIdentity;\n\n  private readonly logger?: Logger;\n\n  constructor(cfg: PolicyConfig | undefined, bot?: BotIdentity, logger?: Logger) {\n    this.cfg = { ...(cfg ?? {}) };\n    this.bot = bot;\n    this.logger = logger;\n    this.warnOnMisconfiguredAllowlists();\n  }\n\n  evaluate(msg: NormalizedMessage): PolicyDecision {\n    if (msg.chatType === 'group') return this.evaluateGroup(msg);\n    return this.evaluateDm(msg);\n  }\n\n  private evaluateGroup(msg: NormalizedMessage): PolicyDecision {\n    const allow = this.cfg.groupAllowlist;\n    if (allow && allow.length > 0 && !allow.includes(msg.chatId)) {\n      return { allowed: false, reason: 'group_not_allowed' };\n    }\n    const requireMention = this.cfg.requireMention ?? true;\n    if (requireMention && !msg.mentionedBot) {\n      return { allowed: false, reason: 'no_mention' };\n    }\n    if (msg.mentionAll && !(this.cfg.respondToMentionAll ?? false)) {\n      return { allowed: false, reason: 'mention_all_blocked' };\n    }\n    return { allowed: true };\n  }\n\n  private evaluateDm(msg: NormalizedMessage): PolicyDecision {\n    const mode = this.cfg.dmMode ?? 'open';\n    if (mode === 'disabled') {\n      return { allowed: false, reason: 'dm_disabled' };\n    }\n    if (mode === 'allowlist') {\n      const allow = this.cfg.dmAllowlist ?? [];\n      if (!allow.includes(msg.senderId)) {\n        return { allowed: false, reason: 'sender_not_allowed' };\n      }\n    }\n    // 'pair' mode is reserved for a future iteration; treat as open for now.\n    return { allowed: true };\n  }\n\n  updateConfig(partial: Partial<PolicyConfig>): void {\n    this.cfg = { ...this.cfg, ...partial };\n    this.warnOnMisconfiguredAllowlists();\n  }\n\n  /**\n   * Flag the most common allowlist misconfiguration: an app id (`cli_…`) in a\n   * list that expects sender ids / chat ids, which silently matches nothing.\n   * Logs only the field name and the single offending value — never the whole\n   * list (no PII / full-table dumps).\n   */\n  private warnOnMisconfiguredAllowlists(): void {\n    this.warnOnCliEntry('dmAllowlist', 'sender ids (ou_/user_id/union_id)', this.cfg.dmAllowlist);\n    this.warnOnCliEntry('groupAllowlist', 'chat ids (oc_)', this.cfg.groupAllowlist);\n  }\n\n  private warnOnCliEntry(field: string, accepts: string, list?: string[]): void {\n    if (!this.logger) return;\n    const offending = list?.find((entry) => entry.startsWith('cli_'));\n    if (!offending) return;\n    this.logger.warn?.(\n      `channel: PolicyConfig.${field} contains an app id (\"${offending}\") — it accepts ${accepts}, not cli_; this entry matches nothing`,\n    );\n  }\n\n  getConfig(): Readonly<PolicyConfig> {\n    return this.cfg;\n  }\n\n  setBotIdentity(bot: BotIdentity): void {\n    this.bot = bot;\n  }\n\n  getBotIdentity(): BotIdentity | undefined {\n    return this.bot;\n  }\n}\n","import { DEFAULT_LOCK_TTL_MS } from './types';\n\n/**\n * Short-TTL in-memory lock to prevent concurrent processing of the same\n * event — complements SeenCache by covering the \"currently in flight\"\n * window, during which the event is not yet committed to SeenCache.\n */\nexport class ProcessingLock {\n  private locks = new Map<string, number>(); // id → expireAt (ms)\n  private sweeper: NodeJS.Timeout;\n\n  constructor(\n    private ttlMs: number = DEFAULT_LOCK_TTL_MS,\n    sweepMs: number = 60_000,\n  ) {\n    this.sweeper = setInterval(() => this.sweep(), sweepMs);\n    this.sweeper.unref?.();\n  }\n\n  /** Returns true if the lock is acquired; false if already held. */\n  acquire(id: string): boolean {\n    const now = Date.now();\n    const exp = this.locks.get(id);\n    if (exp && exp > now) return false;\n    this.locks.set(id, now + this.ttlMs);\n    return true;\n  }\n\n  release(id: string): void {\n    this.locks.delete(id);\n  }\n\n  private sweep(): void {\n    const now = Date.now();\n    for (const [k, v] of this.locks) {\n      if (v <= now) this.locks.delete(k);\n    }\n  }\n\n  dispose(): void {\n    clearInterval(this.sweeper);\n    this.locks.clear();\n  }\n}\n","import { DEFAULT_STALE_MS } from './types';\n\nexport function isStale(createTimeMs: number, windowMs: number = DEFAULT_STALE_MS): boolean {\n  if (!createTimeMs || !Number.isFinite(createTimeMs)) return false;\n  return Date.now() - createTimeMs > windowMs;\n}\n","import type { Cache } from '@larksuiteoapi/node-sdk';\nimport type { Logger } from '../internal';\nimport type {\n  BotIdentity,\n  NormalizedMessage,\n  PolicyConfig,\n  RejectEvent,\n  SafetyConfig,\n} from '../types';\n\nimport { ChatPipelineManager } from './chat-pipeline';\nimport { SeenCache } from './dedup-cache';\nimport { LoopGuard } from './loop-guard';\nimport { PolicyGate } from './policy-gate';\nimport { ProcessingLock } from './processing-lock';\nimport { isStale } from './stale-detector';\nimport {\n  DEFAULT_STALE_MS,\n  type OnMessageDispatch,\n  type OnReject,\n  resolveBatchConfig,\n} from './types';\n\nexport { ChatPipeline, ChatPipelineManager } from './chat-pipeline';\nexport { SeenCache } from './dedup-cache';\nexport { LoopGuard } from './loop-guard';\nexport type { PolicyDecision } from './policy-gate';\nexport { PolicyGate } from './policy-gate';\nexport { ProcessingLock } from './processing-lock';\nexport { isStale } from './stale-detector';\n\nexport interface SafetyPipelineOptions {\n  config?: SafetyConfig;\n  policy?: PolicyConfig;\n  cache: Cache;\n  botIdentity?: BotIdentity;\n  logger: Logger;\n  onReject: OnReject;\n  onMessage: OnMessageDispatch;\n}\n\n/**\n * Pipeline entry facade for the channel's safety layer.\n *\n * Three tiers of protection, each targeting different event shapes:\n *   - pushMessage: full pipeline (stale + dedup + policy + lock + batch + queue)\n *   - pushAction:  dedup + lock + queue — for card button clicks and doc comments\n *   - pushLight:   dedup only — for reactions\n */\nexport class SafetyPipeline {\n  private readonly seenCache: SeenCache;\n  private readonly lock: ProcessingLock;\n  private readonly policy: PolicyGate;\n  private readonly loopGuard: LoopGuard;\n  private readonly manager: ChatPipelineManager;\n  private readonly staleWindow: number;\n  private readonly queueEnabled: boolean;\n\n  private readonly logger: Logger;\n  private readonly onReject: OnReject;\n  private readonly onMessage: OnMessageDispatch;\n\n  constructor(opts: SafetyPipelineOptions) {\n    this.logger = opts.logger;\n    this.onReject = opts.onReject;\n    this.onMessage = opts.onMessage;\n\n    this.staleWindow = opts.config?.staleMessageWindowMs ?? DEFAULT_STALE_MS;\n    this.queueEnabled = opts.config?.chatQueue?.enabled ?? true;\n\n    this.seenCache = new SeenCache(opts.cache, {\n      ttlMs: opts.config?.dedup?.ttl,\n      maxMemEntries: opts.config?.dedup?.maxEntries,\n      sweepMs: opts.config?.dedup?.sweepIntervalMs,\n    });\n    this.lock = new ProcessingLock();\n    this.policy = new PolicyGate(opts.policy, opts.botIdentity, opts.logger);\n    this.loopGuard = new LoopGuard(opts.policy?.botLoopGuard, opts.logger);\n    this.manager = new ChatPipelineManager(resolveBatchConfig(opts.config));\n  }\n\n  // ─── tier 1: full pipeline for IM messages ─────────────\n\n  async pushMessage(msg: NormalizedMessage): Promise<void> {\n    if (isStale(msg.createTime, this.staleWindow)) {\n      this.logger.debug?.(`safety: drop stale message ${msg.messageId}`);\n      return;\n    }\n    if (await this.seenCache.has(msg.messageId)) {\n      this.logger.debug?.(`safety: drop duplicate message ${msg.messageId}`);\n      return;\n    }\n    const decision = this.policy.evaluate(msg);\n    if (!decision.allowed) {\n      this.onReject({\n        messageId: msg.messageId,\n        chatId: msg.chatId,\n        senderId: msg.senderId,\n        reason: decision.reason ?? 'group_not_allowed',\n      } as RejectEvent);\n      return;\n    }\n\n    // Bot ping-pong guard (opt-in). Runs after dedup + policy so a re-delivery\n    // can't inflate the count and a policy-rejected message never counts; a\n    // human message (handled inside record) resets the window.\n    if (this.loopGuard.enabled && this.loopGuard.record(msg)) {\n      if (this.loopGuard.onTrip === 'reject') {\n        this.onReject({\n          messageId: msg.messageId,\n          chatId: msg.chatId,\n          senderId: msg.senderId,\n          reason: 'bot_loop',\n        } as RejectEvent);\n      } else {\n        this.logger.debug?.(`safety: drop bot-loop message ${msg.messageId}`);\n      }\n      return;\n    }\n\n    if (!this.lock.acquire(msg.messageId)) {\n      this.logger.debug?.(`safety: drop in-flight message ${msg.messageId}`);\n      return;\n    }\n\n    const dispatchHandler = async (batch: { message: NormalizedMessage; sourceIds: string[] }) => {\n      try {\n        await this.onMessage(batch.message);\n      } catch (e) {\n        this.logger.error?.(`safety: message handler threw`, e);\n      } finally {\n        for (const id of batch.sourceIds) {\n          try {\n            await this.seenCache.add(id);\n          } catch {\n            /* best effort */\n          }\n          this.lock.release(id);\n        }\n      }\n    };\n\n    if (this.queueEnabled) {\n      this.manager.push(msg.chatId, msg, dispatchHandler);\n    } else {\n      // queueing disabled: fire-and-forget, no batch either\n      void dispatchHandler({ message: msg, sourceIds: [msg.messageId] });\n    }\n  }\n\n  // ─── tier 2: dedup + lock + queue for cardAction & comment ─────\n\n  async pushAction<T>(\n    eventId: string,\n    queueScope: string,\n    handler: () => Promise<T>,\n  ): Promise<T | undefined> {\n    if (await this.seenCache.has(eventId)) {\n      this.logger.debug?.(`safety: drop duplicate action ${eventId}`);\n      return undefined;\n    }\n    if (!this.lock.acquire(eventId)) {\n      this.logger.debug?.(`safety: drop in-flight action ${eventId}`);\n      return undefined;\n    }\n\n    // The handler's return value is propagated back out so card-action\n    // callback responses (e.g. a toast) can reach Feishu. A throwing handler\n    // is logged and yields `undefined` (no response) — the dedup mark and\n    // lock release in `finally` run regardless.\n    const task = async (): Promise<T | undefined> => {\n      try {\n        return await handler();\n      } catch (e) {\n        this.logger.error?.(`safety: action handler threw`, e);\n        return undefined;\n      } finally {\n        try {\n          await this.seenCache.add(eventId);\n        } catch {\n          /* best effort */\n        }\n        this.lock.release(eventId);\n      }\n    };\n\n    if (this.queueEnabled) {\n      return this.manager.run(queueScope, task);\n    }\n    return task();\n  }\n\n  // ─── tier 3: dedup only (reactions) ────────────────────\n\n  async pushLight(eventId: string, handler: () => void | Promise<void>): Promise<void> {\n    if (await this.seenCache.has(eventId)) return;\n    await this.seenCache.add(eventId);\n    try {\n      await handler();\n    } catch (e) {\n      this.logger.warn?.(`safety: light handler threw`, e);\n    }\n  }\n\n  // ─── runtime config ────────────────────────────────────\n\n  updatePolicy(partial: Partial<PolicyConfig>): void {\n    this.policy.updateConfig(partial);\n  }\n\n  getPolicy(): Readonly<PolicyConfig> {\n    return this.policy.getConfig();\n  }\n\n  setBotIdentity(bot: BotIdentity): void {\n    this.policy.setBotIdentity(bot);\n  }\n\n  async dispose(): Promise<void> {\n    await this.manager.dispose();\n    this.seenCache.dispose();\n    this.lock.dispose();\n  }\n}\n","import { createWriteStream } from 'node:fs';\nimport { stat, writeFile } from 'node:fs/promises';\nimport { pipeline } from 'node:stream/promises';\nimport {\n  Client,\n  Domain,\n  defaultHttpInstance,\n  EventDispatcher,\n  LoggerLevel,\n  WSClient,\n} from '@larksuiteoapi/node-sdk';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { ChatMemberCache } from './chat-member-cache';\nimport { ChatModeCache } from './chat-mode-cache';\nimport { CommentSurface } from './comments';\nimport {\n  defaultLogger,\n  internalCache,\n  type Logger,\n  LoggerProxy,\n  type WSConnectionStatus,\n} from './internal';\nimport { type KeepaliveHandle, startKeepalive } from './keepalive';\nimport type { ApiMessageItem, RawMessageEvent } from './normalize';\nimport {\n  normalize,\n  normalizeBotAdded,\n  normalizeCardAction,\n  normalizeComment,\n  normalizeReaction,\n} from './normalize';\nimport { OutboundSender } from './outbound';\nimport { classifyError } from './outbound/errors';\nimport { resolveMentionsInText, resolveNameMentions } from './outbound/markdown/resolve-mentions';\nimport { SafetyPipeline } from './safety';\nimport {\n  type AppInfo,\n  type BotIdentity,\n  type ChatInfo,\n  type ChatMember,\n  type ChatSummary,\n  type CreateChatOptions,\n  type EventMap,\n  type EventName,\n  type IdType,\n  LarkChannelError,\n  type LarkChannelOptions,\n  type MentionInfo,\n  type NormalizedMessage,\n  type PolicyConfig,\n  type ResourceType,\n  type SendInput,\n  type SendOptions,\n  type SendResult,\n  type StreamInput,\n} from './types';\n\ntype Unsubscribe = () => void;\n\n/** Options for {@link LarkChannel.getChatMembers}. */\ninterface GetChatMembersOptions {\n  pageSize?: number;\n  maxPages?: number;\n  idType?: IdType;\n  /** Skip the roster cache and refetch. */\n  force?: boolean;\n}\n\n/** One page of the raw `im.v1.chatMembers.get` response we consume. */\ninterface RawChatMembersPage {\n  items?: Array<{\n    member_id?: string;\n    member_id_type?: string;\n    name?: string;\n    tenant_key?: string;\n  }>;\n  has_more?: boolean;\n  page_token?: string;\n}\n\n/** One item of the raw `.../members/bots` response. */\ninterface RawBotItem {\n  bot_id?: string;\n  bot_name?: string;\n}\n\nexport class LarkChannel {\n  readonly rawClient: Client;\n\n  rawWsClient?: WSClient;\n\n  botIdentity?: BotIdentity;\n\n  /** Cloud-doc comment surface: fetch / reply / reactions with quirk fallbacks. */\n  readonly comments: CommentSurface;\n\n  private readonly opts: LarkChannelOptions;\n\n  private readonly logger: Logger;\n\n  private readonly dispatcher: EventDispatcher;\n\n  private readonly handlers: Partial<EventMap> = {};\n\n  private connectPromise?: Promise<void>;\n\n  private connected = false;\n\n  private readonly sender: OutboundSender;\n\n  private readonly safety: SafetyPipeline;\n\n  private readonly chatModeCache = new ChatModeCache();\n\n  private readonly chatMemberCache = new ChatMemberCache();\n\n  private keepaliveHandle?: KeepaliveHandle;\n\n  private proxyAgent?: HttpsProxyAgent<string>;\n\n  constructor(opts: LarkChannelOptions) {\n    this.opts = opts;\n    this.logger = new LoggerProxy(\n      opts.loggerLevel ?? LoggerLevel.info,\n      opts.logger ?? defaultLogger,\n    );\n\n    this.rawClient = new Client({\n      appId: opts.appId,\n      appSecret: opts.appSecret,\n      domain: opts.domain ?? Domain.Feishu,\n      cache: opts.cache,\n      httpInstance: opts.httpInstance,\n      logger: opts.logger,\n      loggerLevel: opts.loggerLevel,\n      source: opts.source,\n      extraUaTags: ['channel'],\n    });\n\n    this.dispatcher = new EventDispatcher({\n      verificationToken: opts.webhook?.verificationToken,\n      encryptKey: opts.webhook?.encryptKey,\n      cache: opts.cache,\n      logger: opts.logger,\n      loggerLevel: opts.loggerLevel,\n    });\n\n    this.sender = new OutboundSender(this.rawClient, opts.outbound ?? {}, this.logger);\n\n    this.comments = new CommentSurface(this.rawClient, this.logger);\n\n    this.configureHttp();\n\n    this.safety = new SafetyPipeline({\n      config: opts.safety,\n      policy: opts.policy,\n      cache: opts.cache ?? internalCache,\n      logger: this.logger,\n      onReject: (evt) => {\n        this.handlers.reject?.(evt);\n      },\n      onMessage: async (merged) => {\n        const handler = this.handlers.message;\n        if (handler) await handler(merged);\n      },\n    });\n  }\n\n  // ─── lifecycle ──────────────────────────────────────────\n\n  async connect(): Promise<void> {\n    if (this.connectPromise) return this.connectPromise;\n    this.connectPromise = this.doConnect().catch((err) => {\n      this.connectPromise = undefined;\n      throw err;\n    });\n    return this.connectPromise;\n  }\n\n  private async doConnect(): Promise<void> {\n    this.botIdentity = await this.fetchBotIdentity();\n    this.safety.setBotIdentity(this.botIdentity);\n    this.registerDispatcherHandlers();\n\n    const transport = this.opts.transport ?? 'websocket';\n    if (transport === 'websocket') {\n      await this.connectWebSocket(15000);\n      this.startKeepaliveIfEnabled();\n    }\n    // webhook transport wiring is external: user plugs this.dispatcher into\n    // their HTTP handler via the existing adaptor modules.\n    this.connected = true;\n  }\n\n  private startKeepaliveIfEnabled(): void {\n    if (!this.opts.keepalive?.enabled || this.keepaliveHandle) return;\n    this.keepaliveHandle = startKeepalive({\n      getConnectionStatus: () => this.getConnectionStatus(),\n      domain: String(this.opts.domain ?? Domain.Feishu),\n      forceReconnect: () => this.forceReconnect(),\n      onUnrecoverable: this.opts.keepalive.onUnrecoverable,\n      logger: this.logger,\n      intervalMs: this.opts.keepalive.intervalMs,\n    });\n  }\n\n  /**\n   * Tear down the current WebSocket and re-establish it. Used by the\n   * keepalive watchdog when the connection looks stuck. Throws if the fresh\n   * handshake fails, so keepalive can surface it via `onUnrecoverable`.\n   */\n  private async forceReconnect(): Promise<void> {\n    try {\n      this.rawWsClient?.close({ force: true });\n    } catch {\n      /* best effort */\n    }\n    this.rawWsClient = undefined;\n    await this.connectWebSocket(this.opts.handshakeTimeoutMs ?? 15000);\n  }\n\n  /**\n   * Apply a per-request timeout and/or proxy to node-sdk's shared\n   * `defaultHttpInstance` (a typed `AxiosInstance` — `defaults` is visible,\n   * no cast needed). Only runs when the caller opted in, and only when no\n   * custom `httpInstance` was supplied: a caller who brings their own HTTP\n   * instance owns its configuration, and we don't mutate a process-wide\n   * singleton behind their back.\n   */\n  private configureHttp(): void {\n    if (this.opts.httpInstance) return;\n    const timeout = this.opts.httpTimeoutMs;\n    const proxyAgent = this.opts.respectProxyEnv ? this.proxyAgentFromEnv() : undefined;\n    if (timeout == null && !proxyAgent) return;\n    if (timeout != null) defaultHttpInstance.defaults.timeout = timeout;\n    if (proxyAgent) {\n      defaultHttpInstance.defaults.httpsAgent = proxyAgent;\n      defaultHttpInstance.defaults.httpAgent = proxyAgent;\n    }\n  }\n\n  /**\n   * Resolve the Node http(s) agent for the WebSocket transport: an explicit\n   * `agent` option wins; otherwise, when `respectProxyEnv` is set, build one\n   * from `HTTPS_PROXY` / `HTTP_PROXY`.\n   */\n  private resolveWsAgent(): unknown {\n    if (this.opts.agent) return this.opts.agent;\n    if (!this.opts.respectProxyEnv) return undefined;\n    return this.proxyAgentFromEnv();\n  }\n\n  /** Lazily build (and cache) a proxy agent from the proxy env vars. Shared\n   *  by the WebSocket transport and the REST HTTP instance. */\n  private proxyAgentFromEnv(): HttpsProxyAgent<string> | undefined {\n    if (this.proxyAgent) return this.proxyAgent;\n    const proxyUrl =\n      process.env.HTTPS_PROXY ??\n      process.env.https_proxy ??\n      process.env.HTTP_PROXY ??\n      process.env.http_proxy;\n    if (!proxyUrl) return undefined;\n    this.proxyAgent = new HttpsProxyAgent(proxyUrl);\n    this.logger.info?.('channel: proxy detected', { proxy: redactProxyUrl(proxyUrl) });\n    return this.proxyAgent;\n  }\n\n  /**\n   * Construct the underlying WSClient and wait for its `onReady` callback —\n   * so `connect()` only resolves after the first WebSocket handshake\n   * actually succeeds. Rejects on `onError` or if the handshake doesn't\n   * complete within `timeoutMs`.\n   *\n   * Also wires `onReconnecting` / `onReconnected` callbacks to emit the\n   * corresponding public events.\n   */\n  private connectWebSocket(timeoutMs: number): Promise<void> {\n    return new Promise<void>((resolve, reject) => {\n      let settled = false;\n      const timer = setTimeout(() => {\n        if (settled) return;\n        settled = true;\n        reject(\n          new LarkChannelError(\n            'not_connected',\n            `WebSocket handshake did not complete within ${timeoutMs}ms`,\n          ),\n        );\n      }, timeoutMs);\n\n      this.rawWsClient = new WSClient({\n        appId: this.opts.appId,\n        appSecret: this.opts.appSecret,\n        domain: this.opts.domain ?? Domain.Feishu,\n        logger: this.opts.logger,\n        loggerLevel: this.opts.loggerLevel,\n        httpInstance: this.opts.httpInstance,\n        autoReconnect: true,\n        source: this.opts.source,\n        extraUaTags: ['channel'],\n        agent: this.resolveWsAgent(),\n        wsConfig: this.opts.wsConfig,\n        handshakeTimeoutMs: this.opts.handshakeTimeoutMs,\n        onReady: () => {\n          if (settled) return;\n          settled = true;\n          clearTimeout(timer);\n          resolve();\n        },\n        onError: (err) => {\n          if (settled) return;\n          settled = true;\n          clearTimeout(timer);\n          reject(\n            new LarkChannelError('not_connected', `WebSocket connect failed: ${err.message}`, {\n              cause: err,\n            }),\n          );\n        },\n        onReconnecting: () => this.handlers.reconnecting?.(),\n        onReconnected: () => this.handlers.reconnected?.(),\n      });\n      this.rawWsClient.start({ eventDispatcher: this.dispatcher });\n    });\n  }\n\n  async disconnect(): Promise<void> {\n    if (!this.connected) return;\n    this.keepaliveHandle?.stop();\n    this.keepaliveHandle = undefined;\n    try {\n      this.rawWsClient?.close({});\n    } catch {\n      /* best effort */\n    }\n    try {\n      await this.safety.dispose();\n    } catch {\n      /* best effort */\n    }\n    this.connected = false;\n    this.connectPromise = undefined;\n  }\n\n  /**\n   * Snapshot of the WebSocket lifecycle (state, last/next connect times,\n   * current reconnect attempts). Returns `undefined` when the channel\n   * hasn't initialized a WSClient yet (e.g., before `connect()` is called\n   * or under the webhook transport).\n   */\n  getConnectionStatus(): WSConnectionStatus | undefined {\n    return this.rawWsClient?.getConnectionStatus();\n  }\n\n  /**\n   * This bot's own identity ({@link BotIdentity}) — useful to inline into an\n   * agent's system prompt (\"you are @… / your open_id is …\") so it can tell\n   * itself apart from other bots and decide whom to reply to. Resolved during\n   * {@link connect}; throws `LarkChannelError('not_connected')` if called\n   * before then, rather than returning `undefined`, so callers don't silently\n   * build a prompt with a missing identity.\n   */\n  getBotIdentity(): BotIdentity {\n    if (!this.botIdentity) {\n      throw new LarkChannelError(\n        'not_connected',\n        'bot identity not resolved yet — call connect() first',\n      );\n    }\n    return this.botIdentity;\n  }\n\n  // ─── event subscription ────────────────────────────────\n\n  on<K extends EventName>(name: K, handler: EventMap[K]): Unsubscribe;\n\n  on(handlers: Partial<EventMap>): Unsubscribe;\n\n  on(nameOrMap: EventName | Partial<EventMap>, handler?: EventMap[EventName]): Unsubscribe {\n    if (typeof nameOrMap === 'string') {\n      return this.attachSingle(nameOrMap, handler as EventMap[EventName]);\n    }\n    const unsubs: Unsubscribe[] = [];\n    (Object.keys(nameOrMap) as EventName[]).forEach((k) => {\n      const fn = nameOrMap[k];\n      if (fn) unsubs.push(this.attachSingle(k, fn as EventMap[EventName]));\n    });\n    return () => {\n      unsubs.forEach((u) => {\n        u();\n      });\n    };\n  }\n\n  private attachSingle<K extends EventName>(name: K, handler: EventMap[K]): Unsubscribe {\n    if (this.handlers[name]) {\n      this.logger.warn(`channel: handler for \"${name}\" is being overwritten`);\n    }\n    this.handlers[name] = handler;\n    return () => {\n      if (this.handlers[name] === handler) delete this.handlers[name];\n    };\n  }\n\n  // ─── outbound ──────────────────────────────────────────\n\n  async send(to: string, input: SendInput, opts?: SendOptions): Promise<SendResult> {\n    const resolved = this.resolveOutboundMentions(to, input, opts);\n    return this.sender.send(to, resolved.input, resolved.opts);\n  }\n\n  async stream(to: string, input: StreamInput, opts?: SendOptions): Promise<SendResult> {\n    return this.sender.stream(to, input, opts);\n  }\n\n  /**\n   * Reply to a received message: defaults `replyTo` to `msg.messageId` and,\n   * when the trigger is inside a topic thread (`msg.threadId` present), keeps\n   * the reply in that thread — fixing the common \"replied to the wrong place /\n   * fell out of the topic\" mistake of computing the reply target by hand.\n   * `opts` overrides either default. Semantically a {@link send}; streaming\n   * replies still use `stream(to, input, { replyTo })`.\n   */\n  async reply(\n    msg: Pick<NormalizedMessage, 'chatId' | 'messageId' | 'threadId'>,\n    input: SendInput,\n    opts?: SendOptions,\n  ): Promise<SendResult> {\n    return this.send(msg.chatId, input, {\n      ...opts,\n      replyTo: opts?.replyTo ?? msg.messageId,\n      replyInThread: opts?.replyInThread ?? Boolean(msg.threadId),\n    });\n  }\n\n  /**\n   * Resolve \"@name\" into real mentions against the target chat's roster before\n   * sending: fill `openId` on name-only structured mentions, and — when\n   * `resolveMentionsInText` is set — rewrite `@name` tokens in a text/markdown\n   * body. Both no-ops when there is nothing to resolve, so the default send\n   * path is untouched.\n   */\n  private resolveOutboundMentions(\n    to: string,\n    input: SendInput,\n    opts?: SendOptions,\n  ): { input: SendInput; opts?: SendOptions } {\n    if (!opts) return { input };\n    const lookup = (name: string) => this.chatMemberCache.resolveOpenId(to, name);\n\n    let nextOpts = opts;\n    if (opts.mentions?.length) {\n      nextOpts = { ...opts, mentions: resolveNameMentions(opts.mentions, lookup) };\n    }\n\n    let nextInput = input;\n    if (opts.resolveMentionsInText) {\n      if ('text' in input)\n        nextInput = { ...input, text: resolveMentionsInText(input.text, lookup) };\n      else if ('markdown' in input)\n        nextInput = { ...input, markdown: resolveMentionsInText(input.markdown, lookup) };\n    }\n\n    return { input: nextInput, opts: nextOpts };\n  }\n\n  // ─── low-level ─────────────────────────────────────────\n\n  async updateCard(messageId: string, card: object): Promise<void> {\n    await this.sender.patchCard(messageId, card);\n  }\n\n  /**\n   * Create a standalone CardKit 2.0 card entity (`cardkit.v1.card.create`) and\n   * return its `card_id`. The card isn't attached to any message yet — send a\n   * message that references it via `channel.send(to, { cardId })`, then drive\n   * it with {@link updateCardById}. This is the managed-card lifecycle: one\n   * entity, many in-place updates, decoupled from the message that displays it.\n   */\n  async createCard(cardJson: object): Promise<{ cardId: string }> {\n    const cardId = await this.sender.createCardInstance(cardJson);\n    return { cardId };\n  }\n\n  /**\n   * Full-content update of a card entity by `card_id` (`cardkit.v1.card.update`).\n   * `sequence` must strictly increase across calls for the same card — Feishu\n   * rejects stale/out-of-order sequences so a slow update can't overwrite a\n   * newer one. Unlike {@link updateCard} (which targets a message_id), this\n   * updates the shared entity, so every message referencing the card_id\n   * re-renders.\n   */\n  async updateCardById(cardId: string, cardJson: object, sequence: number): Promise<void> {\n    await this.sender.updateCardFull(cardId, cardJson, sequence);\n  }\n\n  /**\n   * Edit an already-sent message's text/post content. Uses `im.v1.message.update`\n   * which (per Feishu docs) only supports editing text and rich-text (post)\n   * messages. For cards, use {@link updateCard} instead — a wrong attempt to\n   * use this on a card would hit the same API and fail with a clearer\n   * Feishu-side error.\n   */\n  async editMessage(messageId: string, text: string): Promise<void> {\n    await this.rawClient.im.v1.message.update({\n      path: { message_id: messageId },\n      data: {\n        msg_type: 'text',\n        content: JSON.stringify({ text }),\n      } as never,\n    });\n  }\n\n  async recallMessage(messageId: string): Promise<void> {\n    await this.rawClient.im.v1.message.delete({\n      path: { message_id: messageId },\n    });\n  }\n\n  /**\n   * Add an emoji reaction to a message. Returns the `reaction_id` Feishu\n   * assigned — stash it if you want to {@link removeReaction} later,\n   * since the raw `im.message.reaction.*_v1` events don't carry the id.\n   * Only the bot's own reactions can be removed.\n   */\n  async addReaction(messageId: string, emojiType: string): Promise<string> {\n    const r = await this.rawClient.im.v1.messageReaction.create({\n      path: { message_id: messageId },\n      data: { reaction_type: { emoji_type: emojiType } } as never,\n    });\n    const rid =\n      (r as { data?: { reaction_id?: string } } | null)?.data?.reaction_id ??\n      (r as { reaction_id?: string } | null)?.reaction_id;\n    if (!rid) {\n      throw new LarkChannelError('unknown', 'messageReaction.create returned no reaction_id');\n    }\n    return rid;\n  }\n\n  /**\n   * Remove a reaction by its `reaction_id` (the value returned from\n   * {@link addReaction}). Only the bot's own reactions can be removed —\n   * removing a user-added reaction will fail with a Feishu permission\n   * error.\n   */\n  async removeReaction(messageId: string, reactionId: string): Promise<void> {\n    await this.rawClient.im.v1.messageReaction.delete({\n      path: { message_id: messageId, reaction_id: reactionId },\n    });\n  }\n\n  /**\n   * Convenience: remove the bot's reaction on `messageId` matching\n   * `emojiType`, without needing the `reaction_id`. Lists the message's\n   * reactions filtered by emoji, picks the one added by this bot\n   * (operator_type === 'app'), and deletes it. Returns `true` if a\n   * matching reaction was found and deleted, `false` otherwise (including\n   * the case where the bot never added that emoji).\n   */\n  async removeReactionByEmoji(messageId: string, emojiType: string): Promise<boolean> {\n    const r = await this.rawClient.im.v1.messageReaction.list({\n      path: { message_id: messageId },\n      params: { reaction_type: emojiType, page_size: 50 } as never,\n    });\n    const items =\n      (\n        r as {\n          data?: {\n            items?: Array<{\n              reaction_id?: string;\n              operator?: { operator_type?: 'app' | 'user' };\n            }>;\n          };\n        } | null\n      )?.data?.items ?? [];\n    const mine = items.find((it) => it.operator?.operator_type === 'app');\n    if (!mine?.reaction_id) return false;\n    await this.removeReaction(messageId, mine.reaction_id);\n    return true;\n  }\n\n  /**\n   * Download a resource (image / file / audio / video / sticker) carried by a\n   * **received** message. Feishu serves message resources via\n   * `im.v1.messageResource.get`, which needs both the owning `messageId` and\n   * the resource's `fileKey` — the `im/v1/images|files/:key` endpoints only\n   * work for media the app itself uploaded and return 400 for received media.\n   *\n   * `type` is `'image'` for image resources, `'file'` for everything else\n   * (file / audio / video / sticker) — matching `ResourceDescriptor.type`.\n   */\n  async downloadResource(messageId: string, fileKey: string, type: ResourceType): Promise<Buffer> {\n    const { buffer } = await this.downloadResourceWithMeta(messageId, fileKey, type);\n    return buffer;\n  }\n\n  /**\n   * Like {@link downloadResource}, but also returns the server's response\n   * `content-type` (when present). Feishu's `im.v1.messageResource.get`\n   * carries the resource's real MIME in the response headers — needed to pick\n   * an accurate file extension. `contentType` is the media type with any\n   * parameters (e.g. `; charset=...`) stripped, or `undefined` when the header\n   * is absent (e.g. a defensive raw-`Buffer` response). Callers should fall\n   * back to a per-kind default in that case.\n   */\n  async downloadResourceWithMeta(\n    messageId: string,\n    fileKey: string,\n    type: ResourceType,\n  ): Promise<{ buffer: Buffer; contentType?: string }> {\n    const r = await this.rawClient.im.v1.messageResource.get({\n      path: { message_id: messageId, file_key: fileKey },\n      params: { type },\n    });\n    const buffer = await bufferFromStream(r as unknown);\n    return { buffer, contentType: extractContentType(r as unknown) };\n  }\n\n  /**\n   * Stream a message resource straight to `destPath` without ever holding the\n   * whole payload in memory — the HTTP response is `pipe`d to the file, so a\n   * 100 MB attachment costs only stream-buffer overhead, not 100 MB of JS\n   * heap. Prefer this over {@link downloadResource} /\n   * {@link downloadResourceWithMeta} whenever the bytes are headed for disk\n   * (e.g. a size-limited attachment cache): those materialize a full `Buffer`\n   * via `Buffer.concat`, which can OOM the process when several large\n   * downloads run concurrently.\n   *\n   * Returns the server `content-type` (params stripped; `undefined` when\n   * absent) for MIME/extension detection, and the number of bytes written.\n   * The parent directory of `destPath` must already exist.\n   */\n  async downloadResourceToFile(\n    messageId: string,\n    fileKey: string,\n    type: ResourceType,\n    destPath: string,\n  ): Promise<{ contentType?: string; bytesWritten: number }> {\n    const r = await this.rawClient.im.v1.messageResource.get({\n      path: { message_id: messageId, file_key: fileKey },\n      params: { type },\n    });\n    const contentType = extractContentType(r as unknown);\n    const bytesWritten = await streamToFile(r as unknown, destPath);\n    return { contentType, bytesWritten };\n  }\n\n  /**\n   * Create a group chat (`im.v1.chat.create`) and return its `chat_id`.\n   * `inviteUserIds` seeds the membership; the ids are interpreted per\n   * `userIdType` (default `'open_id'`). Requires the `im:chat` scope.\n   */\n  async createChat(opts: CreateChatOptions): Promise<{ chatId: string }> {\n    const r = await this.rawClient.im.v1.chat.create({\n      params: { user_id_type: opts.userIdType ?? 'open_id' },\n      data: {\n        name: opts.name,\n        description: opts.description,\n        chat_mode: opts.chatMode ?? 'group',\n        chat_type: opts.chatType ?? 'private',\n        user_id_list: opts.inviteUserIds,\n      } as never,\n    });\n    const chatId = (r as { data?: { chat_id?: string } }).data?.chat_id;\n    if (!chatId) {\n      throw new LarkChannelError('unknown', 'im.v1.chat.create returned no chat_id');\n    }\n    return { chatId };\n  }\n\n  /**\n   * List the chats this bot is a member of (`im.v1.chat.list`), following\n   * pagination automatically. `pageSize` is clamped to Feishu's max of 100;\n   * `maxPages` caps how many pages are fetched (default 10) so an account in\n   * thousands of chats can't spin forever. Returns `{ id, name }` per chat.\n   */\n  async listChats(opts?: { pageSize?: number; maxPages?: number }): Promise<ChatSummary[]> {\n    const pageSize = Math.min(Math.max(opts?.pageSize ?? 100, 1), 100);\n    const maxPages = opts?.maxPages ?? 10;\n    const out: ChatSummary[] = [];\n    let pageToken: string | undefined;\n    for (let page = 0; page < maxPages; page++) {\n      const r = (await this.rawClient.im.v1.chat.list({\n        params: { page_size: pageSize, page_token: pageToken },\n      })) as {\n        data?: {\n          items?: Array<{ chat_id?: string; name?: string }>;\n          has_more?: boolean;\n          page_token?: string;\n        };\n      };\n      const d = r?.data;\n      for (const it of d?.items ?? []) {\n        if (it.chat_id) out.push({ id: it.chat_id, name: it.name ?? '' });\n      }\n      if (!d?.has_more || !d.page_token) break;\n      pageToken = d.page_token;\n    }\n    return out;\n  }\n\n  /**\n   * Fetch this app's own metadata (`application.v6.application.get`) — the\n   * `app_id` is the one the channel was constructed with, so callers don't\n   * pass it. Primarily used to resolve the app owner/admin (`ownerId`) for\n   * access control. Requires the application-info scope.\n   */\n  async getAppInfo(opts?: {\n    lang?: 'zh_cn' | 'en_us' | 'ja_jp';\n    userIdType?: 'open_id' | 'user_id' | 'union_id';\n  }): Promise<AppInfo> {\n    const r = await this.rawClient.application.v6.application.get({\n      path: { app_id: this.opts.appId },\n      params: {\n        lang: opts?.lang ?? 'zh_cn',\n        user_id_type: opts?.userIdType ?? 'open_id',\n      },\n    });\n    const app = (r as { data?: { app?: { owner?: { owner_id?: string }; app_name?: string } } })\n      .data?.app;\n    return { ownerId: app?.owner?.owner_id, appName: app?.app_name };\n  }\n\n  async getChatInfo(chatId: string): Promise<ChatInfo> {\n    const r = await this.rawClient.im.v1.chat.get({\n      path: { chat_id: chatId },\n    });\n    const d = (r as { data?: Record<string, unknown> }).data ?? {};\n    return {\n      chatId,\n      name: d.name as string | undefined,\n      description: d.description as string | undefined,\n      chatType: (d.chat_mode as 'p2p' | 'group') ?? 'group',\n      ownerId: d.owner_id as string | undefined,\n      memberCount: d.user_count as number | undefined,\n    };\n  }\n\n  /**\n   * Fetch the chat's mode via `im.v1.chat.get`. Returns one of:\n   *   - 'p2p'   — direct (1:1) chat\n   *   - 'group' — ordinary group\n   *   - 'topic' — topic group\n   *\n   * Unknown / missing values fall back to 'group' for consistency with\n   * {@link getChatInfo}. The underlying API call is not cached — chat\n   * mode rarely changes within a chat's lifetime, so callers that read\n   * this on every inbound message should keep their own cache keyed by\n   * `chatId`.\n   *\n   * Throws on API failure (network, permission, invalid chatId) so the\n   * caller can decide how to handle it; silently defaulting would hide\n   * real problems.\n   */\n  async getChatMode(chatId: string): Promise<'p2p' | 'group' | 'topic'> {\n    const r = await this.rawClient.im.v1.chat.get({\n      path: { chat_id: chatId },\n    });\n    const mode = (r as { data?: { chat_mode?: string } }).data?.chat_mode;\n    if (mode === 'p2p') return 'p2p';\n    if (mode === 'topic') return 'topic';\n    return 'group';\n  }\n\n  /**\n   * List a chat's members (`im.v1.chatMembers.get`), following pagination.\n   * Returns **users only** — Feishu's chat-members API filters bots out, so\n   * `isBot` is never `true` here. Use {@link getChatBots} for the bots.\n   * `pageSize` is clamped to Feishu's max of 100; `maxPages` (default 10) caps\n   * paging. Results are cached per chat and reused by `senderName` resolution\n   * and \"@name → open_id\"; a second call hits the cache and `force` bypasses\n   * it. A `resolveChatMembers` option, if provided, overrides the API.\n   * Throws {@link LarkChannelError} on API failure.\n   */\n  async getChatMembers(chatId: string, opts?: GetChatMembersOptions): Promise<ChatMember[]> {\n    if (!opts?.force) {\n      const cached = this.chatMemberCache.getMembers(chatId);\n      if (cached) return cached;\n    }\n    const members = await this.fetchChatMembers(chatId, opts);\n    this.chatMemberCache.setMembers(chatId, members, 'api');\n    return members;\n  }\n\n  private async fetchChatMembers(\n    chatId: string,\n    opts?: GetChatMembersOptions,\n  ): Promise<ChatMember[]> {\n    const fromHook = await this.opts.resolveChatMembers?.(chatId);\n    if (fromHook) return fromHook;\n\n    const idType = opts?.idType ?? 'open_id';\n    const pageSize = Math.min(Math.max(opts?.pageSize ?? 100, 1), 100);\n    const maxPages = opts?.maxPages ?? 10;\n    const out: ChatMember[] = [];\n    let pageToken: string | undefined;\n    try {\n      for (let page = 0; page < maxPages; page++) {\n        const r = (await this.rawClient.im.v1.chatMembers.get({\n          path: { chat_id: chatId },\n          params: { member_id_type: idType, page_size: pageSize, page_token: pageToken },\n        } as never)) as { data?: RawChatMembersPage };\n        const d = r?.data;\n        for (const it of d?.items ?? []) {\n          if (!it.member_id) continue;\n          out.push({\n            id: it.member_id,\n            idType: (it.member_id_type as IdType) ?? idType,\n            name: it.name,\n            tenantKey: it.tenant_key,\n            isBot: false,\n          });\n        }\n        if (!d?.has_more || !d.page_token) break;\n        pageToken = d.page_token;\n      }\n    } catch (e) {\n      throw classifyError(e, { to: chatId });\n    }\n    return out;\n  }\n\n  /**\n   * List the **bots** in a chat (`GET .../members/bots`) — the companion to\n   * {@link getChatMembers}, which returns users only (Feishu filters bots from\n   * that list). Returns {@link ChatMember}s with `isBot: true`, and seeds them\n   * into the roster so another bot can be `@`-ed by name **without** having\n   * appeared in an inbound mention first. Cached per chat like\n   * {@link getChatMembers} (`force` bypasses). Throws {@link LarkChannelError}\n   * on API failure.\n   *\n   * There is no typed node-sdk method for this endpoint, so it goes through the\n   * raw request; the response is `{ data: { items: [{ bot_id, bot_name }] } }`.\n   */\n  async getChatBots(chatId: string, opts?: { force?: boolean }): Promise<ChatMember[]> {\n    if (!opts?.force) {\n      const cached = this.chatMemberCache.getBots(chatId);\n      if (cached) return cached;\n    }\n    const bots = await this.fetchChatBots(chatId);\n    this.chatMemberCache.setBots(chatId, bots);\n    return bots;\n  }\n\n  private async fetchChatBots(chatId: string): Promise<ChatMember[]> {\n    try {\n      const r = await this.rawClient.request({\n        url: `/open-apis/im/v1/chats/${encodeURIComponent(chatId)}/members/bots`,\n        method: 'GET',\n      });\n      // client.request returns the parsed body; the envelope carries `data`,\n      // but tolerate a top-level `items` shape defensively.\n      const body = r as { data?: { items?: RawBotItem[] }; items?: RawBotItem[] };\n      const items = body.data?.items ?? body.items ?? [];\n      const out: ChatMember[] = [];\n      for (const it of items) {\n        if (!it.bot_id) continue;\n        out.push({ id: it.bot_id, idType: 'open_id', name: it.bot_name, isBot: true });\n      }\n      return out;\n    } catch (e) {\n      throw classifyError(e, { to: chatId });\n    }\n  }\n\n  /** Warm the roster for `senderName` resolution; failures degrade silently. */\n  private async warmChatRoster(chatId: string): Promise<void> {\n    try {\n      await this.getChatMembers(chatId);\n    } catch (e) {\n      this.logger.debug?.('channel: roster warm failed', e);\n    }\n  }\n\n  /**\n   * Record identities seen in a message's mentions (incl. bots) into the\n   * roster, so a bot that has \"shown its face\" can later be @'d by name.\n   * Source is 'mention' so it never overwrites authoritative API user names.\n   */\n  private collectMentionsIntoRoster(chatId: string, mentions: MentionInfo[]): void {\n    const seen: ChatMember[] = [];\n    for (const m of mentions) {\n      if (m.openId && m.name) seen.push({ id: m.openId, name: m.name, isBot: m.isBot });\n    }\n    if (seen.length) this.chatMemberCache.setMembers(chatId, seen, 'mention');\n  }\n\n  /**\n   * Fetch a message by id and return it as a {@link NormalizedMessage} — the\n   * same shape live `message` events produce. Useful for resolving a\n   * reply-quoted message: `im.v1.message.get` returns a flat item list\n   * (parent + descendants for merge_forward), which this method feeds back\n   * through {@link normalize} so merge_forward gets the same\n   * `<forwarded_messages>` expansion as live events.\n   *\n   * Sunk from bridge's `quote.ts`, which previously synthesized a fake raw\n   * event and called the internal `normalize()` directly. Returns\n   * `undefined` when the message can't be fetched or has no parent item.\n   * `stripBotMentions` is off here so the raw quoted content is preserved.\n   */\n  /**\n   * Fetch a message's raw `data.items[]` (`im.v1.message.get`) without running\n   * them through {@link normalize} — for callers that need fidelity the\n   * normalizer drops: original `body.content` JSON, `mentions`, `sender.id`,\n   * `create_time`. For merge_forward the list is the parent followed by its\n   * descendants (each carrying `upper_message_id`).\n   *\n   * `cardContentType` maps to the `card_msg_content_type` query param.\n   * Defaults to `'user_card_content'` so interactive messages return the\n   * original CardKit 2.0 card JSON (`user_dsl`) rather than the v1-canonical\n   * downgrade. Pass `null` to omit the param entirely.\n   */\n  async fetchRawMessage(\n    messageId: string,\n    opts?: { cardContentType?: 'user_card_content' | string | null },\n  ): Promise<ApiMessageItem[]> {\n    const cardContentType =\n      opts?.cardContentType === undefined ? 'user_card_content' : opts.cardContentType;\n    const r = (await this.rawClient.im.v1.message.get({\n      path: { message_id: messageId },\n      params: (cardContentType ? { card_msg_content_type: cardContentType } : undefined) as never,\n    })) as { data?: { items?: ApiMessageItem[] } };\n    return r?.data?.items ?? [];\n  }\n\n  async fetchMessage(messageId: string): Promise<NormalizedMessage | undefined> {\n    let items: ApiMessageItem[];\n    try {\n      const r = (await this.rawClient.im.v1.message.get({\n        path: { message_id: messageId },\n      })) as { data?: { items?: ApiMessageItem[] } };\n      items = r?.data?.items ?? [];\n    } catch (e) {\n      this.logger.warn?.('channel: fetchMessage failed', e);\n      return undefined;\n    }\n    const parent = items[0];\n    if (!parent || !parent.message_id) return undefined;\n\n    // Reuse the already-fetched items when normalize re-asks for sub-messages\n    // of this same id (merge_forward); nested merge_forwards fall back to a\n    // fresh API call.\n    const fetchSubMessages = async (mid: string): Promise<ApiMessageItem[]> => {\n      if (mid === parent.message_id) return items;\n      try {\n        const r = (await this.rawClient.im.v1.message.get({\n          path: { message_id: mid },\n        })) as { data?: { items?: ApiMessageItem[] } };\n        return r?.data?.items ?? [];\n      } catch {\n        return [];\n      }\n    };\n\n    const senderOpenId = parent.sender?.id;\n    const fakeRaw: RawMessageEvent = {\n      sender: { sender_id: { open_id: senderOpenId } },\n      message: {\n        message_id: parent.message_id,\n        // chat_id / chat_type aren't used by normalize's converters but are\n        // required by the type. Empty strings are safe.\n        chat_id: '',\n        chat_type: 'group',\n        message_type: parent.msg_type ?? 'text',\n        content: parent.body?.content ?? '',\n        create_time: parent.create_time !== undefined ? String(parent.create_time) : undefined,\n        mentions: parent.mentions,\n      },\n    };\n\n    try {\n      return await normalize(fakeRaw, {\n        botIdentity: this.botIdentity ?? { openId: '', name: '' },\n        fetchSubMessages,\n        stripBotMentions: false,\n      });\n    } catch (e) {\n      this.logger.warn?.('channel: fetchMessage normalize failed', e);\n      return undefined;\n    }\n  }\n\n  // ─── runtime config ────────────────────────────────────\n\n  updatePolicy(partial: Partial<PolicyConfig>): void {\n    this.safety.updatePolicy(partial);\n  }\n\n  getPolicy(): Readonly<PolicyConfig> {\n    return this.safety.getPolicy();\n  }\n\n  // ─── internals: bot identity & dispatch wiring ────────\n\n  private async fetchBotIdentity(): Promise<BotIdentity> {\n    // Standard Feishu API: GET /open-apis/bot/v3/info\n    // Returns: { code, msg, bot: { open_id, app_name, avatar_url, ... } }\n    let lastError: unknown;\n    try {\n      const r = await this.rawClient.request({\n        url: '/open-apis/bot/v3/info',\n        method: 'GET',\n      });\n      const bot = (r as { bot?: { open_id?: string; app_name?: string } }).bot;\n      if (bot?.open_id) {\n        return { openId: bot.open_id, name: bot.app_name ?? 'bot' };\n      }\n      lastError = new Error(\n        `bot/v3/info response missing open_id: ${JSON.stringify(r).slice(0, 200)}`,\n      );\n    } catch (e) {\n      lastError = e;\n    }\n\n    // Let the shared error classifier decide: 401/403 / feishu auth codes\n    // → permission_denied; rate_limited / send_timeout pass through;\n    // everything else falls back to `not_connected` (the genuine\n    // \"couldn't reach the API\" bucket). Without this, all connect\n    // failures collapse to `not_connected`, making auth errors\n    // indistinguishable from network errors.\n    const classified = classifyError(lastError);\n    const code = classified.code === 'unknown' ? 'not_connected' : classified.code;\n    throw new LarkChannelError(\n      code,\n      'could not resolve bot identity via /open-apis/bot/v3/info — required for channel to function',\n      { cause: lastError },\n    );\n  }\n\n  private registerDispatcherHandlers(): void {\n    // `im.v1.message.get(mid)` on a merge_forward message returns\n    // `data.items[]` as a flat list: the parent message first (no\n    // `upper_message_id`) followed by every descendant, each with\n    // `upper_message_id` pointing at its direct parent. That is\n    // exactly what `convertMergeForward` / `buildChildrenMap` consume,\n    // so the converter tree-builds correctly without further work.\n    // (Earlier attempts used `message.list` with\n    // `container_id_type: 'message'`, which Feishu rejects — 'message'\n    // isn't a valid container type.)\n    const fetchSubMessages = async (mid: string): Promise<ApiMessageItem[]> => {\n      try {\n        const r = await this.rawClient.im.v1.message.get({\n          path: { message_id: mid },\n        });\n        const items = (r as { data?: { items?: ApiMessageItem[] } }).data?.items ?? [];\n        return items;\n      } catch (e) {\n        this.logger.warn?.('channel: fetchSubMessages failed', e);\n        return [];\n      }\n    };\n\n    // Unified raw-event flag: prefer the new `includeRawEvent` option,\n    // fall back to the legacy `includeRawInMessage` for back-compat.\n    const includeRaw = this.opts.includeRawEvent ?? this.opts.includeRawInMessage ?? false;\n\n    const normalizeOpts = {\n      botIdentity: this.botIdentity!,\n      stripBotMentions: true,\n      includeRaw,\n      fetchSubMessages,\n    };\n\n    this.dispatcher.register({\n      // IM message — full safety pipeline\n      'im.message.receive_v1': async (raw: unknown) => {\n        try {\n          const event = raw as RawMessageEvent;\n          const chatId = event.message.chat_id;\n\n          // Opt-in: warm the chat roster and resolve the sender's display name\n          // (Feishu omits it from message events). Best-effort; degrades to\n          // undefined on failure. Off by default (zero extra API).\n          let resolveSenderName: ((openId: string) => string | undefined) | undefined;\n          if (this.opts.resolveSenderNames) {\n            await this.warmChatRoster(chatId);\n            resolveSenderName = (openId) => this.chatMemberCache.resolveName(chatId, openId);\n          }\n\n          const msg = await normalize(event, { ...normalizeOpts, resolveSenderName });\n\n          // Collect observed mention identities (incl. bots, which the members\n          // API filters out) so they can later be @'d by name.\n          this.collectMentionsIntoRoster(chatId, msg.mentions);\n\n          // Opt-in: resolve the finer-grained chat mode (p2p/group/topic),\n          // which Feishu omits from the event. Cached per chatId; best-effort.\n          if (this.opts.resolveChatMode) {\n            msg.chatMode = await this.chatModeCache.resolve(msg.chatId, (id) =>\n              this.getChatMode(id),\n            );\n          }\n          await this.safety.pushMessage(msg);\n        } catch (e) {\n          this.emitError(e);\n        }\n      },\n\n      // Card button click — dedup + lock + queue (by chatId).\n      // The key includes the action's identity (tag + value) so that\n      // different buttons on the same card by the same user are NOT\n      // collapsed by the dedup cache. A genuine Feishu re-delivery\n      // of the same click still hashes to the same key.\n      'card.action.trigger': async (raw: unknown) => {\n        const evt = normalizeCardAction(raw as never, { includeRaw });\n        if (!evt) return undefined;\n        const actionId = cardActionId(evt.action);\n        // Return the handler's value so a card-action callback response\n        // (e.g. a toast) flows back through the dispatcher to Feishu. A\n        // missing handler or a deduped / in-flight drop yields `undefined`,\n        // which the transport reads as \"no response\".\n        return this.safety.pushAction(\n          `card:${evt.messageId}:${evt.operator.openId}:${actionId}`,\n          evt.chatId,\n          async () => {\n            const h = this.handlers.cardAction;\n            return h ? h(evt) : undefined;\n          },\n        );\n      },\n\n      // Reactions — dedup only\n      'im.message.reaction.created_v1': async (raw: unknown) => {\n        const evt = normalizeReaction(raw as never, 'added', { includeRaw });\n        if (!evt) return;\n        const key = reactionKey(evt);\n        await this.safety.pushLight(key, () => this.handlers.reaction?.(evt));\n      },\n      'im.message.reaction.deleted_v1': async (raw: unknown) => {\n        const evt = normalizeReaction(raw as never, 'removed', { includeRaw });\n        if (!evt) return;\n        const key = reactionKey(evt);\n        await this.safety.pushLight(key, () => this.handlers.reaction?.(evt));\n      },\n\n      // Bot added — direct fire, no safety\n      'im.chat.member.bot.added_v1': (raw: unknown) => {\n        const evt = normalizeBotAdded(raw as never, { includeRaw });\n        if (!evt) return;\n        try {\n          this.handlers.botAdded?.(evt);\n        } catch (e) {\n          this.emitError(e);\n        }\n      },\n\n      // Drive comments — dedup + lock + queue (by fileToken).\n      // The dedup key folds in replyId so thread replies on the same\n      // top-level comment don't collide with each other (or with the\n      // top-level comment itself).\n      'drive.notice.comment_add_v1': async (raw: unknown) => {\n        const evt = normalizeComment(raw as never, { includeRaw });\n        if (!evt) return;\n        await this.safety.pushAction(\n          `comment:${evt.fileToken}:${evt.commentId}:${evt.replyId ?? ''}`,\n          evt.fileToken,\n          async () => {\n            const h = this.handlers.comment;\n            if (h) await h(evt);\n          },\n        );\n      },\n    } as never);\n  }\n\n  private emitError(e: unknown): void {\n    const err =\n      e instanceof LarkChannelError\n        ? e\n        : new LarkChannelError('unknown', String((e as { message?: string })?.message ?? e), {\n            cause: e,\n          });\n    const handler = this.handlers.error;\n    if (handler) handler(err);\n    else this.logger.error?.('channel: unhandled error', err);\n  }\n}\n\nexport function createLarkChannel(opts: LarkChannelOptions): LarkChannel {\n  return new LarkChannel(opts);\n}\n\n/** Mask `user:pass@` credentials in a proxy URL before logging it. */\nfunction redactProxyUrl(url: string): string {\n  return url.replace(/\\/\\/[^:@/]+:[^@/]+@/, '//[redacted]@');\n}\n\n/**\n * Pull the `content-type` media type out of a download response's headers.\n * The code-gen download endpoints expose axios response headers on the\n * wrapper object; header names are case-insensitive, so check both casings.\n * Strips any `; charset=…` / `; boundary=…` parameters and returns the bare\n * media type (lowercased), or `undefined` when no usable header is present.\n */\nfunction extractContentType(raw: unknown): string | undefined {\n  if (typeof raw !== 'object' || raw === null) return undefined;\n  const headers = (raw as { headers?: Record<string, unknown> }).headers;\n  if (!headers) return undefined;\n  const value = headers['content-type'] ?? headers['Content-Type'];\n  if (typeof value !== 'string') return undefined;\n  const mediaType = value.split(';', 1)[0]?.trim().toLowerCase();\n  return mediaType || undefined;\n}\n\n/**\n * Stream a download response to disk without buffering it in heap. The\n * code-gen download endpoints expose the body via `getReadableStream()`,\n * which we `pipeline` straight into a write stream (back-pressure aware,\n * cleans up on error). Falls back to writing an already-materialized\n * `Buffer` / `Uint8Array` for the defensive non-stream response shapes —\n * those are already in memory, so there's nothing to stream. Returns the\n * number of bytes written.\n */\nasync function streamToFile(raw: unknown, destPath: string): Promise<number> {\n  if (typeof raw === 'object' && raw !== null) {\n    const r = raw as {\n      data?: unknown;\n      getReadableStream?: () => NodeJS.ReadableStream;\n    };\n    if (typeof r.getReadableStream === 'function') {\n      await pipeline(r.getReadableStream(), createWriteStream(destPath));\n      const { size } = await stat(destPath);\n      return size;\n    }\n    if (Buffer.isBuffer(r.data)) {\n      await writeFile(destPath, r.data);\n      return r.data.length;\n    }\n    if (r.data instanceof Uint8Array) {\n      const buf = Buffer.from(r.data);\n      await writeFile(destPath, buf);\n      return buf.length;\n    }\n  }\n  if (Buffer.isBuffer(raw)) {\n    await writeFile(destPath, raw);\n    return raw.length;\n  }\n  if (raw instanceof Uint8Array) {\n    const buf = Buffer.from(raw);\n    await writeFile(destPath, buf);\n    return buf.length;\n  }\n  throw new LarkChannelError('unknown', 'unexpected download response type');\n}\n\nasync function bufferFromStream(raw: unknown): Promise<Buffer> {\n  if (Buffer.isBuffer(raw)) return raw;\n  if (raw instanceof Uint8Array) return Buffer.from(raw);\n  if (typeof raw === 'object' && raw !== null) {\n    const r = raw as {\n      data?: unknown;\n      getReadableStream?: () => NodeJS.ReadableStream;\n    };\n    // The code-gen download endpoints (im.v1.image.get / im.v1.file.get)\n    // return a wrapper object `{ writeFile, getReadableStream, headers }`\n    // where the body is exposed as a stream. Consume it into a Buffer.\n    if (typeof r.getReadableStream === 'function') {\n      return await readableToBuffer(r.getReadableStream());\n    }\n    if (Buffer.isBuffer(r.data)) return r.data;\n    if (r.data instanceof Uint8Array) return Buffer.from(r.data);\n  }\n  throw new LarkChannelError('unknown', 'unexpected download response type');\n}\n\nfunction readableToBuffer(stream: NodeJS.ReadableStream): Promise<Buffer> {\n  return new Promise((resolve, reject) => {\n    const chunks: Buffer[] = [];\n    stream.on('data', (chunk: Buffer | string) => {\n      chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n    });\n    stream.on('end', () => resolve(Buffer.concat(chunks)));\n    stream.on('error', reject);\n  });\n}\n\nfunction reactionKey(evt: {\n  messageId: string;\n  operator: { openId: string };\n  emojiType: string;\n  action: 'added' | 'removed';\n  actionTime?: number;\n}): string {\n  return `rx:${evt.messageId}:${evt.operator.openId}:${evt.emojiType}:${evt.action}:${evt.actionTime ?? 0}`;\n}\n\n/**\n * Build a stable identity for a card action event's button/element, so that\n * different clicks on the same card by the same user dedup independently.\n * `tag` plus serialized `value` is enough to tell buttons apart; `name` and\n * `option` are rolled in for form-style interactions where the same value\n * may repeat but the triggering element differs. The serialized payload is\n * length-clamped to keep cache keys small.\n */\nfunction cardActionId(action: {\n  value: unknown;\n  tag: string;\n  name?: string;\n  option?: string;\n}): string {\n  const serialized =\n    typeof action.value === 'string' ? action.value : JSON.stringify(action.value ?? '');\n  const valuePart = serialized.length > 128 ? serialized.slice(0, 128) : serialized;\n  return `${action.tag}|${action.name ?? ''}|${action.option ?? ''}|${valuePart}`;\n}\n"],"mappings":"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"}