{"version":3,"file":"index.cjs","names":["inspect","DEFAULTS","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/types.ts","../src/meeting/coerce.ts","../src/safety/types.ts","../src/safety/dedup-cache.ts","../src/meeting/normalize.ts","../src/meeting/dedup.ts","../src/meeting/errors.ts","../src/meeting/health.ts","../src/meeting/registry.ts","../src/meeting/cursor.ts","../src/meeting/liveness.ts","../src/meeting/rate-limit.ts","../src/meeting/serial-queue.ts","../src/meeting/sources/poll-source.ts","../src/meeting/stabilizer.ts","../src/meeting/session.ts","../src/meeting/token.ts","../src/meeting/index.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/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/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 { Cache, Domain, HttpInstance, LoggerLevel } from '@larksuiteoapi/node-sdk';\nimport type { Logger, WSConfigOverrides } from './internal';\nimport type { MeetingChannelConfig, MeetingInvitedEvent } from './meeting/types';\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  /**\n   * The bot was invited into a meeting. Single-slot like every other channel\n   * event: one invite maps to one decision about whether to join.\n   */\n  meetingInvited: (evt: MeetingInvitedEvent) => 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  /** The operation is unavailable in the current mode (e.g. posting from a followed meeting). */\n  | 'not_supported'\n  /** No active meeting to follow, or the target meeting is no longer active. */\n  | 'meeting_not_found'\n  /** {@link MeetingChannelConfig.maxConcurrentSessions} reached. */\n  | 'too_many_sessions'\n  | 'unknown';\n\nexport class LarkChannelError extends Error {\n  code: LarkChannelErrorCode;\n\n  cause?: unknown;\n\n  context?: {\n    to?: string;\n    messageId?: string;\n    attempt?: number;\n    meetingId?: string;\n    /**\n     * Signed one-click authorization link returned with a permission failure.\n     *\n     * A credential in URL form, not a plain link: passed through byte for byte\n     * because re-encoding invalidates the signature, validated as `https:` before\n     * being surfaced at all, and never written to a log. Do not echo it into a\n     * chat, a UI, or a support ticket.\n     */\n    consoleUrl?: string;\n  };\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  /**\n   * Meeting-channel limits: concurrent sessions, idle reclamation, liveness\n   * probing and the in-meeting send rate. See {@link MeetingChannelConfig}.\n   */\n  meeting?: MeetingChannelConfig;\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) a *single* WebSocket handshake (`open` / `error`) may\n   * take before that attempt is aborted and the underlying retry loop tries\n   * again. Forwarded as-is to the underlying WSClient. When unset, no\n   * per-attempt timeout is enforced — one handshake can hang indefinitely on\n   * stuck DNS / proxy / NAT paths.\n   *\n   * This is **not** the budget for how long `connect()` waits before giving\n   * up; that is {@link connectTimeoutMs}.\n   */\n  handshakeTimeoutMs?: number;\n\n  /**\n   * Total time (ms) to wait for a WebSocket handshake to succeed before\n   * giving up on the attempt. Applies both to `connect()` and to the\n   * internal force-reconnect that keepalive triggers. On expiry the pending\n   * `WSClient` is torn down and the caller gets a `not_connected`\n   * `LarkChannelError` naming the elapsed budget.\n   *\n   * Defaults to 15000. `NaN`, `0`, negatives and non-finite values fall back\n   * to the default; values above the timer's 32-bit ceiling (2147483647ms) are\n   * clamped down to it. Both rules exist for the same reason: `setTimeout`\n   * turns an out-of-domain delay into 1ms, which would silently invert both an\n   * unset `Number(process.env.X)` and a deliberately generous budget.\n   *\n   * Distinct from {@link handshakeTimeoutMs}, which bounds one handshake\n   * attempt; this bounds the wait as a whole.\n   */\n  connectTimeoutMs?: 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     * Which per-chat queue `card.action.trigger` (the `cardAction` handler)\n     * joins. Only meaningful while `enabled` is on.\n     *\n     * - `'same'` (default): card actions share the chat's queue with messages.\n     *   A click runs after any in-flight work for that chat, and messages that\n     *   arrive later wait for it — the 0.6.x behavior.\n     * - `'separate'`: card actions get their own per-chat lane, independent of\n     *   the message queue in both directions: a click no longer waits for an\n     *   in-flight `message` handler, and messages do not wait for clicks.\n     *   Clicks within one chat still run in arrival order. Use it when a\n     *   `message` handler has to wait for a card click (agent tool approvals)\n     *   — under `'same'` that is a deadlock.\n     *\n     * Any other value falls back to `'same'` with a warning at construction.\n     * See the README's `cardAction` notes for what the application then owns.\n     */\n    cardActions?: 'same' | 'separate';\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","/**\n * Coercions for platform payloads.\n *\n * Every field on the wire is optional, and timestamps and durations arrive as\n * decimal strings rather than numbers. Shared so the unpacking path and the\n * channel entry points cannot drift into two slightly different readings of the\n * same value.\n */\n\nexport type Dict = Record<string, unknown>;\n\nexport function asDict(v: unknown): Dict | undefined {\n  return typeof v === 'object' && v !== null && !Array.isArray(v) ? (v as Dict) : undefined;\n}\n\n/** Only object entries survive: a stray scalar in an item array is not an item. */\nexport function asArray(v: unknown): Dict[] {\n  return Array.isArray(v) ? (v.filter((x) => asDict(x) !== undefined) as Dict[]) : [];\n}\n\nexport function asString(v: unknown): string | undefined {\n  return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n\nexport function asNumber(v: unknown): number | undefined {\n  return typeof v === 'number' ? v : undefined;\n}\n\nexport function asBoolean(v: unknown): boolean | undefined {\n  return typeof v === 'boolean' ? v : undefined;\n}\n\n/** Milliseconds, whether they arrive as a number or a decimal string. */\nexport function asMs(v: unknown): number | undefined {\n  if (typeof v === 'number') return v;\n  if (typeof v !== 'string' || v.length === 0) return undefined;\n  const n = Number(v);\n  return Number.isFinite(n) ? n : undefined;\n}\n\nexport function asStringArray(v: unknown): string[] | undefined {\n  return Array.isArray(v) && v.every((x) => typeof x === 'string') ? (v as string[]) : undefined;\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\nexport type CardActionQueueMode = NonNullable<\n  NonNullable<SafetyConfig['chatQueue']>['cardActions']\n>;\n\nconst CARD_ACTION_QUEUE_MODES: readonly CardActionQueueMode[] = ['same', 'separate'];\n\n/**\n * `undefined` is the silent default. Anything outside the accepted set falls\n * back to `'same'` and is flagged, so the pipeline can warn once instead of\n * refusing to start over a typo.\n */\nexport function resolveCardActionQueueMode(value: unknown): {\n  mode: CardActionQueueMode;\n  unrecognized: boolean;\n} {\n  if (value === undefined) return { mode: 'same', unrecognized: false };\n  if (isCardActionQueueMode(value)) return { mode: value, unrecognized: false };\n  return { mode: 'same', unrecognized: true };\n}\n\nfunction isCardActionQueueMode(value: unknown): value is CardActionQueueMode {\n  return (CARD_ACTION_QUEUE_MODES as readonly unknown[]).includes(value);\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","/**\n * Turning in-meeting activity into session events.\n *\n * Two nesting layers, not one: a delivery carries several activities and each\n * activity carries several items. Reading only the first of either drops most of\n * a busy meeting while still looking like it works, because the shape is valid\n * either way.\n *\n * Push and poll disagree about where the payload sits — a push puts\n * `activity_event_type` and the `*_items[]` arrays directly on the activity, a\n * poll response nests both under `payload` — so both are flattened to a single\n * {@link RawActivity} before anything else looks at them. The difference is not\n * described in any prose doc; it comes from the generated types.\n *\n * Order is preserved throughout. A share hand-off arrives as an `ended`\n * followed by a `started` in one delivery, and array position is the only thing\n * that says which document is current.\n */\n\nimport {\n  asArray,\n  asBoolean,\n  asDict,\n  asMs,\n  asNumber,\n  asString,\n  asStringArray,\n  type Dict,\n} from './coerce';\nimport type {\n  MeetingActor,\n  MeetingChatEvent,\n  MeetingDocumentContextEvent,\n  MeetingEventName,\n  MeetingParticipantEvent,\n  MeetingSharedDoc,\n  MeetingShareEvent,\n  MeetingTranscriptEvent,\n} from './types';\n\nexport interface MeetingNormalizeContext {\n  meetingId: string;\n  mode: 'uat' | 'tat';\n  /** Undefined while the bot's identity is still resolving — see `selfEcho`. */\n  botOpenId?: string;\n  includeRaw?: boolean;\n}\n\nexport interface NormalizedMeetingEvent {\n  name: MeetingEventName;\n  /** The platform's `activity_event_type`, kept for health counters. */\n  activityType: string;\n  event: unknown;\n}\n\n/** One activity, with the push/poll nesting difference already flattened away. */\nexport interface RawActivity {\n  meetingId?: string;\n  activityType: string;\n  items: Dict[];\n  /**\n   * Redelivery key. A polled event carries its own `event_id`; a push carries one\n   * on the envelope, which is combined with the activity's position — see\n   * {@link readPushActivities}.\n   */\n  eventId?: string;\n}\n\n/**\n * `activity_event_type` → the array field holding its items.\n *\n * An activity type absent from this table is one the platform added after this\n * release: it yields no events and is counted as a parse miss, which is the\n * signal that the SDK has fallen behind.\n */\n/**\n * A `Map` rather than an object literal: the key is a string the server chooses,\n * and a plain object would happily resolve `'constructor'` or `'toString'` to an\n * inherited member.\n */\nconst ITEMS_FIELD = new Map<string, string>([\n  ['transcript_received', 'transcript_received_items'],\n  ['chat_received', 'chat_received_items'],\n  ['participant_joined', 'participant_joined_items'],\n  ['participant_left', 'participant_left_items'],\n  ['magic_share_started', 'magic_share_started_items'],\n  ['magic_share_ended', 'magic_share_ended_items'],\n  ['document_context_changed', 'document_context_changed_items'],\n]);\n\nconst EVENT_NAME = new Map<string, MeetingEventName>([\n  ['transcript_received', 'transcript'],\n  ['chat_received', 'chat'],\n  ['participant_joined', 'participant'],\n  ['participant_left', 'participant'],\n  ['magic_share_started', 'share'],\n  ['magic_share_ended', 'share'],\n  ['document_context_changed', 'documentContext'],\n]);\n\nconst CONTEXT_TYPE_BY_NAME = new Map<string, MeetingDocumentContextEvent['contextType']>([\n  ['comment_focus', 'commentFocus'],\n  ['section_location', 'sectionLocation'],\n  ['element_preview', 'elementPreview'],\n]);\n\n// ─────────────────────────────────────────────────────────────\n// Reading the two wire shapes\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Activities out of a `vc.bot.meeting_activity_v1` push.\n *\n * The id that identifies a re-delivery lives on the envelope, not on the\n * activities inside it, so it is combined with the activity's position: the\n * whole push is suppressed on redelivery, while the several activities within one\n * push stay distinct from each other.\n */\nexport function readPushActivities(payload: unknown): RawActivity[] {\n  const envelope = payload as Dict | undefined;\n  const envelopeId = asString(envelope?.event_id);\n  return asArray(envelope?.meeting_activity_items).map((activity, index) => {\n    const parsed = toRawActivity(activity);\n    return {\n      ...parsed,\n      eventId: parsed.eventId ?? (envelopeId ? `${envelopeId}#${index}` : undefined),\n    };\n  });\n}\n\n/** Activities out of a `vc.v1.bot.events` response body — one activity per event. */\nexport function readPollActivities(data: unknown): RawActivity[] {\n  const events = asArray((data as Dict | undefined)?.events);\n  return events.map((event) => {\n    const payload = asDict(event.payload) ?? {};\n    return {\n      ...toRawActivity(payload),\n      eventId: asString(event.event_id),\n    };\n  });\n}\n\nfunction toRawActivity(carrier: Dict): RawActivity {\n  const activityType = asString(carrier.activity_event_type) ?? '';\n  const field = ITEMS_FIELD.get(activityType);\n  const meeting = asDict(carrier.meeting);\n  return {\n    meetingId: asString(meeting?.id),\n    activityType,\n    // Both nestings are accepted on both paths: the flat form is what a push\n    // sends, the `payload` form is what a poll returns, and neither is\n    // guaranteed to stay put.\n    items: field ? asArray(carrier[field] ?? asDict(carrier.payload)?.[field]) : [],\n    eventId: asString(carrier.event_id),\n  };\n}\n\n// ─────────────────────────────────────────────────────────────\n// Normalizing\n// ─────────────────────────────────────────────────────────────\n\nexport interface ActivityResult {\n  events: NormalizedMeetingEvent[];\n  /**\n   * The activity was understood but every item inside it was a variant this\n   * release does not know. That is planned forward-compatibility, not a parse\n   * failure, and counting it as one would train people to ignore the counter.\n   */\n  forwardCompatible: boolean;\n}\n\nexport function normalizeActivity(\n  activity: RawActivity,\n  ctx: MeetingNormalizeContext,\n): ActivityResult {\n  const name = EVENT_NAME.get(activity.activityType);\n  if (!name) return { events: [], forwardCompatible: false };\n\n  const events: NormalizedMeetingEvent[] = [];\n  let dropped = 0;\n\n  for (const item of activity.items) {\n    const event = buildEvent(name, activity.activityType, item, ctx);\n    if (!event) {\n      dropped++;\n      continue;\n    }\n    events.push({ name, activityType: activity.activityType, event });\n  }\n\n  return {\n    events,\n    forwardCompatible:\n      activity.activityType === 'document_context_changed' && dropped > 0 && events.length === 0,\n  };\n}\n\n/** Convenience wrapper over a whole push, scoped to one meeting. */\nexport function normalizeMeetingPush(\n  payload: unknown,\n  ctx: MeetingNormalizeContext,\n): NormalizedMeetingEvent[] {\n  return normalizeAll(readPushActivities(payload), ctx);\n}\n\n/** Convenience wrapper over a whole poll response body, scoped to one meeting. */\nexport function normalizeMeetingPoll(\n  data: unknown,\n  ctx: MeetingNormalizeContext,\n): NormalizedMeetingEvent[] {\n  return normalizeAll(readPollActivities(data), ctx);\n}\n\nfunction normalizeAll(\n  activities: RawActivity[],\n  ctx: MeetingNormalizeContext,\n): NormalizedMeetingEvent[] {\n  return activities\n    .filter((a) => !a.meetingId || a.meetingId === ctx.meetingId)\n    .flatMap((a) => normalizeActivity(a, ctx).events);\n}\n\n// ─────────────────────────────────────────────────────────────\n// Per-type item mapping\n// ─────────────────────────────────────────────────────────────\n\nfunction buildEvent(\n  name: MeetingEventName,\n  activityType: string,\n  item: Dict,\n  ctx: MeetingNormalizeContext,\n): unknown {\n  const actor = readActor(item);\n  const base = {\n    meetingId: ctx.meetingId,\n    actor,\n    selfEcho: isSelfEcho(actor, ctx),\n    ...(ctx.includeRaw ? { raw: item } : {}),\n  };\n\n  switch (name) {\n    case 'transcript':\n      return {\n        ...base,\n        text: asString(item.text) ?? '',\n        sentenceId: asString(item.sentence_id),\n        language: asString(item.language),\n        startMs: asMs(item.start_time_ms),\n        endMs: asMs(item.end_time_ms),\n      } satisfies MeetingTranscriptEvent;\n\n    case 'chat':\n      return {\n        ...base,\n        content: asString(item.content) ?? '',\n        messageId: asString(item.message_id),\n        messageType: asNumber(item.message_type),\n        sendTime: asMs(item.send_time),\n      } satisfies MeetingChatEvent;\n\n    case 'participant':\n      return {\n        ...base,\n        action: activityType === 'participant_left' ? 'left' : 'joined',\n        joinTime: asMs(item.join_time),\n        leaveTime: asMs(item.leave_time),\n        leaveReason: asNumber(item.leave_reason),\n      } satisfies MeetingParticipantEvent;\n\n    case 'share':\n      return {\n        ...base,\n        action: activityType === 'magic_share_ended' ? 'ended' : 'started',\n        shareId: asString(item.share_id),\n        doc: readDoc(item.share_doc),\n        time: asMs(item.time),\n      } satisfies MeetingShareEvent;\n\n    case 'documentContext':\n      return buildDocumentContext(base, item);\n\n    default:\n      return undefined;\n  }\n}\n\n/**\n * `contextType` is derived from which sub-object is present.\n *\n * The `context_type` discriminator the prose docs describe does not exist in the\n * generated API surface at all, so presence is the only reliable signal — but a\n * platform-sent `context_type` wins when it is there, since the generated types\n * can lag the wire. An item carrying none of the three known variants is a new\n * context kind and is dropped.\n */\nfunction buildDocumentContext(base: object, item: Dict): MeetingDocumentContextEvent | undefined {\n  const declared = asString(item.context_type);\n  const commentFocus = asDict(item.comment_focus);\n  const sectionLocation = asDict(item.section_location);\n  const elementPreview = asDict(item.element_preview);\n\n  const contextType = pickContextType(declared, {\n    commentFocus,\n    sectionLocation,\n    elementPreview,\n  });\n  if (!contextType) return undefined;\n\n  const shared = {\n    ...base,\n    contextType,\n    shareId: asString(item.share_id),\n    doc: readDoc(item.share_doc),\n    time: asMs(item.time),\n  } as MeetingDocumentContextEvent;\n\n  if (contextType === 'commentFocus') {\n    shared.commentFocus = {\n      commentId: asString(commentFocus?.comment_id),\n      focused: asBoolean(commentFocus?.focused),\n    };\n  } else if (contextType === 'sectionLocation') {\n    shared.sectionLocation = {\n      title: asString(sectionLocation?.title),\n      level: asNumber(sectionLocation?.level),\n      parentTitles: asStringArray(sectionLocation?.parent_titles),\n    };\n  } else {\n    shared.elementPreview = {\n      action: asString(elementPreview?.action),\n      elementType: asString(elementPreview?.element_type),\n      elementToken: asString(elementPreview?.element_token),\n      blockId: asString(elementPreview?.block_id),\n    };\n  }\n  return shared;\n}\n\nfunction pickContextType(\n  declared: string | undefined,\n  present: { commentFocus?: Dict; sectionLocation?: Dict; elementPreview?: Dict },\n): MeetingDocumentContextEvent['contextType'] | undefined {\n  const named = declared ? CONTEXT_TYPE_BY_NAME.get(declared) : undefined;\n\n  // A declared name this release does not know is a new context kind: dropped,\n  // and deliberately not second-guessed from whatever sub-objects came with it.\n  if (declared && !named) return undefined;\n  // A known name still has to come with a sub-object — the name alone is no data.\n  if (named && present[named]) return named;\n\n  if (present.commentFocus) return 'commentFocus';\n  if (present.sectionLocation) return 'sectionLocation';\n  if (present.elementPreview) return 'elementPreview';\n  return undefined;\n}\n\n/**\n * Each activity type names its actor differently — `speaker`, `operator`,\n * `participant` — and the id field is not consistent either, so both are\n * normalized here rather than at every call site.\n */\nexport function readActor(item: Dict): MeetingActor {\n  const raw = asDict(item.speaker) ?? asDict(item.operator) ?? asDict(item.participant) ?? {};\n  return {\n    id: readActorId(raw),\n    name: asString(raw.user_name) ?? asString(raw.name),\n    userType: asNumber(raw.user_type),\n    userRole: asNumber(raw.user_role),\n  };\n}\n\n/**\n * The actor id, whichever shape it arrives in.\n *\n * Both shapes are real, and which one arrives follows from the request. A poll passes\n * `user_id_type: 'open_id'`, so the service picks a convention and `id` is that\n * string. A push has no such parameter, so it sends the whole set nested —\n * `{ open_id, union_id, user_id }` — confirmed against the live service, and contrary\n * to the generated types, which declare `id?: string` for both.\n *\n * Reading only the string form is the worst possible failure here: it does not throw,\n * it leaves the id empty, and `selfEcho` then compares against `''` and never matches\n * — so the bot answers its own messages, with nothing in the logs to say why.\n * `open_id` first, because that is the namespace the bot's own id lives in.\n */\nfunction readActorId(raw: Dict): string {\n  const nested = asDict(raw.id);\n  if (nested) {\n    return asString(nested.open_id) ?? asString(nested.user_id) ?? asString(nested.union_id) ?? '';\n  }\n  return (\n    asString(raw.id) ??\n    asString(raw.open_id) ??\n    asString(raw.user_id) ??\n    asString(raw.union_id) ??\n    ''\n  );\n}\n\nfunction isSelfEcho(actor: MeetingActor, ctx: MeetingNormalizeContext): boolean {\n  // Follow mode never puts the bot in the meeting, so nothing can be its echo.\n  if (ctx.mode === 'uat') return false;\n  // Not knowing has to read as \"possibly me\": `false` is the value that lets a\n  // caller respond, which is exactly how a bot ends up answering itself.\n  if (!ctx.botOpenId) return true;\n  return actor.id === ctx.botOpenId;\n}\n\nfunction readDoc(value: unknown): MeetingSharedDoc | undefined {\n  const doc = asDict(value);\n  if (!doc) return undefined;\n  return { url: asString(doc.url), title: asString(doc.title) };\n}\n","/**\n * Duplicate suppression for in-meeting activity.\n *\n * Two keys per activity, because neither alone is enough. A delivery key catches the\n * same delivery arriving twice. A content key catches the overlap between the push\n * stream and the liveness probe's gap-recovery read — their delivery ids can never be\n * equal, since one is a platform id and the other is synthesised from the push\n * envelope plus a position.\n *\n * `sentence_id` is NOT a suppression key: a sentence is re-sent as the speaker keeps\n * talking, so keying on it freezes a caption at its first word, silently.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { Cache } from '@larksuiteoapi/node-sdk';\nimport { SeenCache } from '../safety/dedup-cache';\nimport type { RawActivity } from './normalize';\nimport { readActor } from './normalize';\n\n/** Its own namespace: sharing the IM path's would let either side suppress the other. */\nconst NAMESPACE = 'channel:meeting:seen';\n\n/** Two keys per activity, so the window holds half as many activities. */\nconst MAX_ENTRIES = 10_000;\n\nconst DIGEST_LENGTH = 24;\n\nexport class MeetingDedup {\n  private readonly seen: SeenCache;\n\n  constructor(cache: Cache) {\n    this.seen = new SeenCache(cache, { namespace: NAMESPACE, maxMemEntries: MAX_ENTRIES });\n  }\n\n  /**\n   * True when this activity has already been delivered to `scope`.\n   *\n   * `scope` isolates sessions: one meeting can carry both an app-identity bot and a\n   * user-identity follower, reading the same endpoint.\n   */\n  async isDuplicate(activity: RawActivity, scope: string): Promise<boolean> {\n    const keys = [\n      activity.eventId ? `${scope}|d|${activity.eventId}` : undefined,\n      `${scope}|c|${contentKey(activity)}`,\n    ].filter((k): k is string => k !== undefined);\n\n    const hits = await Promise.all(keys.map((k) => this.seen.has(k)));\n    if (hits.some(Boolean)) return true;\n\n    await Promise.all(keys.map((k) => this.seen.add(k)));\n    return false;\n  }\n\n  dispose(): void {\n    this.seen.dispose();\n  }\n}\n\n/** Digest over an explicit tuple, so a value containing the separator cannot fake one. */\nfunction contentKey(activity: RawActivity): string {\n  const parts = activity.items.map((item) => [\n    item.sentence_id ?? item.message_id ?? item.share_id ?? null,\n    item.text ?? item.content ?? null,\n    item.start_time_ms ?? item.send_time ?? item.time ?? item.join_time ?? item.leave_time ?? null,\n    readActor(item).id || null,\n  ]);\n  return createHash('sha256')\n    .update(JSON.stringify([activity.activityType, parts]))\n    .digest('base64url')\n    .slice(0, DIGEST_LENGTH);\n}\n","/**\n * Error construction for the meeting path.\n *\n * A failed request's transport error never becomes `cause`. It arrives holding\n * `Authorization: Bearer <user token>`, and the resulting `LarkChannelError` reaches\n * both the SDK's logger and the caller's `error` handler — which typically forwards it\n * to a tracker that walks `cause`. So the credential is dropped at construction: only\n * the fields needed to diagnose the failure are carried over, `log_id` included.\n */\n\nimport { LarkChannelError, type LarkChannelErrorCode } from '../types';\n\n/** Everything kept from a failed call. Deliberately flat, deliberately small. */\nexport interface ApiFailure {\n  status?: number;\n  feishuCode?: number;\n  msg?: string;\n  /** Feishu's request id — what support actually needs to trace a call. */\n  logId?: string;\n}\n\nexport interface MeetingErrorContext {\n  meetingId?: string;\n  /**\n   * Signed one-click authorization link from a permission failure. A credential\n   * in URL form: passed through byte for byte because re-encoding invalidates\n   * the signature, but never logged, and dropped entirely unless it is `https:`.\n   */\n  consoleUrl?: string;\n}\n\n/** Statuses where a `bots/join` may have succeeded while the caller saw a failure. */\nconst INCONCLUSIVE_STATUS = new Set([408, 502, 504]);\n\n/** Every inconclusive status is retryable, plus the ones that answered \"later\". */\nconst RETRYABLE_STATUS = new Set([...INCONCLUSIVE_STATUS, 429, 500, 503]);\n\n/**\n * Codes that mean \"this credential will not work until the caller fixes it\".\n *\n * `99991668` is here because it is what an *expired user access token* actually\n * returns — observed on the live wire, arriving as HTTP 400. Without it the follow\n * loop still terminates (a 400 is not retryable either), but the caller sees\n * `format_error` and has no signal to refresh the token, which is the single most\n * common failure this path has.\n */\nconst PERMISSION_CODES = new Set([99991400, 99991401, 99991663, 99991668, 99991672, 20017]);\n\ninterface RawFailure {\n  status?: number;\n  code?: unknown;\n  message?: string;\n  response?: {\n    status?: number;\n    data?: Record<string, unknown>;\n    headers?: Record<string, unknown>;\n  };\n  data?: Record<string, unknown>;\n}\n\n/** Where a Feishu error body lives, whichever transport shape it arrived in. */\nfunction readFailureData(err: unknown): Record<string, unknown> | undefined {\n  const raw = err as RawFailure | undefined;\n  return raw?.response?.data ?? raw?.data;\n}\n\n/**\n * Reduce a transport error to the fields worth keeping. Nothing structural is copied —\n * no `config`, `headers`, `request`, or serialized body.\n */\nexport function summarizeApiFailure(err: unknown): ApiFailure {\n  const raw = err as RawFailure | undefined;\n  const data = readFailureData(err);\n  return {\n    status: raw?.response?.status ?? raw?.status,\n    feishuCode: numberOrUndefined(data?.code),\n    msg: stringOrUndefined(data?.msg) ?? raw?.message,\n    logId:\n      stringOrUndefined(data?.log_id) ?? stringOrUndefined(raw?.response?.headers?.['x-tt-logid']),\n  };\n}\n\n/**\n * Pull out `console_url` if, and only if, it is an `https:` URL — returned unchanged or\n * not at all, so byte-for-byte pass-through holds. `domain` is configurable, so this\n * field is not from a trusted source, and a downstream rendering it as a link would turn\n * `javascript:` or `data:` into script execution.\n */\nexport function extractConsoleUrl(err: unknown): string | undefined {\n  const data = readFailureData(err);\n  const nested = data?.error as Record<string, unknown> | undefined;\n  const candidate = stringOrUndefined(nested?.console_url) ?? stringOrUndefined(data?.console_url);\n  if (!candidate) return undefined;\n  try {\n    return new URL(candidate).protocol === 'https:' ? candidate : undefined;\n  } catch {\n    return undefined;\n  }\n}\n\nexport function classifyMeetingError(err: unknown): LarkChannelErrorCode {\n  const { status, feishuCode } = summarizeApiFailure(err);\n  const code = (err as { code?: unknown } | undefined)?.code;\n\n  if (feishuCode !== undefined && PERMISSION_CODES.has(feishuCode)) return 'permission_denied';\n  if (status === 401 || status === 403) return 'permission_denied';\n  if (status === 429) return 'rate_limited';\n  if (status === 400) return 'format_error';\n  if (status === 404) return 'target_revoked';\n  if (code === 'ECONNABORTED' || code === 'ETIMEDOUT') return 'send_timeout';\n  return 'unknown';\n}\n\n/**\n * Wrap a transport error as a {@link LarkChannelError} carrying no credentials.\n *\n * Already-classified errors pass through untouched so a rethrow does not nest.\n */\nexport function meetingError(err: unknown, context?: MeetingErrorContext): LarkChannelError {\n  if (err instanceof LarkChannelError) return err;\n\n  const failure = summarizeApiFailure(err);\n  const consoleUrl = extractConsoleUrl(err);\n  const merged = { ...context, ...(consoleUrl ? { consoleUrl } : {}) };\n\n  return new LarkChannelError(classifyMeetingError(err), failure.msg ?? String(err), {\n    cause: failure,\n    ...(Object.keys(merged).length > 0 ? { context: merged } : {}),\n  });\n}\n\n/** Whether retrying stands a chance, or the credential/request needs fixing first. */\nexport function isRetryableMeetingError(err: LarkChannelError): boolean {\n  if (err.code === 'permission_denied' || err.code === 'format_error') return false;\n  const status = (err.cause as ApiFailure | undefined)?.status;\n  if (status !== undefined) return RETRYABLE_STATUS.has(status);\n  // No HTTP status at all means the request never landed — a socket reset or a\n  // DNS blip, both of which a retry can genuinely clear.\n  return true;\n}\n\n/**\n * Whether a failure leaves the request's outcome unknown: no HTTP status, or one of\n * {@link INCONCLUSIVE_STATUS}. Keying on `ECONNABORTED`-style codes alone would miss\n * socket resets.\n */\nexport function isInconclusiveFailure(err: unknown): boolean {\n  if (err instanceof LarkChannelError) return false;\n  const status = summarizeApiFailure(err).status;\n  return status === undefined || INCONCLUSIVE_STATUS.has(status);\n}\n\nfunction numberOrUndefined(v: unknown): number | undefined {\n  return typeof v === 'number' ? v : undefined;\n}\n\nfunction stringOrUndefined(v: unknown): string | undefined {\n  return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n","/**\n * Parse-health counters for one inbound link.\n *\n * Counting \"activities received\" separately from \"activities that unpacked to nothing\"\n * separates \"the platform never sent it\" from \"it arrived and could not be read\". The\n * partial case (`0 < empty < received`) hides best: a single total looks healthy while\n * a subset vanishes.\n */\n\nimport type { MeetingActivityStats, MeetingLinkHealth } from './types';\n\n/** Which transport an activity arrived over. Health is counted per link, not per mode. */\nexport type MeetingLink = 'push' | 'poll';\n\n/**\n * Ceiling on distinct keys, matching `LoopGuard.MAX_KEYS`. The keys are server-chosen\n * strings and these counters live as long as the process.\n */\nconst MAX_DISTINCT_KEYS = 5000;\n\n/** Where types past the ceiling are folded, so totals stay honest. */\nconst OVERFLOW_KEY = '__other__';\n\nexport class MeetingHealth {\n  private received = 0;\n  private lastAt: number | undefined;\n  private readonly perType = new Map<string, MeetingActivityStats>();\n\n  /**\n   * Record one received activity. `itemCount` of 0 counts as empty, including for an\n   * unrecognized activity type — that is the \"SDK has fallen behind\" signal.\n   * `forwardCompatible` opts out, for a sub-variant the platform just added.\n   */\n  record(activityType: string, itemCount: number, opts?: { forwardCompatible?: boolean }): void {\n    this.received++;\n    this.lastAt = Date.now();\n\n    const key = this.keyFor(activityType);\n    const stats = this.perType.get(key) ?? { received: 0, empty: 0 };\n    stats.received++;\n    if (itemCount === 0 && !opts?.forwardCompatible) stats.empty++;\n    this.perType.set(key, stats);\n  }\n\n  stats(): Record<string, MeetingActivityStats> {\n    return Object.fromEntries([...this.perType].map(([k, v]) => [k, { ...v }]));\n  }\n\n  counters(): MeetingLinkHealth {\n    return {\n      received: this.received,\n      ...(this.lastAt ? { lastAt: this.lastAt } : {}),\n      stats: this.stats(),\n    };\n  }\n\n  private keyFor(activityType: string): string {\n    if (this.perType.has(activityType)) return activityType;\n    return this.perType.size >= MAX_DISTINCT_KEYS ? OVERFLOW_KEY : activityType;\n  }\n}\n","/**\n * Admission, routing and reclamation for meeting sessions.\n *\n * Sessions are started from outside this process — anyone who can pull the bot into a\n * meeting starts one — so admission is refused *before* `bots/join` goes out, and never\n * after, which would park the bot in a meeting with nothing listening.\n *\n * The cap counts server-side membership rather than live objects: `disconnect()`\n * disposes sessions without leaving their meetings, so a counter watching local objects\n * would read zero while the bot is still a participant everywhere.\n */\n\nimport type { Logger } from '../internal';\nimport { LarkChannelError } from '../types';\nimport { readPushActivities } from './normalize';\nimport type { LiveMeetingSession } from './session';\nimport type { MeetingMembership } from './types';\n\n/**\n * Keyed by mode as well as meeting id: one meeting can carry both an app-identity bot\n * and a user-identity follower, and a meeting-id-only key would silently replace one\n * with the other.\n */\nfunction sessionKey(mode: 'uat' | 'tat', meetingId: string): string {\n  return `${mode}:${meetingId}`;\n}\n\nexport class MeetingRegistry {\n  private readonly sessions = new Map<string, LiveMeetingSession>();\n  /**\n   * Meetings this process has joined and not left, session or no session, mapped to the\n   * number `joinMeeting` takes — the id alone cannot be rejoined.\n   */\n  private readonly membership = new Map<string, string>();\n\n  constructor(\n    private readonly logger: Logger,\n    private readonly maxConcurrentSessions: number,\n  ) {}\n\n  list(): LiveMeetingSession[] {\n    return [...this.sessions.values()];\n  }\n\n  /** Push events and `meeting_ended_v1` are app-identity, so they route to tat. */\n  get(meetingId: string): LiveMeetingSession | undefined {\n    return this.sessions.get(sessionKey('tat', meetingId));\n  }\n\n  /**\n   * Throws before any API call when the cap is reached.\n   *\n   * A meeting the bot is already in is exempt: after `disconnect()` the membership\n   * outlives the session, and re-attaching to it takes no new slot. Without the\n   * exemption a process at the cap could never recover the sessions it just disposed.\n   */\n  assertCanJoin(meetingNo?: string): void {\n    if (meetingNo !== undefined && [...this.membership.values()].includes(meetingNo)) return;\n    if (this.membership.size >= this.maxConcurrentSessions) {\n      throw new LarkChannelError(\n        'too_many_sessions',\n        `already in ${this.membership.size} meetings (maxConcurrentSessions)`,\n      );\n    }\n  }\n\n  /** Record that the bot is now a participant, whether or not a session lives. */\n  addMembership(meetingId: string, meetingNo: string): void {\n    this.membership.set(meetingId, meetingNo);\n  }\n\n  /**\n   * Meetings the bot is still in that have no live session — what `disconnect()` leaves\n   * behind. Nothing routes their pushes and nothing can leave them until a caller\n   * re-attaches, so this is the only way to find them again after losing the session\n   * references.\n   */\n  retained(): MeetingMembership[] {\n    return [...this.membership]\n      .filter(([meetingId]) => !this.sessions.has(sessionKey('tat', meetingId)))\n      .map(([meetingId, meetingNo]) => ({ meetingId, meetingNo }));\n  }\n\n  releaseMembership(meetingId: string): void {\n    this.membership.delete(meetingId);\n  }\n\n  /**\n   * Register a session, tearing down any predecessor under the same key.\n   *\n   * A replaced session would be unreachable but still running: not routed to, unable to\n   * remove itself (its `onEnded` identity check no longer matches), invisible to\n   * `disconnect()`, and for a follow session still polling with the caller's token.\n   */\n  add(session: LiveMeetingSession): void {\n    const key = sessionKey(session.mode, session.meetingId);\n    const previous = this.sessions.get(key);\n    this.sessions.set(key, session);\n\n    if (previous && previous !== session) {\n      this.logger.warn?.('meeting: replacing an existing session for this meeting', {\n        meetingId: session.meetingId,\n        mode: session.mode,\n      });\n      previous.dispose();\n    }\n  }\n\n  remove(session: LiveMeetingSession): void {\n    const key = sessionKey(session.mode, session.meetingId);\n    // Identity check: a replacement session must not be removed by its predecessor.\n    if (this.sessions.get(key) === session) this.sessions.delete(key);\n  }\n\n  /**\n   * Find a live session by meeting number — the only id `joinMeeting` is given.\n   * Mode-scoped, because handing a follow session to a `joinMeeting` caller would give\n   * them one whose `sendMessage` rejects.\n   */\n  findByMeetingNo(meetingNo: string, mode: 'uat' | 'tat'): LiveMeetingSession | undefined {\n    return this.list().find((s) => s.meetingNo === meetingNo && s.mode === mode);\n  }\n\n  /**\n   * Fan one `vc.bot.meeting_activity_v1` push out to the sessions it belongs to. The\n   * push is app-level, so each activity is routed by its own `meeting.id`; activities\n   * for meetings this process does not own are dropped at debug level.\n   */\n  async route(payload: unknown): Promise<void> {\n    for (const activity of readPushActivities(payload)) {\n      const session = activity.meetingId ? this.get(activity.meetingId) : undefined;\n      if (!session) {\n        this.logger.debug?.('meeting: activity for an unmanaged meeting', {\n          meetingId: activity.meetingId,\n          activityType: activity.activityType,\n        });\n        continue;\n      }\n      await session.deliver(activity, 'push');\n    }\n  }\n\n  /**\n   * Dispose every session without leaving any meeting. Membership is untouched: the\n   * slots stay taken because the participants remain.\n   */\n  disposeAll(): void {\n    for (const session of this.list()) session.dispose();\n  }\n}\n","/**\n * The `bot.events` page cursor, owned by the session.\n *\n * Both links read that endpoint — the follow path polls it, and the app path's probe\n * reuses it for gap recovery — so they share one cursor rather than each re-reading\n * what the other consumed.\n */\nexport interface Cursor {\n  get(): string | undefined;\n  /** Ignores `undefined`, so a response without a token cannot rewind. */\n  set(value: string | undefined): void;\n}\n\nexport function createCursor(): Cursor {\n  let value: string | undefined;\n  return {\n    get: () => value,\n    set: (next) => {\n      value = next ?? value;\n    },\n  };\n}\n","/**\n * Confirms a TAT session's bot is still in its meeting, and catches up on anything the\n * push stream dropped while doing it.\n *\n * `vc.bot.meeting_ended_v1` does not cover a host removing the bot or a meeting\n * changing hands, so this is the backstop for those.\n *\n * It reuses `bot.events` under the app identity, which needs no scope beyond the one\n * `bots/join` already requires. The same call is the push path's gap-recovery read:\n * whatever it returns is delivered and the shared cursor advances.\n *\n * Every verdict fails open. Probes across sessions share a cadence, so a probe failure\n * is correlated — one network blip, or one missing scope, hits every session in the\n * same tick. Reading \"I could not tell\" as \"the bot has left\" would end all of them at\n * once, while the opposite mistake is bounded by idle reclamation.\n */\n\nimport type { Client } from '@larksuiteoapi/node-sdk';\nimport type { Logger } from '../internal';\nimport type { Cursor } from './cursor';\nimport { classifyMeetingError } from './errors';\nimport type { RawActivity } from './normalize';\nimport { readPollActivities } from './normalize';\n\nexport type LivenessVerdict =\n  /** The service answered and the bot is still in the meeting. */\n  | 'active'\n  /** The service answered and confirmed the bot is not in the meeting. */\n  | 'gone'\n  /** No usable answer. Never ends a session. */\n  | 'unknown';\n\n/**\n * Feishu codes that positively mean \"this bot is not in that meeting\".\n *\n * `120004` (HTTP 403, `msg: \"bot is not in the meeting\"`) was observed live — a\n * membership statement rather than an auth or quota one, which is what makes it safe to\n * act on. Not to be confused with `120003 user is not in the meeting`, the user\n * identity's equivalent, which the follow path sees. Anything unlisted reads as\n * inconclusive.\n */\nexport const NOT_IN_MEETING_CODES: ReadonlySet<number> = new Set([120004]);\n\nexport interface LivenessProbeOptions {\n  client: Client;\n  meetingId: string;\n  logger: Logger;\n  cursor?: Cursor;\n  /** Deliver what the probe read, so the call doubles as gap recovery. */\n  onActivities?: (activities: RawActivity[]) => Promise<void>;\n}\n\nexport class LivenessProbe {\n  constructor(private readonly opts: LivenessProbeOptions) {}\n\n  /** Never throws: the caller has no failure branch to take. */\n  async check(): Promise<LivenessVerdict> {\n    const { client, meetingId, cursor, onActivities } = this.opts;\n    try {\n      const cursorValue = cursor?.get();\n      const res = await client.vc.v1.bot.events({\n        params: {\n          meeting_id: meetingId,\n          // Must be >= 20: smaller pages are rejected at field validation\n          // (`99992402`), before membership is ever considered.\n          page_size: 100,\n          user_id_type: 'open_id',\n          ...(cursorValue ? { page_token: cursorValue } : {}),\n        },\n      });\n\n      cursor?.set(res?.data?.page_token ?? cursorValue);\n      const activities = readPollActivities(res?.data);\n      // Overlap with the push stream is expected; the session serializes delivery, so\n      // the duplicate check suppresses it.\n      if (activities.length > 0) await onActivities?.(activities);\n\n      // A quiet meeting returns an empty list too, so it proves nothing either way.\n      return activities.length > 0 ? 'active' : 'unknown';\n    } catch (err) {\n      return this.classifyFailure(err);\n    }\n  }\n\n  private classifyFailure(err: unknown): LivenessVerdict {\n    const feishuCode = (err as { response?: { data?: { code?: unknown } } })?.response?.data?.code;\n    if (typeof feishuCode === 'number' && NOT_IN_MEETING_CODES.has(feishuCode)) return 'gone';\n\n    // Probe failures are expected background noise.\n    this.opts.logger.debug?.('meeting: liveness probe inconclusive', {\n      meetingId: this.opts.meetingId,\n      code: classifyMeetingError(err),\n    });\n    return 'unknown';\n  }\n}\n","/**\n * A sliding one-minute window over outbound in-meeting messages.\n *\n * The bot's own messages come back as `chat_received`, so a handler that replies without\n * checking `selfEcho` answers itself at network speed. This bounds the damage.\n */\n\nconst WINDOW_MS = 60_000;\n\nexport class SendRateLimiter {\n  private readonly sentAt: number[] = [];\n\n  constructor(private readonly maxPerMinute: number) {}\n\n  /** Consume one slot, or report that the window is full. */\n  tryAcquire(): boolean {\n    const cutoff = Date.now() - WINDOW_MS;\n    while (this.sentAt.length > 0 && this.sentAt[0] <= cutoff) this.sentAt.shift();\n\n    if (this.sentAt.length >= this.maxPerMinute) return false;\n    this.sentAt.push(Date.now());\n    return true;\n  }\n}\n","/**\n * Runs tasks one at a time.\n *\n * A session has two producers — the push router and the probe's gap-recovery read —\n * and letting them interleave breaks both the documented \"a handler is awaited before\n * the next item\" and the duplicate check, whose read-then-write would see two misses\n * for one re-sent activity.\n */\nexport class SerialQueue {\n  private tail: Promise<unknown> = Promise.resolve();\n\n  /**\n   * Queue `task` and resolve with its result. Awaiting the result applies\n   * backpressure; a rejection reaches the caller without stalling the queue.\n   *\n   * The barrier is published before the task can start: `.then` defers the call while\n   * `this.tail` is reassigned in the same synchronous step. A task's synchronous\n   * prefix can re-enter `run` — a settled caption invokes a handler, the handler calls\n   * `leave()`, teardown queues `end` — and any arrangement that runs the task first\n   * would let that nested call queue behind the *previous* barrier.\n   */\n  run<T>(task: () => Promise<T>): Promise<T> {\n    const result = this.tail.then(task, task);\n    this.tail = result.then(noop, noop);\n    return result;\n  }\n}\n\nfunction noop(): void {\n  /* a failed task must not stall everything behind it */\n}\n","/**\n * The follow-mode (user identity) event source.\n *\n * Two loops ride the same user access token: `bot.events` for activity, and\n * `userActiveMeeting` to notice the meeting has ended, since the follow path gets no end\n * event. A rejected credential must stop both — stopping one leaves the other retrying\n * forever with nothing to stop it.\n *\n * Empty rounds and failed rounds back off on separate counters, per loop. Sharing the\n * empty-poll counter would cap failures at its ceiling, and a rejected token retried\n * every ten seconds means thousands of authentication attempts an hour — against the\n * caller's own auth service, since the token provider is theirs.\n */\n\nimport { type Client, withUserAccessToken } from '@larksuiteoapi/node-sdk';\nimport type { Logger } from '../../internal';\nimport type { LarkChannelError } from '../../types';\nimport type { Cursor } from '../cursor';\nimport { isRetryableMeetingError, meetingError } from '../errors';\nimport { readPollActivities } from '../normalize';\nimport type { UserTokenProvider } from '../token';\nimport type { MeetingSourceCallbacks } from './types';\n\nconst EMPTY_BASE_MS = 3_000;\nconst EMPTY_MAX_MS = 10_000;\nconst FAILURE_MAX_MS = 60_000;\n\n/** Consecutive failures to absorb before giving up — roughly eight minutes at the backoff above. */\nconst MAX_CONSECUTIVE_FAILURES = 12;\n\n/** How often to re-check that the followed meeting is still active. */\nconst END_CHECK_INTERVAL_MS = 30_000;\n\nconst PAGE_SIZE = 100;\n\n/**\n * Back-to-back drain rounds allowed before pacing resumes. `has_more` drives an unpaced\n * loop, so a server that keeps saying \"more\" would otherwise spin flat out.\n */\nconst MAX_DRAIN_ROUNDS = 20;\n\n/** One loop's failure schedule; independent instances so the loops cannot exhaust each other. */\nclass FailureBackoff {\n  private rounds = 0;\n\n  reset(): void {\n    this.rounds = 0;\n  }\n\n  /** The next delay, or `null` when this loop has failed too many times running. */\n  next(): number | null {\n    this.rounds++;\n    if (this.rounds >= MAX_CONSECUTIVE_FAILURES) return null;\n    return Math.min(EMPTY_BASE_MS * 2 ** (this.rounds - 1), FAILURE_MAX_MS);\n  }\n}\n\nexport interface PollSourceOptions {\n  client: Client;\n  logger: Logger;\n  meetingId: string;\n  /** Always a provider, never a raw string — see `meeting/token.ts`. */\n  token: UserTokenProvider;\n  cursor: Cursor;\n  callbacks: MeetingSourceCallbacks;\n}\n\nexport class PollSource {\n  private emptyRounds = 0;\n  private drainRounds = 0;\n  private readonly pollFailures = new FailureBackoff();\n  private readonly endCheckFailures = new FailureBackoff();\n  private pollTimer?: NodeJS.Timeout;\n  private endCheckTimer?: NodeJS.Timeout;\n  private running = false;\n\n  constructor(private readonly opts: PollSourceOptions) {}\n\n  start(): void {\n    if (this.running) return;\n    this.running = true;\n    this.schedulePoll(0);\n    this.scheduleEndCheck(END_CHECK_INTERVAL_MS);\n  }\n\n  stop(): void {\n    this.running = false;\n    if (this.pollTimer) clearTimeout(this.pollTimer);\n    if (this.endCheckTimer) clearTimeout(this.endCheckTimer);\n    this.pollTimer = undefined;\n    this.endCheckTimer = undefined;\n  }\n\n  private schedulePoll(delayMs: number): void {\n    if (!this.running) return;\n    this.pollTimer = setTimeout(() => {\n      void this.poll();\n    }, delayMs);\n    this.pollTimer.unref?.();\n  }\n\n  private scheduleEndCheck(delayMs: number): void {\n    if (!this.running) return;\n    this.endCheckTimer = setTimeout(() => {\n      void this.checkStillActive();\n    }, delayMs);\n    this.endCheckTimer.unref?.();\n  }\n\n  // ─── activity loop ──────────────────────────────────────\n\n  private async poll(): Promise<void> {\n    if (!this.running) return;\n\n    try {\n      const { activities, hasMore } = await this.fetchActivities();\n      this.pollFailures.reset();\n\n      for (const activity of activities) {\n        if (!this.running) return;\n        await this.opts.callbacks.onActivity(activity);\n      }\n\n      this.schedulePoll(this.nextPollDelay(activities.length > 0, hasMore));\n    } catch (err) {\n      this.handleFailure(\n        meetingError(err, { meetingId: this.opts.meetingId }),\n        this.pollFailures,\n        (delay) => this.schedulePoll(delay),\n      );\n    }\n  }\n\n  private async fetchActivities() {\n    const cursorValue = this.opts.cursor.get();\n    const res = await this.opts.client.vc.v1.bot.events(\n      {\n        params: {\n          meeting_id: this.opts.meetingId,\n          page_size: PAGE_SIZE,\n          // Always open_id: any other convention would put actor ids in a\n          // different namespace from the bot's own, silently breaking selfEcho.\n          user_id_type: 'open_id',\n          ...(cursorValue ? { page_token: cursorValue } : {}),\n        },\n      },\n      withUserAccessToken(await this.opts.token()),\n    );\n    this.opts.cursor.set(res?.data?.page_token ?? cursorValue);\n    return { activities: readPollActivities(res?.data), hasMore: res?.data?.has_more === true };\n  }\n\n  /**\n   * A backlog drains at full speed; an idle meeting backs off. Pacing a backlog would\n   * make a busy meeting arrive minutes late, one page at a time.\n   */\n  private nextPollDelay(hadActivity: boolean, hasMore: boolean): number {\n    if (hasMore && this.drainRounds < MAX_DRAIN_ROUNDS) {\n      this.drainRounds++;\n      return 0;\n    }\n    this.drainRounds = 0;\n    if (hadActivity) {\n      this.emptyRounds = 0;\n      return EMPTY_BASE_MS;\n    }\n    const delay = Math.min(EMPTY_BASE_MS * 2 ** this.emptyRounds, EMPTY_MAX_MS);\n    this.emptyRounds++;\n    return delay;\n  }\n\n  // ─── end-of-meeting loop ────────────────────────────────\n\n  /**\n   * The follow path has no end event, so absence from the active list is the\n   * signal. Fails open: only a successful response that omits this meeting ends\n   * the session, because a failed request means \"unknown\", and probes across\n   * sessions fail together.\n   */\n  private async checkStillActive(): Promise<void> {\n    if (!this.running) return;\n\n    try {\n      const res = await this.opts.client.vc.v1.bot.userActiveMeeting(\n        { params: { user_id_type: 'open_id' } },\n        withUserAccessToken(await this.opts.token()),\n      );\n      this.endCheckFailures.reset();\n\n      const meetings = res?.data?.meetings;\n      if (Array.isArray(meetings) && !meetings.some((m) => m?.meeting_id === this.opts.meetingId)) {\n        this.stop();\n        this.opts.callbacks.onNoLongerActive();\n        return;\n      }\n      this.scheduleEndCheck(END_CHECK_INTERVAL_MS);\n    } catch (err) {\n      this.handleFailure(\n        meetingError(err, { meetingId: this.opts.meetingId }),\n        this.endCheckFailures,\n        (delay) => this.scheduleEndCheck(delay),\n        // Exhausting this loop must not end the session: activity may be flowing\n        // perfectly well, and these failures are correlated — one API, one 30s\n        // cadence, often one user — so terminating here would end every follow\n        // session in the same window, captions still streaming.\n        { terminateOnExhaustion: false },\n      );\n    }\n  }\n\n  // ─── shared failure policy ──────────────────────────────\n\n  /**\n   * One policy for both loops. A rejected credential always terminates, since it is\n   * shared; running out of retries only terminates for the loop carrying the session's\n   * actual purpose.\n   */\n  private handleFailure(\n    err: LarkChannelError,\n    backoff: FailureBackoff,\n    reschedule: (delayMs: number) => void,\n    opts: { terminateOnExhaustion?: boolean } = {},\n  ): void {\n    if (!this.running) return;\n    this.opts.callbacks.onError(err);\n\n    // A rejected credential cannot be retried into working, and every retry is\n    // another authentication attempt with a bad token.\n    if (!isRetryableMeetingError(err)) {\n      this.terminate(err);\n      return;\n    }\n\n    const delay = backoff.next();\n    if (delay !== null) {\n      reschedule(delay);\n      return;\n    }\n\n    if (opts.terminateOnExhaustion === false) {\n      // Keep checking, just slowly: losing end-detection is better than ending a\n      // session whose activity stream is healthy.\n      reschedule(FAILURE_MAX_MS);\n      return;\n    }\n    this.terminate(err);\n  }\n\n  private terminate(err: LarkChannelError): void {\n    this.stop();\n    this.opts.callbacks.onTerminate(err);\n  }\n}\n","/**\n * Caption settling.\n *\n * Nothing on the wire marks which send of a sentence is the final one, so\n * settling is a debounce: later sends of a `sentence_id` overwrite earlier ones,\n * and the caller hears about the sentence once it stops changing.\n *\n * Arrival order does not decide which send is later. A session ingests from two\n * transports — pushes, and the liveness probe's gap-recovery read on a shared cursor\n * that can lag behind them — so an earlier, shorter version of a sentence can arrive\n * after a longer one. `endMs` grows as the speaker keeps talking, which makes it the\n * ordering key; a strictly older `endMs` is ignored. Equal values keep last-arrival-wins,\n * because a transcription fix rewrites a sentence without extending it.\n *\n * Two ways to lose a caption, both silent, both avoided here. Tearing down while\n * a sentence is pending flushes it rather than dropping it with the timer — the\n * last thing said before a meeting ended is usually the part that mattered. And\n * when the pending buffer is full the oldest entry is pushed out to the caller,\n * not discarded: bounding memory is not a licence to lose data.\n */\n\nimport type { MeetingTranscriptEvent } from './types';\n\n/** Enough for a long meeting's worth of unsettled sentences; see the ceiling note. */\nconst DEFAULT_MAX_PENDING = 200;\n\nexport interface StabilizerOptions {\n  /** `0` forwards every update immediately. */\n  stabilizeMs: number;\n  maxPending?: number;\n  onFlush: (event: MeetingTranscriptEvent) => void;\n}\n\ninterface Pending {\n  event: MeetingTranscriptEvent;\n  timer: NodeJS.Timeout;\n}\n\nexport class TranscriptStabilizer {\n  private readonly stabilizeMs: number;\n  private readonly maxPending: number;\n  private readonly onFlush: (event: MeetingTranscriptEvent) => void;\n  /** Insertion-ordered, which is what makes \"evict the oldest\" well defined. */\n  private readonly pending = new Map<string, Pending>();\n  private disposed = false;\n\n  constructor(opts: StabilizerOptions) {\n    this.stabilizeMs = opts.stabilizeMs;\n    this.maxPending = opts.maxPending ?? DEFAULT_MAX_PENDING;\n    this.onFlush = opts.onFlush;\n  }\n\n  push(event: MeetingTranscriptEvent): void {\n    if (this.disposed) return;\n\n    // Without a debounce window, or without an id to debounce on, there is\n    // nothing to wait for.\n    if (this.stabilizeMs <= 0 || !event.sentenceId) {\n      this.onFlush(event);\n      return;\n    }\n\n    const key = event.sentenceId;\n    // Not even the timer is restarted for a stale send: a run of late arrivals would\n    // otherwise keep pushing the flush out while adding nothing.\n    if (this.isStale(event, this.pending.get(key)?.event)) return;\n\n    this.clearTimer(key);\n\n    const timer = setTimeout(() => this.flush(key), this.stabilizeMs);\n    timer.unref?.();\n    this.pending.set(key, { event, timer });\n\n    this.evictOldestIfFull(key);\n  }\n\n  /** Flush everything still pending. Idempotent. */\n  dispose(): void {\n    if (this.disposed) return;\n    this.disposed = true;\n    for (const key of [...this.pending.keys()]) this.flush(key);\n  }\n\n  /** True when `incoming` describes an earlier state of the sentence than `held`. */\n  private isStale(incoming: MeetingTranscriptEvent, held?: MeetingTranscriptEvent): boolean {\n    if (held?.endMs === undefined || incoming.endMs === undefined) return false;\n    return incoming.endMs < held.endMs;\n  }\n\n  private evictOldestIfFull(justAdded: string): void {\n    while (this.pending.size > this.maxPending) {\n      const oldest = this.pending.keys().next().value;\n      if (oldest === undefined || oldest === justAdded) break;\n      this.flush(oldest);\n    }\n  }\n\n  private flush(key: string): void {\n    const entry = this.pending.get(key);\n    if (!entry) return;\n    clearTimeout(entry.timer);\n    this.pending.delete(key);\n    this.onFlush(entry.event);\n  }\n\n  private clearTimer(key: string): void {\n    const existing = this.pending.get(key);\n    if (existing) clearTimeout(existing.timer);\n  }\n}\n","/**\n * One meeting, as the caller sees it.\n *\n * Three invariants shape this file.\n *\n * Teardown never depends on an API call succeeding: `bots/leave` is most likely to fail\n * exactly when a meeting has just ended, which is the ordinary end of every meeting.\n *\n * Ending the session and giving up the bot's seat are separate steps. `dispose()` ends\n * without leaving, so a reconnect does not evict the bot; `leave()` gives up the seat and\n * stays callable after the session has ended.\n *\n * Delivery is strictly ordered and handlers are awaited, because array position carries\n * meaning — a share hand-off arrives as an `ended` followed by a `started` in one\n * delivery. A slow handler therefore holds up the stream.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { inspect } from 'node:util';\nimport type { Client } from '@larksuiteoapi/node-sdk';\nimport type { Logger } from '../internal';\nimport { LarkChannelError } from '../types';\nimport { type Cursor, createCursor } from './cursor';\nimport type { MeetingDedup } from './dedup';\nimport { meetingError } from './errors';\nimport { MeetingHealth, type MeetingLink } from './health';\nimport { LivenessProbe } from './liveness';\nimport { normalizeActivity, type RawActivity } from './normalize';\nimport { SendRateLimiter } from './rate-limit';\nimport { SerialQueue } from './serial-queue';\nimport { PollSource } from './sources/poll-source';\nimport { TranscriptStabilizer } from './stabilizer';\nimport type { UserTokenProvider } from './token';\nimport type {\n  MeetingActivityStats,\n  MeetingEndReason,\n  MeetingEventMap,\n  MeetingEventName,\n  MeetingSession,\n  MeetingTranscriptEvent,\n  Unsubscribe,\n} from './types';\n\nexport interface ResolvedMeetingConfig {\n  maxConcurrentSessions: number;\n  idleTimeoutMs: number;\n  livenessProbeIntervalMs: number;\n  sendRateLimitPerMinute: number;\n}\n\n/** Same shape as {@link MeetingHealth.record}, plus the link the activity arrived over. */\nexport type RecordActivity = (\n  link: MeetingLink,\n  activityType: string,\n  itemCount: number,\n  opts?: { forwardCompatible?: boolean },\n) => void;\n\nexport interface MeetingSessionDeps {\n  client: Client;\n  logger: Logger;\n  meetingId: string;\n  meetingNo: string;\n  topic?: string;\n  mode: 'uat' | 'tat';\n  config: ResolvedMeetingConfig;\n  dedup: MeetingDedup;\n  includeRaw: boolean;\n  /** Resolved late: the bot's own id is not known until the channel connects. */\n  botOpenId: () => string | undefined;\n  stabilizeMs: number;\n  /** Follow mode only, and always a provider so no token becomes a property. */\n  token?: UserTokenProvider;\n  /** Channel-wide counters, alongside this session's own. */\n  recordHealth: RecordActivity;\n  /** Drop this session from the registry. */\n  onEnded: (session: LiveMeetingSession) => void;\n  /** Give back the concurrency slot. Only when the bot is really out. */\n  onMembershipReleased: () => void;\n}\n\nexport class LiveMeetingSession implements MeetingSession {\n  readonly meetingId: string;\n  readonly meetingNo: string;\n  readonly topic?: string;\n  readonly mode: 'uat' | 'tat';\n\n  /**\n   * Replaceable on purpose: the wire signal for \"not in this meeting\" has not\n   * been observed yet, so this is the one seam where that classification lives.\n   */\n  liveness?: LivenessProbe;\n\n  private readonly handlers = new Map<MeetingEventName, Set<(payload: never) => unknown>>();\n  private readonly health = new MeetingHealth();\n  private readonly rateLimiter: SendRateLimiter;\n  private readonly stabilizer: TranscriptStabilizer;\n  /** Shared by the poll loop and the liveness probe — see {@link Cursor}. */\n  private readonly cursor: Cursor = createCursor();\n  private source?: PollSource;\n  private idleTimer?: NodeJS.Timeout;\n  private probeTimer?: NodeJS.Timeout;\n  private ended = false;\n  /** The bot is a participant of this meeting and the slot is still taken. */\n  private membershipHeld: boolean;\n  /**\n   * Serializes everything the caller observes: two producers feed a session, so this is\n   * what keeps ordering true across both and the duplicate check atomic.\n   */\n  private readonly queue = new SerialQueue();\n\n  constructor(private readonly deps: MeetingSessionDeps) {\n    this.meetingId = deps.meetingId;\n    this.meetingNo = deps.meetingNo;\n    this.topic = deps.topic;\n    this.mode = deps.mode;\n    this.membershipHeld = deps.mode === 'tat';\n    this.rateLimiter = new SendRateLimiter(deps.config.sendRateLimitPerMinute);\n    this.stabilizer = new TranscriptStabilizer({\n      stabilizeMs: deps.stabilizeMs,\n      onFlush: (event) => {\n        void this.queue.run(() => this.emit('transcript', event));\n      },\n    });\n  }\n\n  /** Begin whatever this mode needs: a poll loop, or timers around the push feed. */\n  start(): void {\n    if (this.mode === 'uat') {\n      this.startPolling();\n      return;\n    }\n\n    if (this.deps.config.livenessProbeIntervalMs > 0) {\n      this.liveness = new LivenessProbe({\n        client: this.deps.client,\n        meetingId: this.meetingId,\n        logger: this.deps.logger,\n        cursor: this.cursor,\n        // The probe's read is also the push path's gap recovery: whatever it\n        // pulled is delivered rather than discarded.\n        onActivities: async (activities) => {\n          // Recovered over REST, so it counts as poll even on an app-identity session.\n          for (const activity of activities) await this.deliver(activity, 'poll');\n        },\n      });\n      this.scheduleProbe();\n    }\n    this.resetIdleTimer();\n  }\n\n  private startPolling(): void {\n    const token = this.deps.token;\n    if (!token) throw new LarkChannelError('format_error', 'follow mode requires a token provider');\n\n    this.source = new PollSource({\n      client: this.deps.client,\n      logger: this.deps.logger,\n      meetingId: this.meetingId,\n      token,\n      cursor: this.cursor,\n      callbacks: {\n        onActivity: (activity) => this.deliver(activity, 'poll'),\n        onError: (err) => this.emitError(err),\n        onTerminate: () => {\n          this.endOnce('error');\n        },\n        onNoLongerActive: () => {\n          this.endOnce('no_longer_active');\n        },\n      },\n    });\n    this.source.start();\n  }\n\n  // ─── subscription ───────────────────────────────────────\n\n  on<K extends MeetingEventName>(name: K, handler: MeetingEventMap[K]): Unsubscribe {\n    let set = this.handlers.get(name);\n    if (!set) {\n      set = new Set();\n      this.handlers.set(name, set);\n    }\n    const fn = handler as (payload: never) => unknown;\n    set.add(fn);\n    return () => {\n      set?.delete(fn);\n    };\n  }\n\n  // ─── inbound ────────────────────────────────────────────\n\n  /**\n   * Unpack one activity and hand its items to the caller in order. Health is counted\n   * before dedup, since a suppressed duplicate did arrive.\n   */\n  async deliver(activity: RawActivity, link: MeetingLink): Promise<void> {\n    return this.queue.run(() => this.deliverNow(activity, link));\n  }\n\n  private async deliverNow(activity: RawActivity, link: MeetingLink): Promise<void> {\n    if (this.ended) return;\n\n    const { events, forwardCompatible } = normalizeActivity(activity, {\n      meetingId: this.meetingId,\n      mode: this.mode,\n      botOpenId: this.deps.botOpenId(),\n      includeRaw: this.deps.includeRaw,\n    });\n\n    this.health.record(activity.activityType, events.length, { forwardCompatible });\n    this.deps.recordHealth(link, activity.activityType, events.length, { forwardCompatible });\n    this.resetIdleTimer();\n\n    // Scoped per session, not per meeting: a follower and an in-meeting bot can\n    // both be watching this meeting through the same endpoint.\n    if (await this.deps.dedup.isDuplicate(activity, `${this.mode}:${this.meetingId}`)) return;\n\n    for (const { name, event } of events) {\n      if (this.ended) return;\n      if (name === 'transcript' && this.deps.stabilizeMs > 0) {\n        // Settling is out-of-band by definition, so the caller already accepted\n        // relaxed ordering across sentences by setting a window.\n        this.stabilizer.push(event as MeetingTranscriptEvent);\n        continue;\n      }\n      await this.emit(name, event);\n    }\n  }\n\n  getStats(): Record<string, MeetingActivityStats> {\n    return this.health.stats();\n  }\n\n  // ─── logging representation ─────────────────────────────\n\n  /**\n   * A small representation for logging: without it both `JSON.stringify` and\n   * `util.inspect` walk into the SDK `Client`, which is circular.\n   */\n  toJSON(): object {\n    return this.describe();\n  }\n\n  [inspect.custom](): object {\n    return this.describe();\n  }\n\n  private describe(): object {\n    return {\n      meetingId: this.meetingId,\n      meetingNo: this.meetingNo,\n      topic: this.topic,\n      mode: this.mode,\n      ended: this.ended,\n    };\n  }\n\n  // ─── outbound ───────────────────────────────────────────\n\n  /**\n   * Post a text message into the meeting chat.\n   *\n   * `content` goes out as plain text, unlike IM: `im.v1.message.create` wants a JSON\n   * string (`'{\"text\":\"hi\"}'`), while `vc.v1.bot.message` displays whatever it is given\n   * verbatim, so the IM encoding would show up as a JSON literal in the meeting. Any\n   * future `msg_type` here must have its own encoding confirmed against a live meeting —\n   * the generated types say only `content?: string`.\n   */\n  async sendMessage(text: string): Promise<void> {\n    if (this.mode !== 'tat') {\n      throw new LarkChannelError(\n        'not_supported',\n        'sendMessage requires the bot to be in the meeting; follow mode cannot post',\n      );\n    }\n    if (this.ended) {\n      throw new LarkChannelError('not_supported', 'this meeting session has already ended');\n    }\n    if (!this.rateLimiter.tryAcquire()) {\n      throw new LarkChannelError(\n        'rate_limited',\n        'in-meeting message rate limit exceeded for this session',\n      );\n    }\n\n    try {\n      await this.deps.client.vc.v1.bot.message({\n        data: {\n          meeting_id: this.meetingId,\n          msg_type: 'text',\n          content: text,\n          uuid: randomUUID(),\n        },\n      });\n    } catch (err) {\n      throw meetingError(err, { meetingId: this.meetingId });\n    }\n  }\n\n  // ─── teardown ───────────────────────────────────────────\n\n  /**\n   * End the session without leaving the meeting. Idempotent.\n   *\n   * The bot stays a participant, which is what makes a reconnect safe — and why a\n   * process must still `leave()` before exiting.\n   */\n  dispose(): void {\n    this.endOnce('disposed');\n  }\n\n  /**\n   * Leave the meeting and give up the slot, then end the session. Idempotent, and\n   * still effective after the session has already ended by another route.\n   */\n  async leave(): Promise<void> {\n    this.endOnce('left');\n    await this.giveUpSeat();\n  }\n\n  /** Reaction to `vc.bot.meeting_ended_v1`: end, then leave the meeting once. */\n  async endedByPlatform(): Promise<void> {\n    this.endOnce('meeting_ended');\n    await this.giveUpSeat();\n  }\n\n  /**\n   * Call `bots/leave` once and release the slot regardless of the outcome — released on\n   * attempt, or a permanently failing leave would burn a slot for the life of the process.\n   */\n  private async giveUpSeat(): Promise<void> {\n    if (!this.membershipHeld) return;\n    this.membershipHeld = false;\n\n    try {\n      await this.deps.client.vc.v1.bot.leave({ data: { meeting_id: this.meetingId } });\n    } catch (err) {\n      this.emitError(meetingError(err, { meetingId: this.meetingId }));\n    } finally {\n      this.deps.onMembershipReleased();\n    }\n  }\n\n  /** The bot is already out (the server said so), so release without calling. */\n  private releaseSeatWithoutLeaving(): void {\n    if (!this.membershipHeld) return;\n    this.membershipHeld = false;\n    this.deps.onMembershipReleased();\n  }\n\n  /**\n   * Stop everything and announce the end, exactly once. Returns false when the session\n   * had already ended, without preventing `leave()` from still giving up the seat.\n   */\n  private endOnce(reason: MeetingEndReason): boolean {\n    if (this.ended) return false;\n    this.ended = true;\n\n    this.source?.stop();\n    if (this.idleTimer) clearTimeout(this.idleTimer);\n    if (this.probeTimer) clearTimeout(this.probeTimer);\n    this.idleTimer = undefined;\n    this.probeTimer = undefined;\n    // Flushes pending captions rather than dropping them with the timers. Queued\n    // onto the same chain as `end`, so a settled caption cannot arrive after it.\n    this.stabilizer.dispose();\n\n    this.deps.onEnded(this);\n    void this.queue.run(() => this.emit('end', { meetingId: this.meetingId, reason }));\n    return true;\n  }\n\n  // ─── timers ─────────────────────────────────────────────\n\n  /** App-identity only: a follow session has its own end signal and a healthy poll loop. */\n  private resetIdleTimer(): void {\n    const { idleTimeoutMs } = this.deps.config;\n    if (this.ended || this.mode !== 'tat' || idleTimeoutMs <= 0) return;\n    if (this.idleTimer) clearTimeout(this.idleTimer);\n    this.idleTimer = setTimeout(() => this.reclaimIdle(), idleTimeoutMs);\n    this.idleTimer.unref?.();\n  }\n\n  /** Silent for too long: end, and hand the seat back rather than burning it. */\n  private reclaimIdle(): void {\n    if (!this.endOnce('idle_timeout')) return;\n    void this.giveUpSeat();\n  }\n\n  private scheduleProbe(): void {\n    if (this.ended) return;\n    this.probeTimer = setTimeout(() => {\n      void this.probe();\n    }, this.deps.config.livenessProbeIntervalMs);\n    this.probeTimer.unref?.();\n  }\n\n  /** Only a confirmed departure ends the session; see {@link LivenessProbe}. */\n  private async probe(): Promise<void> {\n    if (this.ended || !this.liveness) return;\n    const verdict = await this.liveness.check();\n    if (this.ended) return;\n\n    if (verdict === 'gone') {\n      if (this.endOnce('no_longer_active')) {\n        // The server already confirmed the bot is out, so calling `bots/leave`\n        // would be pointless — but the slot must still come back.\n        this.releaseSeatWithoutLeaving();\n      }\n      return;\n    }\n    this.scheduleProbe();\n  }\n\n  // ─── emit ───────────────────────────────────────────────\n\n  private async emit(name: MeetingEventName, payload: unknown): Promise<void> {\n    const handlers = this.handlers.get(name);\n    if (!handlers || handlers.size === 0) return;\n\n    // Snapshot: a handler may unsubscribe during its own delivery.\n    for (const handler of [...handlers]) {\n      try {\n        await (handler as (p: unknown) => unknown)(payload);\n      } catch (err) {\n        this.emitError(meetingError(err, { meetingId: this.meetingId }));\n      }\n    }\n  }\n\n  private emitError(err: LarkChannelError): void {\n    const handlers = this.handlers.get('error');\n    if (handlers && handlers.size > 0) {\n      for (const handler of [...handlers]) {\n        try {\n          (handler as (p: unknown) => unknown)(err);\n        } catch {\n          // An error handler that throws has nowhere left to report to.\n        }\n      }\n      return;\n    }\n    // Message body stays a constant: meeting content is participant-authored, and\n    // interpolating it would let anyone in the meeting forge log lines.\n    this.deps.logger.error?.('meeting: unhandled session error', {\n      meetingId: this.meetingId,\n      code: err.code,\n      message: err.message,\n      cause: err.cause,\n    });\n  }\n}\n","/**\n * User access token handling for the follow path.\n *\n * Everything downstream takes a function, never a string: a string would sit on the\n * session and its poll loop as an enumerable property, where `{...session}` or\n * `Object.entries` would find it. A closure variable is not a property.\n */\n\nimport { LarkChannelError } from '../types';\nimport type { MeetingTokenSource } from './types';\n\nexport type UserTokenProvider = () => Promise<string>;\n\n/**\n * Normalize either form of {@link MeetingTokenSource} into a provider. A function is\n * re-invoked per request, which is how a meeting outlives a shorter-lived token.\n */\nexport function toTokenProvider(source: MeetingTokenSource): UserTokenProvider {\n  if (typeof source === 'function') return async () => validate(await source());\n  const fixed = validate(source);\n  return async () => fixed;\n}\n\nfunction validate(token: unknown): string {\n  if (typeof token !== 'string' || token.length === 0) {\n    throw new LarkChannelError('format_error', 'userAccessToken resolved to an empty value');\n  }\n  return token;\n}\n","/**\n * The meeting channel: entry points, dispatcher handlers, and the registry that\n * ties them together.\n *\n * Kept out of `channel.ts` so the boundary is visible in the file tree — the IM\n * path and its error handling are deliberately untouched by this work.\n */\n\nimport type { Cache } from '@larksuiteoapi/node-sdk';\nimport { type Client, withUserAccessToken } from '@larksuiteoapi/node-sdk';\nimport type { Logger } from '../internal';\nimport { type EventMap, LarkChannelError } from '../types';\nimport { asDict, asMs, asString } from './coerce';\nimport { MeetingDedup } from './dedup';\nimport { isInconclusiveFailure, meetingError } from './errors';\nimport { MeetingHealth, type MeetingLink } from './health';\nimport { readActor } from './normalize';\nimport { MeetingRegistry } from './registry';\nimport { LiveMeetingSession, type ResolvedMeetingConfig } from './session';\nimport { toTokenProvider, type UserTokenProvider } from './token';\nimport type {\n  FollowMeetingOptions,\n  JoinMeetingOptions,\n  MeetingChannelConfig,\n  MeetingEventHealth,\n  MeetingInvitedEvent,\n  MeetingMembership,\n  MeetingSession,\n} from './types';\n\nconst DEFAULTS: ResolvedMeetingConfig = {\n  maxConcurrentSessions: 32,\n  // Off by default: the liveness probe detects a removed bot directly, so the only\n  // sessions idle reclamation would still reach are quiet-but-live meetings — and\n  // reclaiming one means leaving it. See MeetingChannelConfig.idleTimeoutMs.\n  idleTimeoutMs: 0,\n  livenessProbeIntervalMs: 5 * 60_000,\n  sendRateLimitPerMinute: 20,\n};\n\n/** `join_type` is fixed by the protocol; the field exists but has one legal value. */\nconst JOIN_TYPE_BY_MEETING_NO = 1;\n\nexport interface MeetingChannelDeps {\n  client: Client;\n  logger: Logger;\n  cache: Cache;\n  config?: MeetingChannelConfig;\n  includeRaw: boolean;\n  botOpenId: () => string | undefined;\n  isConnected: () => boolean;\n  /** The channel's single-slot `meetingInvited` handler, read at dispatch time. */\n  invitedHandler: () => EventMap['meetingInvited'] | undefined;\n  /** Where anything a dispatcher handler throws goes, instead of the transport. */\n  onError: (err: unknown) => void;\n}\n\nexport class MeetingChannel {\n  private readonly config: ResolvedMeetingConfig;\n  private readonly registry: MeetingRegistry;\n  private readonly dedup: MeetingDedup;\n  /**\n   * Joins currently in flight, by meeting number.\n   *\n   * The \"already in this meeting\" check cannot cover a concurrent pair on its own:\n   * both callers look before either has a session, so both would call `bots/join`\n   * and the second would replace the first's session. Feishu redelivers\n   * `meeting_invited_v1` and the documented handler joins unconditionally, so the\n   * pair is routine rather than hypothetical.\n   */\n  private readonly joining = new Map<string, Promise<MeetingSession>>();\n\n  /**\n   * One counter per link, owned here rather than by the registry: the registry keys\n   * sessions and membership, and has no view of which transport an activity came in on\n   * — nor of push registration, which is the channel's own act.\n   */\n  private readonly linkHealth: Record<MeetingLink, MeetingHealth> = {\n    push: new MeetingHealth(),\n    poll: new MeetingHealth(),\n  };\n\n  private pushRegistered = false;\n  private pushUnregisteredReason: string | undefined = 'channel not connected';\n\n  constructor(private readonly deps: MeetingChannelDeps) {\n    this.config = { ...DEFAULTS, ...stripUndefined(deps.config) };\n    this.registry = new MeetingRegistry(deps.logger, this.config.maxConcurrentSessions);\n    this.dedup = new MeetingDedup(deps.cache);\n  }\n\n  /** Live sessions. Also the seam teardown assertions look at. */\n  list(): LiveMeetingSession[] {\n    return this.registry.list();\n  }\n\n  health(): MeetingEventHealth {\n    return {\n      push: {\n        registered: this.pushRegistered,\n        ...(this.pushUnregisteredReason ? { reason: this.pushUnregisteredReason } : {}),\n        ...this.linkHealth.push.counters(),\n      },\n      poll: {\n        sessions: this.registry.list().filter((s) => s.mode === 'uat').length,\n        ...this.linkHealth.poll.counters(),\n      },\n    };\n  }\n\n  markRegistered(): void {\n    this.pushRegistered = true;\n    this.pushUnregisteredReason = undefined;\n  }\n\n  /** Meetings the bot is in with no session listening. See {@link MeetingRegistry.retained}. */\n  retainedMeetings(): MeetingMembership[] {\n    return this.registry.retained();\n  }\n\n  disposeAll(): void {\n    this.registry.disposeAll();\n  }\n\n  // ─── entry points ───────────────────────────────────────\n\n  /**\n   * Put the bot in a meeting as a visible participant.\n   *\n   * Requires a live connection: this path depends on `meeting_activity_v1`\n   * pushes, so without one the bot would join and then hear nothing at all —\n   * failing loudly beats joining deaf.\n   */\n  async joinMeeting(meetingNo: string, opts: JoinMeetingOptions = {}): Promise<MeetingSession> {\n    if (!this.deps.isConnected()) {\n      throw new LarkChannelError(\n        'not_connected',\n        'joinMeeting needs the event connection for in-meeting activity — call connect() first',\n      );\n    }\n\n    const existing = this.registry.findByMeetingNo(meetingNo, 'tat');\n    if (existing) {\n      this.deps.logger.debug?.('meeting: already in this meeting, reusing the session', {\n        meetingId: existing.meetingId,\n      });\n      return existing;\n    }\n\n    // Overlapping calls share one join rather than racing to replace each other.\n    const inFlight = this.joining.get(meetingNo);\n    if (inFlight) return inFlight;\n\n    this.registry.assertCanJoin(meetingNo);\n    const attempt = this.performJoin(meetingNo, opts).finally(() => {\n      this.joining.delete(meetingNo);\n    });\n    this.joining.set(meetingNo, attempt);\n    return attempt;\n  }\n\n  private async performJoin(meetingNo: string, opts: JoinMeetingOptions): Promise<MeetingSession> {\n    const meeting = await this.callJoin(meetingNo, opts);\n    const meetingId = meeting?.id;\n    if (!meetingId) {\n      throw new LarkChannelError('meeting_not_found', 'bots/join returned no meeting id');\n    }\n\n    this.registry.addMembership(meetingId, meeting.meeting_no ?? meetingNo);\n    return this.startSession({\n      meetingId,\n      meetingNo: meeting.meeting_no ?? meetingNo,\n      topic: meeting.topic,\n      mode: 'tat',\n      stabilizeMs: opts.stabilizeMs ?? 0,\n    });\n  }\n\n  /**\n   * Follow the meeting the token's owner is currently in, without joining it.\n   *\n   * Deliberately does not require `connect()`: this path is REST polling only, so\n   * demanding a WebSocket would be pure overhead for an app that never touches\n   * the IM side.\n   */\n  async followMyMeeting(opts: FollowMeetingOptions): Promise<MeetingSession> {\n    // Normalized to a provider immediately: a raw string handed further down would\n    // become an enumerable property on the session and its poll loop.\n    const token = toTokenProvider(opts.userAccessToken);\n\n    const meetings = await this.listActiveMeetings(token);\n    const chosen = opts.meetingNo\n      ? meetings.find((m) => m.meeting_no === opts.meetingNo)\n      : meetings[0];\n\n    if (!chosen?.meeting_id) {\n      throw new LarkChannelError(\n        'meeting_not_found',\n        opts.meetingNo\n          ? 'the requested meeting is not among the active meetings'\n          : 'no active meeting to follow',\n      );\n    }\n\n    if (!opts.meetingNo && meetings.length > 1) {\n      // No titles: they are free text from whoever created the meeting, and one\n      // containing newlines can forge log lines. No meeting numbers for the\n      // meetings we are *not* following either — for a meeting without a password\n      // the number is itself the credential to join it, and the caller did not ask\n      // about those. Only the followed one's number is echoed back.\n      this.deps.logger.warn?.('meeting: several active meetings, following the first', {\n        followedMeetingNo: chosen.meeting_no,\n        otherActiveCount: meetings.length - 1,\n      });\n    }\n\n    return this.startSession({\n      meetingId: chosen.meeting_id,\n      meetingNo: chosen.meeting_no ?? '',\n      topic: chosen.meeting_title,\n      mode: 'uat',\n      stabilizeMs: opts.stabilizeMs ?? 0,\n      token,\n    });\n  }\n\n  // ─── dispatcher ─────────────────────────────────────────\n\n  /**\n   * The three `vc.bot.*` handlers the channel registers internally.\n   *\n   * Each is guarded, matching how the IM built-ins are written. The failure that\n   * matters is the documented one: the `meetingInvited` handler is *supposed* to\n   * call `joinMeeting()`, which rejects on `too_many_sessions` /\n   * `permission_denied` / `not_connected` — unguarded, that becomes an unhandled\n   * rejection at the transport instead of reaching `channel.on('error')`.\n   */\n  handlers(): Record<string, (raw: unknown) => Promise<unknown>> {\n    return {\n      'vc.bot.meeting_invited_v1': (raw) =>\n        this.guard(async () => {\n          const handler = this.deps.invitedHandler();\n          if (handler) await handler(toInvitedEvent(raw, this.deps.includeRaw));\n        }),\n\n      'vc.bot.meeting_activity_v1': (raw) => this.guard(() => this.registry.route(raw)),\n\n      'vc.bot.meeting_ended_v1': (raw) =>\n        this.guard(async () => {\n          const meetingId = asString(readMeeting(raw)?.id);\n          await this.registry.get(meetingId ?? '')?.endedByPlatform();\n        }),\n    };\n  }\n\n  /** Route a handler failure to the channel's `error` event, never to the transport. */\n  private async guard(run: () => Promise<void>): Promise<undefined> {\n    try {\n      await run();\n    } catch (err) {\n      this.deps.onError(meetingError(err));\n    }\n    return undefined;\n  }\n\n  // ─── internals ──────────────────────────────────────────\n\n  private async callJoin(\n    meetingNo: string,\n    opts: JoinMeetingOptions,\n  ): Promise<{ id?: string; meeting_no?: string; topic?: string } | undefined> {\n    try {\n      const res = await this.deps.client.vc.v1.bot.join({\n        data: {\n          join_type: JOIN_TYPE_BY_MEETING_NO,\n          join_identify: { meeting_no: meetingNo },\n          ...(opts.password ? { password: opts.password } : {}),\n          ...(opts.callId ? { call_id: opts.callId } : {}),\n        },\n      });\n      return res?.data?.meeting;\n    } catch (err) {\n      // \"Sent, but no usable answer\" is the shape that leaves orphan participants\n      // behind — not just the two timeout error codes. See `isInconclusiveFailure`.\n      if (isInconclusiveFailure(err)) this.warnAboutInconclusiveJoin(meetingNo);\n      throw meetingError(err);\n    }\n  }\n\n  /**\n   * A join whose outcome is unknown may well have succeeded server-side, leaving a\n   * participant with no local handle — a bot visible in a meeting that nothing is\n   * listening to, until the meeting ends.\n   *\n   * Nothing can be done about it automatically, and this used to try. The long\n   * meeting id was in the response that never arrived, so the only handle available\n   * is the meeting number — and `bots/leave` was observed to reject one outright\n   * (HTTP 400, `121105 meeting not exist`). Issuing that call was therefore a request\n   * guaranteed to fail, on a path that had already failed. So this warns and stops:\n   * reclaiming the orphan needs an operator, or the meeting ending on its own.\n   */\n  private warnAboutInconclusiveJoin(meetingNo: string): void {\n    this.deps.logger.warn?.(\n      'meeting: join outcome unknown — the bot may be a participant with no session, ' +\n        'and cannot be removed automatically (bots/leave needs the long meeting id, ' +\n        'which never arrived)',\n      { meetingNo },\n    );\n  }\n\n  private async listActiveMeetings(\n    token: UserTokenProvider,\n  ): Promise<Array<{ meeting_id?: string; meeting_no?: string; meeting_title?: string }>> {\n    try {\n      const res = await this.deps.client.vc.v1.bot.userActiveMeeting(\n        { params: { user_id_type: 'open_id' } },\n        withUserAccessToken(await token()),\n      );\n      return res?.data?.meetings ?? [];\n    } catch (err) {\n      throw meetingError(err);\n    }\n  }\n\n  private startSession(spec: {\n    meetingId: string;\n    meetingNo: string;\n    topic?: string;\n    mode: 'uat' | 'tat';\n    stabilizeMs: number;\n    token?: UserTokenProvider;\n  }): MeetingSession {\n    const session = new LiveMeetingSession({\n      client: this.deps.client,\n      logger: this.deps.logger,\n      meetingId: spec.meetingId,\n      meetingNo: spec.meetingNo,\n      topic: spec.topic,\n      mode: spec.mode,\n      config: this.config,\n      dedup: this.dedup,\n      includeRaw: this.deps.includeRaw,\n      botOpenId: this.deps.botOpenId,\n      stabilizeMs: spec.stabilizeMs,\n      token: spec.token,\n      recordHealth: (link, type, count, opts) => this.linkHealth[link].record(type, count, opts),\n      onEnded: (s) => this.registry.remove(s),\n      onMembershipReleased: () => this.registry.releaseMembership(spec.meetingId),\n    });\n\n    this.registry.add(session);\n    session.start();\n    return session;\n  }\n}\n\n// ─────────────────────────────────────────────────────────────\n\nfunction stripUndefined(config?: MeetingChannelConfig): Partial<ResolvedMeetingConfig> {\n  if (!config) return {};\n  return Object.fromEntries(\n    Object.entries(config).filter(([, v]) => v !== undefined),\n  ) as Partial<ResolvedMeetingConfig>;\n}\n\nfunction readMeeting(raw: unknown): Record<string, unknown> | undefined {\n  return asDict((raw as { meeting?: unknown } | undefined)?.meeting);\n}\n\nfunction toInvitedEvent(raw: unknown, includeRaw: boolean): MeetingInvitedEvent {\n  const event = (asDict(raw) ?? {}) as Record<string, unknown>;\n  const meeting = readMeeting(raw);\n  return {\n    meetingNo: asString(meeting?.meeting_no) ?? '',\n    meetingId: asString(meeting?.id),\n    topic: asString(meeting?.topic),\n    inviter: readActor({ operator: event.inviter }),\n    bot: readActor({ operator: event.bot }),\n    callId: asString(event.call_id),\n    inviteTime: asMs(event.invite_time),\n    ...(includeRaw ? { raw } : {}),\n  };\n}\n\nexport type { LiveMeetingSession };\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// Emitted when sub-message fetch fails after retries (or on a non-transient\n// error). Kept distinct from the bare `<forwarded_messages/>` empty tag so\n// downstream can tell \"fetch failed\" apart from \"genuinely empty\" instead of\n// treating dropped content as an empty forward.\nconst FORWARDED_FETCH_FAILED = '<forwarded_messages status=\"fetch_failed\"/>';\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    // Fetch failed after retries (or hit a non-transient error). Surface a\n    // distinct marker rather than silently degrading to an empty forward.\n    return { content: FORWARDED_FETCH_FAILED, 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, escapeAttr, safeParse, unwrapLocale } from '../utils';\n\ninterface PostBody {\n  title?: string;\n  content?: PostElement[][];\n  content_v2?: PostElement[][];\n}\n\n/**\n * A validated attachment-zone entry. Unlike the raw wire record, every field\n * here is guaranteed by `topLevelAttachments`: `fileKey` is a non-empty string,\n * `isFolder` is a real boolean, and `fileName` is a string or absent. Rendering\n * can then interpolate these without re-checking types.\n */\ninterface PostAttachment {\n  fileKey: string;\n  fileName?: string;\n  isFolder: boolean;\n}\n\nconst placeholder = '[rich text message]';\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: placeholder, resources: [] };\n  }\n\n  // The attachment zone is a sibling of the locale documents, not part of one,\n  // so it must be read before the locale guard below — otherwise a post whose\n  // locale document is unparseable would silently drop its attachments too.\n  const attachments = topLevelAttachments(rawParsed as Record<string, unknown>);\n\n  const body = unwrapLocale<PostBody>(rawParsed as Record<string, unknown>);\n  if (!body && attachments.length === 0) {\n    return { content: placeholder, resources: [] };\n  }\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  // Attachment zone: files render as <file .../> and are downloadable; folders\n  // render as <folder .../> tags only, mirroring the standalone converters.\n  for (const att of attachments) {\n    // Both key and name are escaped: downstream parses these tags as structured\n    // info, so a quote inside a key must not be able to forge an extra attribute.\n    const tag = att.isFolder ? 'folder' : 'file';\n    const nameAttr = att.fileName ? ` name=\"${escapeAttr(att.fileName)}\"` : '';\n    lines.push(`<${tag} key=\"${escapeAttr(att.fileKey)}\"${nameAttr}/>`);\n    if (!att.isFolder) {\n      resources.push({ type: 'file', fileKey: att.fileKey, fileName: att.fileName });\n    }\n  }\n\n  const content = lines.join('\\n').trim() || placeholder;\n  return { content, resources };\n};\n\n/**\n * Extract and normalize the top-level attachment-zone entries of a post message.\n *\n * Wire values are untrusted, so every field is narrowed here rather than at the\n * point of use: a non-string `file_name` would otherwise reach `escapeAttr` and\n * throw, which `dispatchConvert` traps by falling back to the unknown-message\n * converter — silently replacing the whole message with a placeholder. Likewise\n * `is_folder` is compared against `true` rather than tested for truthiness, so\n * that a string `\"false\"` cannot hide a real, downloadable file behind a\n * `<folder/>` tag. Entries without a usable key are dropped.\n */\nfunction topLevelAttachments(parsed: Record<string, unknown>): PostAttachment[] {\n  const files = parsed.files;\n  if (!Array.isArray(files)) return [];\n  const out: PostAttachment[] = [];\n  for (const f of files) {\n    if (f == null || typeof f !== 'object') continue;\n    const rec = f as Record<string, unknown>;\n    if (typeof rec.file_key !== 'string' || !rec.file_key) continue;\n    out.push({\n      fileKey: rec.file_key,\n      fileName: typeof rec.file_name === 'string' ? rec.file_name : undefined,\n      isFolder: rec.is_folder === true,\n    });\n  }\n  return out;\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 { 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 message = extractMessage(err);\n  const code = inferCode(err, message);\n  return new LarkChannelError(code, message, { cause: err, context });\n}\n\nfunction inferCode(err: unknown, message: string): 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 = message.toLowerCase();\n\n  if (typeof feishuCode === 'number') {\n    // 230011: the message targeted by a reply has already been withdrawn.\n    if (feishuCode === 230011 || feishuCode === 230017 || feishuCode === 230020) {\n      return 'target_revoked';\n    }\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 isWithdrawnReplyTarget(msg) ? 'target_revoked' : '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\n/**\n * Text fallback for a reply whose target message has already been withdrawn.\n * Feishu has been observed to answer such replies with HTTP 400 and a body\n * whose message reads \"The message was withdrawn.\" but without a numeric\n * platform code; the coded variant (230011) is handled by the code table\n * above. Callers pass the lower-cased message. A substring probe keeps this\n * linear in the (server-controlled) body length.\n */\nfunction isWithdrawnReplyTarget(msg: string): boolean {\n  return msg.includes('withdrawn');\n}\n\nfunction extractMessage(err: unknown): string {\n  const raw = err as any;\n  const candidates = [raw?.response?.data?.msg, raw?.response?.data?.message, raw?.message];\n  const found = candidates.find((c) => typeof c === 'string' && c.length > 0);\n  return found ?? 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   * Also retry `send_timeout` errors. Off by default so send paths keep their\n   * fail-fast-on-timeout behavior (a timed-out send may have landed — retrying\n   * risks a duplicate). Idempotent read paths (e.g. fetching merge-forward\n   * sub-messages) turn this on: a timed-out GET is safe to re-issue.\n   */\n  retryTimeouts?: boolean;\n}\n\n/**\n * Execute `op` with exponential backoff. Only retries errors classified as\n * retryable (rate_limited / unknown), plus `send_timeout` when\n * `retryTimeouts` is set. Business errors (format / revoked / permission)\n * 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      const retryable = isRetryable(err) || (!!opts.retryTimeouts && err.code === 'send_timeout');\n      if (attempt >= max || !retryable) {\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 { 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  type CardActionQueueMode,\n  DEFAULT_STALE_MS,\n  type OnMessageDispatch,\n  type OnReject,\n  resolveBatchConfig,\n  resolveCardActionQueueMode,\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 (fileToken lane) — doc comments\n *   - pushCardAction: dedup + lock + queue — card clicks; the lane is chosen by\n *                     `chatQueue.cardActions`\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 cardActionManager: ChatPipelineManager;\n  private readonly cardActionMode: CardActionQueueMode;\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    const batch = resolveBatchConfig(opts.config);\n    this.manager = new ChatPipelineManager(batch);\n    // Card actions may get a lane of their own (`chatQueue.cardActions:\n    // 'separate'`). A second manager rather than a prefixed scope in the first:\n    // it only ever sees `run()`, so its pipelines are pure-serial, and it shares\n    // no state with the message lane — a click can neither wait behind a message\n    // handler nor flush that chat's debounce window early.\n    this.cardActionManager = new ChatPipelineManager(batch);\n    const rawMode = opts.config?.chatQueue?.cardActions;\n    const { mode, unrecognized } = resolveCardActionQueueMode(rawMode);\n    this.cardActionMode = mode;\n    if (unrecognized) {\n      this.logger.warn?.(\n        `safety: unrecognized chatQueue.cardActions \"${String(rawMode)}\" (expected 'same' | 'separate'), falling back to 'same'`,\n      );\n    }\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  /**\n   * Doc comments, or any action keyed by a non-chat scope. Serialized on the\n   * shared manager under `queueScope`.\n   */\n  async pushAction<T>(\n    eventId: string,\n    queueScope: string,\n    handler: () => Promise<T>,\n  ): Promise<T | undefined> {\n    return this.guardAction(eventId, handler, (task) =>\n      this.queueEnabled ? this.manager.run(queueScope, task) : task(),\n    );\n  }\n\n  /**\n   * Card button clicks. Which per-chat lane they join is decided here, from\n   * `chatQueue.cardActions`, so the channel only has to say \"this is a card\n   * action\". Under `'same'` this is exactly the shared path `pushAction`\n   * takes; under `'separate'` the click never touches the message lane.\n   */\n  async pushCardAction<T>(\n    eventId: string,\n    chatId: string,\n    handler: () => Promise<T>,\n  ): Promise<T | undefined> {\n    return this.guardAction(eventId, handler, (task) => {\n      if (!this.queueEnabled) return task();\n      const lanes = this.cardActionMode === 'separate' ? this.cardActionManager : this.manager;\n      return lanes.run(chatId, task);\n    });\n  }\n\n  /**\n   * What every action shares: drop redeliveries, hold the in-flight lock across\n   * the handler, and always leave the dedup mark + release the lock whatever the\n   * handler does. The lock is taken BEFORE `enqueue`, so a same-key redelivery\n   * is dropped whether the first is queued, running or done — independent of\n   * which lane runs it.\n   *\n   * The handler's return value is propagated back out so card-action callback\n   * responses (e.g. a toast) can reach Feishu. A throwing handler is logged and\n   * yields `undefined` (no response).\n   */\n  private async guardAction<T>(\n    eventId: string,\n    handler: () => Promise<T>,\n    enqueue: (task: () => Promise<T | undefined>) => Promise<T | undefined>,\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    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    return enqueue(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    // Both lanes drain before the cache and lock go away: a queued or running\n    // action's `finally` still has to write its dedup mark.\n    await Promise.all([this.manager.dispose(), this.cardActionManager.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 { MeetingChannel } from './meeting';\nimport type {\n  FollowMeetingOptions,\n  JoinMeetingOptions,\n  MeetingEventHealth,\n  MeetingMembership,\n  MeetingSession,\n} from './meeting/types';\nimport type { ApiMessageItem, RawMessageEvent } from './normalize';\nimport {\n  normalize,\n  normalizeBotAdded,\n  normalizeCardAction,\n  normalizeComment,\n  normalizeReaction,\n} from './normalize';\nimport { OutboundSender, retry } 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/** Fallback budget for {@link LarkChannelOptions.connectTimeoutMs}. */\nconst DEFAULT_CONNECT_TIMEOUT_MS = 15_000;\n\n/**\n * `setTimeout`'s 32-bit ceiling. Anything above it wraps to a 1ms delay, so a\n * deliberately generous budget would otherwise become an instant timeout.\n */\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\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  /**\n   * Meeting channel internals. Private so its wiring methods do not become de\n   * facto public API of a pre-1.0 package — the supported surface is\n   * {@link joinMeeting}, {@link followMyMeeting} and {@link getMeetingEventHealth}.\n   */\n  private readonly meetings: MeetingChannel;\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  /**\n   * The channel's own dispatcher handlers, keyed by event type. Read at dispatch\n   * time rather than captured, so `onRawEvent` can compose with them whether it\n   * is called before or after `connect()`.\n   */\n  private builtinHandlers: Record<string, (raw: unknown) => unknown> = {};\n\n  private readonly rawHandlers = new Map<string, Set<(payload: unknown) => unknown>>();\n\n  /**\n   * Event types already wired into the dispatcher. `EventDispatcher.register`\n   * logs an error when a key is re-registered, so each type gets exactly one\n   * composed entry and the composition reads mutable state instead.\n   */\n  private readonly dispatchedTypes = new Set<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.meetings = new MeetingChannel({\n      client: this.rawClient,\n      logger: this.logger,\n      cache: opts.cache ?? internalCache,\n      config: opts.meeting,\n      includeRaw: opts.includeRawEvent ?? opts.includeRawInMessage ?? false,\n      // Late-bound: the bot's own open_id is only known once connected, and\n      // selfEcho compares against it.\n      botOpenId: () => this.botIdentity?.openId,\n      isConnected: () => this.connected,\n      invitedHandler: () => this.handlers.meetingInvited,\n      onError: (e) => this.emitError(e),\n    });\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(this.resolveConnectTimeoutMs());\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.resolveConnectTimeoutMs());\n  }\n\n  /**\n   * Shared by `connect()` and `forceReconnect()` so the two can't drift apart.\n   * Value domain and the reason for the fallback: see\n   * {@link LarkChannelOptions.connectTimeoutMs}.\n   */\n  private resolveConnectTimeoutMs(): number {\n    const configured = this.opts.connectTimeoutMs;\n    if (typeof configured === 'number' && Number.isFinite(configured) && configured > 0) {\n      return Math.min(configured, MAX_TIMER_DELAY_MS);\n    }\n    return DEFAULT_CONNECT_TIMEOUT_MS;\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      // Held per attempt rather than read off `this.rawWsClient`: a concurrent\n      // reconnect may already have repointed that field at a newer client, and\n      // tearing that one down would kill a live session.\n      let attemptClient: WSClient | undefined;\n      const timer = setTimeout(() => {\n        if (settled) return;\n        settled = true;\n        // The caller has given up on this attempt and may start another right\n        // away. Left alone, this client keeps reconnecting on its own —\n        // leaking a socket and timers, and still delivering events into the\n        // caller's handlers — with nothing left holding a reference to it.\n        // See https://github.com/larksuite/node-sdk/issues/197\n        try {\n          attemptClient?.close({ force: true });\n        } catch {\n          /* best effort */\n        }\n        reject(\n          new LarkChannelError(\n            'not_connected',\n            `WebSocket handshake did not complete within ${timeoutMs}ms`,\n          ),\n        );\n      }, timeoutMs);\n\n      attemptClient = 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 = attemptClient;\n      attemptClient.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    // Dispose, never leave: a reconnect must not make the bot disappear from\n    // every meeting it is in. Two consequences the caller has to handle, both\n    // documented on `getRetainedMeetings`: sessions end with `disposed` and are\n    // NOT rebuilt by a later `connect()`, and the bot lingers as a participant\n    // until someone leaves it.\n    this.meetings.disposeAll();\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 = (mid: string): Promise<ApiMessageItem[]> =>\n      mid === parent.message_id ? Promise.resolve(items) : this.fetchMessageItemsWithRetry(mid);\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  /**\n   * Read a message's `data.items[]` (`im.v1.message.get`) with retry — used to\n   * expand merge-forward sub-messages. Wraps the GET in the shared\n   * exponential-backoff {@link retry}: transient upstream failures\n   * (5xx → `unknown`, `rate_limited`) and — since this is an idempotent read —\n   * timeouts are retried; non-transient errors (permission / not-found /\n   * format) fail fast. On exhaustion it **throws** the classified\n   * {@link LarkChannelError} instead of degrading to `[]`, so the converter can\n   * tell \"fetch failed\" apart from \"genuinely empty\". The failure is warn-logged\n   * here (once, after retries) before re-throwing.\n   */\n  private fetchMessageItemsWithRetry(messageId: string): Promise<ApiMessageItem[]> {\n    return retry(\n      async () => {\n        const r = (await this.rawClient.im.v1.message.get({\n          path: { message_id: messageId },\n        })) as { data?: { items?: ApiMessageItem[] } };\n        return r?.data?.items ?? [];\n      },\n      { ...(this.opts.outbound?.retry ?? {}), retryTimeouts: true },\n    ).catch((e) => {\n      this.logger.warn?.('channel: fetchSubMessages failed', e);\n      throw e;\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 = (mid: string): Promise<ApiMessageItem[]> =>\n      this.fetchMessageItemsWithRetry(mid);\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.builtinHandlers = {\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. Which per-chat lane it joins\n      // (shared with messages, or its own) is decided by\n      // safety.chatQueue.cardActions; the default is the shared one.\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.pushCardAction(\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\n      // Meeting channel — the three vc.bot.* events, registered internally so\n      // callers never have to wire them up themselves.\n      ...this.meetings.handlers(),\n    };\n\n    this.meetings.markRegistered();\n    for (const type of Object.keys(this.builtinHandlers)) this.ensureDispatchEntry(type);\n  }\n\n  /**\n   * Subscribe to a Feishu event type the channel does not wrap.\n   *\n   * Multicast; the returned function removes only this handler. Without this the\n   * alternatives are reaching into the dispatcher's private map, which breaks on\n   * a version bump, or opening a second long-lived connection — and a second\n   * connection for the same app makes Feishu split delivery between them, so the\n   * channel's own IM traffic starts disappearing.\n   *\n   * **Raw handlers run outside the safety pipeline.** They run after signature\n   * verification and decryption, but `PolicyGate` (`dmMode`, `dmAllowlist`,\n   * `groupAllowlist`, `requireMention`), dedup, the per-chat processing lock, the\n   * loop guard and the stale-message filter are all downstream of normalization\n   * and do not apply here. Registering a raw handler for an event type the channel\n   * already handles therefore opens a path around those checks — deliberately,\n   * but worth knowing before using it on `im.message.receive_v1`.\n   *\n   * The payload is the decrypted platform event, unredacted and unaffected by\n   * `includeRawEvent: false`: it carries `tenant_key`, full user ids and message\n   * bodies.\n   *\n   * Handlers are awaited before the dispatcher replies to Feishu, so that replies\n   * stay ordered after them. On `card.action.trigger` that matters: a slow raw\n   * handler delays the callback response past Feishu's timeout even though its\n   * return value is discarded. Keep raw handlers on that event type cheap, or hand\n   * the work to a queue.\n   */\n  onRawEvent(eventType: string, handler: (payload: unknown) => void | Promise<void>): Unsubscribe {\n    let set = this.rawHandlers.get(eventType);\n    if (!set) {\n      set = new Set();\n      this.rawHandlers.set(eventType, set);\n    }\n    set.add(handler);\n    this.ensureDispatchEntry(eventType);\n    return () => {\n      set?.delete(handler);\n    };\n  }\n\n  private ensureDispatchEntry(eventType: string): void {\n    if (this.dispatchedTypes.has(eventType)) return;\n    this.dispatchedTypes.add(eventType);\n    this.dispatcher.register({\n      [eventType]: (raw: unknown) => this.dispatchToHandlers(eventType, raw),\n    } as never);\n  }\n\n  /**\n   * Built-in first, raw handlers after, and the built-in's return value is the\n   * one that goes back to Feishu — a card action's callback response must not be\n   * rewritable by an observer that merely subscribed to the same event.\n   */\n  private async dispatchToHandlers(eventType: string, raw: unknown): Promise<unknown> {\n    const builtin = this.builtinHandlers[eventType];\n    const result = builtin ? await builtin(raw) : undefined;\n\n    for (const handler of [...(this.rawHandlers.get(eventType) ?? [])]) {\n      try {\n        await handler(raw);\n      } catch (e) {\n        // Contained: a raw subscriber must not break the built-in path.\n        this.emitError(e);\n      }\n    }\n    return result;\n  }\n\n  // ─── meeting channel ───────────────────────────────────\n\n  /**\n   * Put the bot in a meeting as a visible participant (app identity).\n   * Requires {@link connect} — this path is driven by event pushes.\n   */\n  async joinMeeting(meetingNo: string, opts?: JoinMeetingOptions): Promise<MeetingSession> {\n    return this.meetings.joinMeeting(meetingNo, opts);\n  }\n\n  /**\n   * Follow the meeting the given user access token's owner is currently in,\n   * without joining it (user identity). Does **not** require {@link connect}:\n   * this path is REST polling only.\n   */\n  async followMyMeeting(opts: FollowMeetingOptions): Promise<MeetingSession> {\n    return this.meetings.followMyMeeting(opts);\n  }\n\n  /**\n   * Diagnostics for the in-meeting event path, counted per link — `push` for event\n   * pushes, `poll` for REST reads. See {@link MeetingEventHealth}.\n   */\n  getMeetingEventHealth(): MeetingEventHealth {\n    return this.meetings.health();\n  }\n\n  /**\n   * Meetings the bot is still a participant of with nothing listening — what\n   * `disconnect()` leaves behind.\n   *\n   * `disconnect()` disposes sessions without leaving their meetings, so the bot stays in\n   * them; a later `connect()` re-registers the event handlers but does not rebuild the\n   * sessions, and their pushes are then dropped. Sessions signal this by ending with\n   * `reason: 'disposed'`.\n   *\n   * Re-attach with `joinMeeting(meetingNo)` — which does not consume a new concurrency\n   * slot for a meeting already held — or, once attached, `leave()` to give the slot back.\n   * Ignoring an entry here means the bot sits in a meeting deaf, holding a slot, until\n   * the meeting ends.\n   */\n  getRetainedMeetings(): MeetingMembership[] {\n    return this.meetings.retainedMeetings();\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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"}