@ashx-j/lunr 0.2.24 → 0.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/gateway/adapters/discord.d.ts.map +1 -1
  3. package/dist/gateway/adapters/discord.js +2 -7
  4. package/dist/gateway/adapters/discord.js.map +1 -1
  5. package/dist/gateway/adapters/telegram.d.ts.map +1 -1
  6. package/dist/gateway/adapters/telegram.js +7 -0
  7. package/dist/gateway/adapters/telegram.js.map +1 -1
  8. package/dist/gateway/agent-bridge.d.ts +4 -1
  9. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  10. package/dist/gateway/agent-bridge.js +57 -17
  11. package/dist/gateway/agent-bridge.js.map +1 -1
  12. package/dist/gateway/buttons.d.ts.map +1 -1
  13. package/dist/gateway/buttons.js +29 -19
  14. package/dist/gateway/buttons.js.map +1 -1
  15. package/dist/gateway/commands.d.ts +2 -0
  16. package/dist/gateway/commands.d.ts.map +1 -1
  17. package/dist/gateway/commands.js +39 -18
  18. package/dist/gateway/commands.js.map +1 -1
  19. package/dist/gateway/config.d.ts +1 -0
  20. package/dist/gateway/config.d.ts.map +1 -1
  21. package/dist/gateway/config.js +2 -0
  22. package/dist/gateway/config.js.map +1 -1
  23. package/dist/gateway/cron.d.ts +1 -1
  24. package/dist/gateway/cron.d.ts.map +1 -1
  25. package/dist/gateway/cron.js +2 -2
  26. package/dist/gateway/cron.js.map +1 -1
  27. package/dist/gateway/index.d.ts.map +1 -1
  28. package/dist/gateway/index.js +26 -26
  29. package/dist/gateway/index.js.map +1 -1
  30. package/dist/gateway/mobile-commands.d.ts.map +1 -1
  31. package/dist/gateway/mobile-commands.js +3 -3
  32. package/dist/gateway/mobile-commands.js.map +1 -1
  33. package/dist/gateway/presenter.d.ts +12 -1
  34. package/dist/gateway/presenter.d.ts.map +1 -1
  35. package/dist/gateway/presenter.js +146 -48
  36. package/dist/gateway/presenter.js.map +1 -1
  37. package/dist/gateway/router.d.ts +5 -5
  38. package/dist/gateway/router.d.ts.map +1 -1
  39. package/dist/gateway/router.js +95 -41
  40. package/dist/gateway/router.js.map +1 -1
  41. package/dist/gateway/service.d.ts.map +1 -1
  42. package/dist/gateway/service.js +13 -4
  43. package/dist/gateway/service.js.map +1 -1
  44. package/dist/gateway/setup.d.ts +5 -0
  45. package/dist/gateway/setup.d.ts.map +1 -1
  46. package/dist/gateway/setup.js +98 -42
  47. package/dist/gateway/setup.js.map +1 -1
  48. package/dist/gateway/stream.d.ts +6 -5
  49. package/dist/gateway/stream.d.ts.map +1 -1
  50. package/dist/gateway/stream.js +24 -11
  51. package/dist/gateway/stream.js.map +1 -1
  52. package/dist/node-runtime/{agent-session-services-IEF7LQWD.js → agent-session-services-YXXFCP5J.js} +7 -6
  53. package/dist/node-runtime/{builtin-extensions-EBAWTH4M.js → builtin-extensions-ZLAX7TUH.js} +1 -1
  54. package/dist/node-runtime/{chunk-OF7PWMG4.js → chunk-2HHLJWGD.js} +3 -87
  55. package/dist/node-runtime/{chunk-VWCE2HSZ.js → chunk-2HMIRK7W.js} +4 -4
  56. package/dist/node-runtime/{chunk-K5AGHH57.js → chunk-3XPLBUEM.js} +1 -1
  57. package/dist/node-runtime/{chunk-RORY2HMJ.js → chunk-6XL67VVY.js} +1 -1
  58. package/dist/node-runtime/chunk-7S3T6YEA.js +97 -0
  59. package/dist/node-runtime/{chunk-R3K2F5GI.js → chunk-B4HP6ZWM.js} +2 -0
  60. package/dist/node-runtime/{chunk-YVI6L6LU.js → chunk-E4D3IGP2.js} +38 -22
  61. package/dist/node-runtime/{chunk-5Q3EHOHI.js → chunk-IJIFHRTB.js} +14 -12
  62. package/dist/node-runtime/{chunk-Y73WPE6S.js → chunk-KWIYUUZK.js} +63 -37
  63. package/dist/node-runtime/{chunk-F4FLTQKV.js → chunk-LQP3RB67.js} +184 -1527
  64. package/dist/node-runtime/{chunk-FU6OODRZ.js → chunk-NRKGMJYU.js} +1 -1
  65. package/dist/node-runtime/{chunk-IGV54Q5V.js → chunk-QTU6AKNJ.js} +128 -63
  66. package/dist/node-runtime/{chunk-L3LX4SNR.js → chunk-RC7DMV45.js} +5 -5
  67. package/dist/node-runtime/{chunk-UWPJL22I.js → chunk-SLVOAQOY.js} +15 -4
  68. package/dist/node-runtime/{chunk-WHLRAGUQ.js → chunk-TORR5XBV.js} +6 -6
  69. package/dist/node-runtime/{chunk-UP2XBVH5.js → chunk-UOEDI7RP.js} +1 -1
  70. package/dist/node-runtime/{chunk-DE2JB6J5.js → chunk-VQFSUJJG.js} +2 -2
  71. package/dist/node-runtime/{chunk-QM43FEAE.js → chunk-WF7PGWRZ.js} +3 -3
  72. package/dist/node-runtime/chunk-YCUZEGJG.js +1361 -0
  73. package/dist/node-runtime/{command-7BG7VVTS.js → command-6WDOYKS6.js} +5 -5
  74. package/dist/node-runtime/{commands-QFDKNQTL.js → commands-QC6VUZJQ.js} +5 -5
  75. package/dist/node-runtime/{config-RORQUQFG.js → config-D4QUPAHX.js} +1 -1
  76. package/dist/node-runtime/{gateway-TWR3N65G.js → gateway-INAYBF4Q.js} +134 -78
  77. package/dist/node-runtime/index.js +15 -12
  78. package/dist/node-runtime/{install-cli-VAP6KBFN.js → install-cli-KL4UDDAD.js} +3 -3
  79. package/dist/node-runtime/{interactive-mode-6J5VZ2TT.js → interactive-mode-Z5M7WYYZ.js} +8 -7
  80. package/dist/node-runtime/{lunr-browser-J5ZZYVGM.js → lunr-browser-IR7TL3J7.js} +3 -3
  81. package/dist/node-runtime/{lunr-cron-CXOL37AL.js → lunr-cron-LWIZJM2N.js} +5 -5
  82. package/dist/node-runtime/main.js +12 -10
  83. package/dist/node-runtime/{mobile-commands-VTBM5DSZ.js → mobile-commands-J7OJEKFO.js} +4 -4
  84. package/dist/node-runtime/{package-manager-cli-ZLW5DZXF.js → package-manager-cli-VYZM5WHN.js} +10 -7
  85. package/dist/node-runtime/{pi-subagents-XPCLLKIM.js → pi-subagents-DNY7EAIG.js} +6 -6
  86. package/dist/node-runtime/rpc-entry.js +12 -10
  87. package/dist/node-runtime/{runtime-bridges-QYXUX2TG.js → runtime-bridges-VRDDR73P.js} +5 -5
  88. package/dist/node-runtime/{service-ISSP6VHE.js → service-MDQIPQ26.js} +1 -1
  89. package/dist/node-runtime/{settings-manager-WCQDUVQB.js → settings-manager-J4SPQPDJ.js} +4 -4
  90. package/dist/node-runtime/setup-CQ2NLVK5.js +214 -0
  91. package/dist/node-runtime/{subagent-executor-OTNISDQU.js → subagent-executor-KZZQGAKV.js} +5 -5
  92. package/dist/node-runtime/{update-cli-VBR2777K.js → update-cli-GTFJC7XK.js} +4 -4
  93. package/docs/features.md +4 -4
  94. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  95. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  96. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  97. package/examples/extensions/gondolin/package-lock.json +2 -2
  98. package/examples/extensions/gondolin/package.json +1 -1
  99. package/examples/extensions/sandbox/package-lock.json +2 -2
  100. package/examples/extensions/sandbox/package.json +1 -1
  101. package/examples/extensions/with-deps/package-lock.json +2 -2
  102. package/examples/extensions/with-deps/package.json +1 -1
  103. package/package.json +4 -4
  104. package/dist/node-runtime/setup-YRBKH2LD.js +0 -155
@@ -1 +1 @@
1
- {"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../../src/gateway/adapters/telegram.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway Telegram adapter (Phase 2b).\n *\n * Talks to the Telegram Bot API over HTTPS long-polling (zero-dep: global\n * fetch). connect() verifies the token via getMe, caches {botId, botUsername},\n * then starts the poll loop (getUpdates, offset-tracked, 30s long-poll). The\n * loop honors 429 retry_after, backs off exponentially on network/5xx errors\n * (1s→2s→5s→…max 30s, reset on success) and stops on disconnect() via an\n * AbortController.\n *\n * Inbound: updateToEvent() is a pure update → MessageEvent mapping (dm/group/\n * channel chat types, forum thread ids with the General topic normalized away,\n * @-mention / text_mention / `/cmd@bot` detection with the mention stripped,\n * own + all-bot messages dropped, media-only dropped — captions accepted).\n * Consecutive texts from the same chat/thread/user are debounced 300ms and\n * merged (\"\\n\"-joined, latest messageId kept) to reassemble client-split long\n * messages; texts starting with \"/\" flush immediately so commands never merge.\n * Callback queries are polled and mapped via callbackQueryToEvent().\n *\n * Outbound: sendMessage/editMessageText with parse_mode \"Markdown\" and a\n * one-shot plain-text retry when Telegram rejects the markup; send() honors\n * 429 retry_after once and then reports { success: false, retryable: true }.\n * Message splitting is NOT done here — the router owns that (text.ts).\n *\n * Typing: sendTyping() is a self-contained refresher — Telegram typing\n * bubbles expire after ~5s, so the action is re-sent on an unref'd 4s\n * interval per chat. The interval stops when send() completes for that chat\n * or after 2 minutes without a refresh (safety net). Keeping this here means\n * router/agent-bridge stay untouched.\n *\n * Testability: all Bot API HTTP goes through an injectable callApi; timers go\n * through an injectable Scheduler; backoffDelayMs() is a pure function.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { isAuthorized } from \"../authz.ts\";\nimport { loadGatewayConfig, type PlatformConfig } from \"../config.ts\";\nimport { downloadAttachment } from \"../download.ts\";\nimport { createPairingStore } from \"../pairing.ts\";\nimport type {\n\tButtonSpec,\n\tCallbackEvent,\n\tChatType,\n\tInboundAttachment,\n\tMessageEvent,\n\tPlatformAdapter,\n\tSendOptions,\n\tSendResult,\n} from \"../types.ts\";\n\nconst API_BASE = \"https://api.telegram.org\";\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst TYPING_INTERVAL_MS = 4000; // bubbles expire after ~5s\nconst TYPING_SAFETY_MS = 120_000; // auto-stop after 2min without a refresh\nconst REPLY_TO_TEXT_MAX = 500;\nconst MARKDOWN_PARSE_ERROR_RE = /can't parse|parse entities/i;\nconst NOT_MODIFIED_RE = /message is not modified/i;\n\n// ---------------------------------------------------------------------------\n// Bot API shapes (only the fields the adapter reads)\n// ---------------------------------------------------------------------------\n\nexport interface TelegramUser {\n\tid: number;\n\tis_bot?: boolean;\n\tusername?: string;\n\tfirst_name?: string;\n}\n\nexport interface TelegramChat {\n\tid: number;\n\ttype: string;\n\tis_forum?: boolean;\n}\n\nexport interface TelegramEntity {\n\ttype: string;\n\toffset: number; // UTF-16 code units — same indexing as JS strings\n\tlength: number;\n\tuser?: TelegramUser;\n}\n\nexport interface TelegramPhotoSize {\n\tfile_id: string;\n\tfile_unique_id: string;\n\twidth: number;\n\theight: number;\n\tfile_size?: number;\n}\n\nexport interface TelegramDocument {\n\tfile_id: string;\n\tfile_unique_id: string;\n\tfile_name?: string;\n\tmime_type?: string;\n\tfile_size?: number;\n}\n\nexport interface TelegramMessage {\n\tmessage_id: number;\n\tfrom?: TelegramUser;\n\tchat: TelegramChat;\n\ttext?: string;\n\tcaption?: string;\n\tentities?: TelegramEntity[];\n\tcaption_entities?: TelegramEntity[];\n\tmessage_thread_id?: number;\n\treply_to_message?: { text?: string };\n\tphoto?: TelegramPhotoSize[];\n\tdocument?: TelegramDocument;\n}\n\nexport interface TelegramUpdate {\n\tupdate_id: number;\n\tmessage?: TelegramMessage;\n\tcallback_query?: TelegramCallbackQuery;\n}\n\nexport interface TelegramCallbackQuery {\n\tid: string;\n\tfrom: TelegramUser;\n\tmessage?: TelegramMessage;\n\tdata?: string;\n}\n\nexport interface BotInfo {\n\tbotId: number;\n\tbotUsername: string;\n}\n\ninterface TelegramApiResponse {\n\tok: boolean;\n\tresult?: unknown;\n\tdescription?: string;\n\terror_code?: number;\n\tparameters?: { retry_after?: number };\n}\n\n/** Bot API call: resolves with the unwrapped `result`; throws TelegramApiError on API/HTTP errors. */\nexport type CallApi = (method: string, body: Record<string, unknown>, signal?: AbortSignal) => Promise<unknown>;\n\n/** Timer injection seam (production default uses globals, unref'd). */\nexport interface Scheduler {\n\tsetTimeout(fn: () => void, ms: number): ReturnType<typeof setTimeout>;\n\tclearTimeout(handle: ReturnType<typeof setTimeout>): void;\n\tsetInterval(fn: () => void, ms: number): ReturnType<typeof setInterval>;\n\tclearInterval(handle: ReturnType<typeof setInterval>): void;\n}\n\nexport class TelegramApiError extends Error {\n\treadonly status: number;\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, description: string, retryAfter?: number) {\n\t\tsuper(description);\n\t\tthis.name = \"TelegramApiError\";\n\t\tthis.status = status;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n\n/** Exponential poll-error backoff: 1s→2s→5s→10s→30s (max), indexed by consecutive failure count. */\nexport const BACKOFF_DELAYS_MS = [1000, 2000, 5000, 10000, 30000] as const;\n\nexport function backoffDelayMs(consecutiveFailures: number): number {\n\tconst index = Math.min(Math.max(consecutiveFailures, 0), BACKOFF_DELAYS_MS.length - 1);\n\treturn BACKOFF_DELAYS_MS[index];\n}\n\nconst defaultScheduler: Scheduler = {\n\tsetTimeout: (fn, ms) => {\n\t\tconst handle = setTimeout(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearTimeout: (handle) => clearTimeout(handle),\n\tsetInterval: (fn, ms) => {\n\t\tconst handle = setInterval(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearInterval: (handle) => clearInterval(handle),\n};\n\nasync function defaultCallApi(\n\ttoken: string,\n\tmethod: string,\n\tbody: Record<string, unknown>,\n\tsignal?: AbortSignal,\n): Promise<unknown> {\n\tconst res = await fetch(`${API_BASE}/bot${token}/${method}`, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"content-type\": \"application/json\" },\n\t\tbody: JSON.stringify(body),\n\t\tsignal,\n\t});\n\tconst data = (await res.json().catch(() => undefined)) as TelegramApiResponse | undefined;\n\tif (res.status === 429) {\n\t\tthrow new TelegramApiError(429, data?.description ?? \"rate limited\", data?.parameters?.retry_after);\n\t}\n\tif (!res.ok || !data?.ok) {\n\t\tthrow new TelegramApiError(\n\t\t\tdata?.error_code ?? res.status,\n\t\t\tdata?.description ?? `HTTP ${res.status}`,\n\t\t\tdata?.parameters?.retry_after,\n\t\t);\n\t}\n\treturn data.result;\n}\n\n/** lunr: download an inbound file by its file_path from the Telegram file API. */\nasync function defaultDownloadFile(token: string, filePath: string): Promise<Uint8Array> {\n\treturn downloadAttachment(`${API_BASE}/file/bot${token}/${filePath}`);\n}\n\nfunction errMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction chatTypeOf(chat: TelegramChat): ChatType | null {\n\tswitch (chat.type) {\n\t\tcase \"private\":\n\t\t\treturn \"dm\";\n\t\tcase \"group\":\n\t\tcase \"supergroup\":\n\t\t\treturn \"group\";\n\t\tcase \"channel\":\n\t\t\treturn \"channel\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\n/**\n * Detect bot mentions via entities and strip them from the text.\n * - \"mention\" whose slice is `@<botUsername>` (case-insensitive): whole slice removed.\n * - \"text_mention\" whose user is the bot: whole slice removed.\n * - \"bot_command\" containing `@<botUsername>` (e.g. /new@lunrbot): only the\n * \"@bot\" suffix is removed so router slash-command parsing still works.\n */\nfunction extractMention(\n\ttext: string,\n\tentities: TelegramEntity[],\n\tbotInfo: BotInfo,\n): { text: string; mentioned: boolean } {\n\tif (!botInfo.botUsername) return { text: text.trim(), mentioned: false };\n\tconst botAt = `@${botInfo.botUsername}`.toLowerCase();\n\tconst removals: Array<[number, number]> = [];\n\tlet mentioned = false;\n\tfor (const entity of entities) {\n\t\tconst slice = text.slice(entity.offset, entity.offset + entity.length);\n\t\tif (entity.type === \"mention\" && slice.toLowerCase() === botAt) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"text_mention\" && entity.user?.id === botInfo.botId) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"bot_command\" && slice.toLowerCase().includes(botAt)) {\n\t\t\tmentioned = true;\n\t\t\tconst at = slice.toLowerCase().indexOf(botAt);\n\t\t\tremovals.push([entity.offset + at, entity.offset + at + botAt.length]);\n\t\t}\n\t}\n\tif (removals.length === 0) return { text: text.trim(), mentioned };\n\tremovals.sort((a, b) => a[0] - b[0]);\n\tlet out = \"\";\n\tlet pos = 0;\n\tfor (const [start, end] of removals) {\n\t\tout += text.slice(pos, start);\n\t\tpos = Math.max(pos, end);\n\t}\n\tout += text.slice(pos);\n\treturn { text: out.trim(), mentioned };\n}\n\n/**\n * lunr: media descriptor for an inbound image (photo or image document).\n * The adapter downloads the bytes via getFile + downloadFile and base64-encodes them.\n */\nexport interface TelegramMediaDescriptor {\n\tfileId: string;\n\tmimeType: string;\n\tfilename?: string;\n\tfileSize?: number;\n}\n\n/** Collect the largest photo and document attachments. */\nexport function collectTelegramMedia(message: TelegramMessage): TelegramMediaDescriptor[] {\n\tconst descriptors: TelegramMediaDescriptor[] = [];\n\tif (message.photo && message.photo.length > 0) {\n\t\t// Largest size is last per Telegram Bot API ordering.\n\t\tconst largest = message.photo[message.photo.length - 1];\n\t\tdescriptors.push({\n\t\t\tfileId: largest.file_id,\n\t\t\tmimeType: \"image/jpeg\",\n\t\t\t...(largest.file_size !== undefined ? { fileSize: largest.file_size } : {}),\n\t\t});\n\t}\n\tif (message.document) {\n\t\tdescriptors.push({\n\t\t\tfileId: message.document.file_id,\n\t\t\tmimeType: message.document.mime_type ?? \"application/octet-stream\",\n\t\t\t...(message.document.file_name ? { filename: message.document.file_name } : {}),\n\t\t\t...(message.document.file_size !== undefined ? { fileSize: message.document.file_size } : {}),\n\t\t});\n\t}\n\treturn descriptors;\n}\n\n/**\n * Map a Telegram update to a MessageEvent; null = drop (non-message update,\n * own message, any bot message, unknown chat type, or a message with neither\n * text nor inbound image media).\n */\nexport function updateToEvent(update: TelegramUpdate, botInfo: BotInfo): MessageEvent | null {\n\tconst message = update.message;\n\tif (!message) return null;\n\tconst from = message.from;\n\tif (!from) return null; // channel posts without a user signature\n\tif (from.id === botInfo.botId) return null; // own messages\n\tif (from.is_bot) return null; // all bots (hermes exclusive_bot_mentions, simplified)\n\n\tconst hasMedia = collectTelegramMedia(message).length > 0;\n\tconst rawText = message.text ?? message.caption;\n\tif (!rawText && !hasMedia) return null; // no text and no image media\n\tconst rawEntities = message.text !== undefined ? message.entities : message.caption_entities;\n\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\n\t// Forum threads only; the General topic (id 1) is normalized away (hermes).\n\tconst threadId =\n\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t? String(message.message_thread_id)\n\t\t\t: undefined;\n\n\tconst { text, mentioned } = extractMention(rawText ?? \"\", rawEntities ?? [], botInfo);\n\n\tconst replyText = message.reply_to_message?.text;\n\n\treturn {\n\t\ttext,\n\t\tmessageId: String(message.message_id),\n\t\treplyToText: replyText !== undefined ? replyText.slice(0, REPLY_TO_TEXT_MAX) : undefined,\n\t\tsource: {\n\t\t\tplatform: \"telegram\",\n\t\t\tchatId: String(message.chat.id),\n\t\t\tchatType,\n\t\t\tuserId: String(from.id),\n\t\t\tuserName: from.username ?? from.first_name,\n\t\t\tthreadId,\n\t\t},\n\t\tmetadata: mentioned ? { mentionedBot: true } : undefined,\n\t};\n}\n\nexport function callbackQueryToEvent(query: TelegramCallbackQuery): CallbackEvent | null {\n\tconst message = query.message;\n\tif (!message) return null;\n\tconst from = query.from;\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\treturn {\n\t\tid: query.id,\n\t\tchatId: String(message.chat.id),\n\t\tmessageId: String(message.message_id),\n\t\tuserId: String(from.id),\n\t\tuserName: from.username ?? from.first_name,\n\t\tdata: query.data ?? \"\",\n\t\tthreadId:\n\t\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t\t? String(message.message_thread_id)\n\t\t\t\t: undefined,\n\t};\n}\n\nexport interface TelegramAdapterOptions {\n\t/** Injected Bot API transport (tests). When omitted, HTTPS calls are made with cfg.token. */\n\tcallApi?: CallApi;\n\t/** lunr: injected file downloader for inbound media (tests). Downloads from api.telegram.org/file/bot<token>/<file_path>. */\n\tdownloadFile?: (filePath: string) => Promise<Uint8Array>;\n\t/** lunr: skip inbound media larger than this many bytes (pre-download). Defaults to 10MB. */\n\tmaxMediaBytes?: number;\n\tscheduler?: Scheduler;\n\tdebounceMs?: number;\n\tnow?: () => number;\n}\n\ninterface PendingEntry {\n\tevent: MessageEvent;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\ninterface TypingEntry {\n\tinterval: ReturnType<typeof setInterval>;\n\tlastRefresh: number;\n}\n\nexport class TelegramAdapter implements PlatformAdapter {\n\treadonly platform = \"telegram\";\n\treadonly maxMessageLength = 4096;\n\n\tprivate readonly callApi: CallApi;\n\tprivate readonly downloadFile: (filePath: string) => Promise<Uint8Array>;\n\tprivate readonly maxMediaBytes: number;\n\tprivate readonly scheduler: Scheduler;\n\tprivate readonly debounceMs: number;\n\tprivate readonly now: () => number;\n\tprivate handler?: (event: MessageEvent) => void;\n\tprivate callbackHandler?: (event: CallbackEvent) => void;\n\tprivate botInfo?: BotInfo;\n\tprivate offset = 0;\n\tprivate abortController?: AbortController;\n\tprivate readonly pending = new Map<string, PendingEntry>();\n\tprivate readonly typing = new Map<string, TypingEntry>();\n\n\tprivate readonly cfg: PlatformConfig;\n\tconstructor(cfg: PlatformConfig, options: TelegramAdapterOptions = {}) {\n\t\tthis.cfg = cfg;\n\t\tthis.scheduler = options.scheduler ?? defaultScheduler;\n\t\tthis.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\t\tthis.now = options.now ?? Date.now;\n\t\tif (options.callApi) {\n\t\t\tthis.callApi = options.callApi;\n\t\t} else {\n\t\t\tif (!cfg.token) {\n\t\t\t\tthrow new Error(\"TelegramAdapter: no bot token (set telegram.token or LUNR_TELEGRAM_BOT_TOKEN)\");\n\t\t\t}\n\t\t\tconst token = cfg.token;\n\t\t\tthis.callApi = (method, body, signal) => defaultCallApi(token, method, body, signal);\n\t\t}\n\t\t// lunr: inbound media download. When tests inject callApi they also inject\n\t\t// downloadFile; production uses the bot token against the Telegram file API.\n\t\tthis.downloadFile = options.downloadFile ?? ((filePath) => defaultDownloadFile(cfg.token ?? \"\", filePath));\n\t\tthis.maxMediaBytes = options.maxMediaBytes ?? 10 * 1024 * 1024;\n\t}\n\n\tonMessage(handler: (event: MessageEvent) => void): void {\n\t\tthis.handler = handler;\n\t}\n\n\tonCallback(handler: (event: CallbackEvent) => void): void {\n\t\tthis.callbackHandler = handler;\n\t}\n\n\tasync connect(): Promise<boolean> {\n\t\tconst me = (await this.callApi(\"getMe\", {})) as TelegramUser;\n\t\tthis.botInfo = { botId: me.id, botUsername: me.username ?? \"\" };\n\t\tthis.abortController = new AbortController();\n\t\tvoid this.pollLoop(this.abortController.signal); // intentionally not awaited\n\t\treturn true;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tthis.abortController?.abort();\n\t\tfor (const key of [...this.pending.keys()]) this.cancelPending(key);\n\t\tfor (const key of [...this.typing.keys()]) this.stopTyping(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Long-poll loop\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollLoop(signal: AbortSignal): Promise<void> {\n\t\tlet failures = 0;\n\t\twhile (!signal.aborted) {\n\t\t\ttry {\n\t\t\t\tconst updates = (await this.callApi(\n\t\t\t\t\t\"getUpdates\",\n\t\t\t\t\t{ offset: this.offset, timeout: 30, allowed_updates: [\"message\", \"callback_query\"] },\n\t\t\t\t\tsignal,\n\t\t\t\t)) as TelegramUpdate[];\n\t\t\t\tfailures = 0;\n\t\t\t\tfor (const update of updates) {\n\t\t\t\t\tthis.offset = update.update_id + 1;\n\t\t\t\t\tawait this.dispatchUpdate(update);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) return;\n\t\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\t\tconst retryAfter = Math.min(err.retryAfter ?? 1, 30);\n\t\t\t\t\tawait this.sleep(retryAfter * 1000, signal);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst delay = backoffDelayMs(failures);\n\t\t\t\tfailures += 1;\n\t\t\t\tconsole.error(`[gateway] telegram poll error (retry in ${delay}ms): ${errMessage(err)}`);\n\t\t\t\tawait this.sleep(delay, signal);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst timer = this.scheduler.setTimeout(resolve, ms);\n\t\t\tsignal?.addEventListener(\n\t\t\t\t\"abort\",\n\t\t\t\t() => {\n\t\t\t\t\tthis.scheduler.clearTimeout(timer);\n\t\t\t\t\tresolve();\n\t\t\t\t},\n\t\t\t\t{ once: true },\n\t\t\t);\n\t\t});\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Inbound dispatch + debounce\n\t// -----------------------------------------------------------------------\n\n\tprivate debounceKey(event: MessageEvent): string {\n\t\treturn `${event.source.chatId}:${event.source.threadId ?? \"\"}:${event.source.userId}`;\n\t}\n\n\tprivate async dispatchUpdate(update: TelegramUpdate): Promise<void> {\n\t\tif (!this.botInfo) return;\n\t\tif (update.callback_query) {\n\t\t\tconst event = callbackQueryToEvent(update.callback_query);\n\t\t\tif (event) this.callbackHandler?.(event);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.handler) return;\n\t\tconst event = updateToEvent(update, this.botInfo);\n\t\tif (!event) return;\n\t\t// lunr: download inbound image media before dispatch so the bridge can attach\n\t\t// images to the turn. Failures drop the media but keep the text turn going.\n\t\tif (\n\t\t\tupdate.message &&\n\t\t\tisAuthorized(event.source, { ...loadGatewayConfig(), telegram: this.cfg }, createPairingStore())\n\t\t) {\n\t\t\tconst attachments = await this.downloadMedia(update.message);\n\t\t\tif (attachments.length > 0) event.attachments = attachments;\n\t\t}\n\t\tconst key = this.debounceKey(event);\n\t\tif (event.text.trimStart().startsWith(\"/\")) {\n\t\t\t// Commands must not merge: flush any pending text, emit immediately.\n\t\t\tthis.flushPending(key);\n\t\t\tthis.handler(event);\n\t\t\treturn;\n\t\t}\n\t\tconst existing = this.pending.get(key);\n\t\tif (existing) {\n\t\t\tthis.scheduler.clearTimeout(existing.timer);\n\t\t\t// Merge: earlier text first, latest messageId (and other fields) win.\n\t\t\tconst mergedAttachments = [...(existing.event.attachments ?? []), ...(event.attachments ?? [])];\n\t\t\texisting.event = {\n\t\t\t\t...event,\n\t\t\t\ttext: `${existing.event.text}\\n${event.text}`,\n\t\t\t\t...(mergedAttachments.length > 0 ? { attachments: mergedAttachments } : {}),\n\t\t\t};\n\t\t\texisting.timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t} else {\n\t\t\tconst timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t\tthis.pending.set(key, { event, timer });\n\t\t}\n\t}\n\n\t/** lunr: download + base64-encode inbound image media for one Telegram message. */\n\tprivate async downloadMedia(message: TelegramMessage): Promise<InboundAttachment[]> {\n\t\tconst descriptors = collectTelegramMedia(message);\n\t\tif (descriptors.length === 0) return [];\n\t\tconst attachments: InboundAttachment[] = [];\n\t\tfor (const descriptor of descriptors) {\n\t\t\tif (descriptor.fileSize !== undefined && descriptor.fileSize > this.maxMediaBytes) continue;\n\t\t\ttry {\n\t\t\t\tconst fileResult = (await this.callApi(\"getFile\", { file_id: descriptor.fileId })) as\n\t\t\t\t\t| { file_path?: string; file_size?: number }\n\t\t\t\t\t| undefined;\n\t\t\t\tconst filePath = fileResult?.file_path;\n\t\t\t\tif (!filePath) continue;\n\t\t\t\tif (fileResult?.file_size !== undefined && fileResult.file_size > this.maxMediaBytes) continue;\n\t\t\t\tconst bytes = await this.downloadFile(filePath);\n\t\t\t\tif (bytes.byteLength > this.maxMediaBytes) continue;\n\t\t\t\tattachments.push({\n\t\t\t\t\tdata: Buffer.from(bytes).toString(\"base64\"),\n\t\t\t\t\tmimeType: descriptor.mimeType,\n\t\t\t\t\t...(descriptor.filename ? { filename: descriptor.filename } : {}),\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\t// Media download failed — drop this attachment, keep the text turn.\n\t\t\t}\n\t\t}\n\t\treturn attachments;\n\t}\n\n\tprivate flushPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t\tthis.handler?.(entry.event);\n\t}\n\n\tprivate cancelPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Outbound\n\t// -----------------------------------------------------------------------\n\n\tprivate buildSendBody(chatId: string, text: string, opts?: SendOptions): Record<string, unknown> {\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, text, parse_mode: \"Markdown\" };\n\t\tif (opts?.threadId !== undefined) body.message_thread_id = Number(opts.threadId);\n\t\tif (opts?.replyTo !== undefined) body.reply_parameters = { message_id: Number(opts.replyTo) };\n\t\treturn body;\n\t}\n\n\tprivate buildReplyMarkup(\n\t\tbuttons?: ButtonSpec[][],\n\t): { inline_keyboard: { text: string; callback_data: string }[][] } | undefined {\n\t\tif (buttons === undefined) return undefined;\n\t\treturn {\n\t\t\tinline_keyboard: buttons.map((row) => row.map((b) => ({ text: b.label, callback_data: b.data }))),\n\t\t};\n\t}\n\n\tasync send(chatId: string, text: string, opts?: SendOptions): Promise<SendResult> {\n\t\t// A completed send ends the typing bubble — stop the refresher (see sendTyping).\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\treturn this.callSend(body);\n\t}\n\n\tasync sendButtons(chatId: string, text: string, buttons: ButtonSpec[][], opts?: SendOptions): Promise<SendResult> {\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup) body.reply_markup = markup;\n\t\treturn this.callSend(body);\n\t}\n\n\tasync answerCallback(id: string, text?: string): Promise<void> {\n\t\tconst body: Record<string, unknown> = { callback_query_id: id };\n\t\tif (text !== undefined) body.text = text;\n\t\tawait this.callApi(\"answerCallbackQuery\", body).catch(() => {});\n\t}\n\n\t/** lunr: register the bot's slash-command menu (Telegram client autocomplete). Best-effort — caller catches. */\n\tasync sendFile(chatId: string, file: string, opts?: SendOptions): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst data = new FormData();\n\t\t\tdata.set(\"chat_id\", chatId);\n\t\t\tif (opts?.threadId) data.set(\"message_thread_id\", opts.threadId);\n\t\t\tdata.set(\"document\", new Blob([new Uint8Array(readFileSync(file))]), basename(file));\n\t\t\tconst response = await fetch(`${API_BASE}/bot${this.cfg.token}/sendDocument`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: data,\n\t\t\t\tsignal: AbortSignal.timeout(30_000),\n\t\t\t});\n\t\t\tif (!response.ok) return { success: false, error: `Telegram file delivery failed: HTTP ${response.status}` };\n\t\t\treturn { success: true };\n\t\t} catch {\n\t\t\treturn { success: false, error: \"Telegram file delivery failed. Check your connection.\" };\n\t\t}\n\t}\n\n\tasync registerCommands(commands: { name: string; description: string }[]): Promise<void> {\n\t\tconst valid = commands\n\t\t\t.filter((c) => /^[a-z0-9_]{1,32}$/.test(c.name) && c.description.trim().length > 0)\n\t\t\t.map((c) => ({ command: c.name, description: c.description.trim().slice(0, 256) }));\n\t\tif (valid.length === 0) return;\n\t\tawait this.callApi(\"setMyCommands\", { commands: valid });\n\t}\n\n\tprivate async callSend(body: Record<string, unknown>): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t} catch (err) {\n\t\t\tif (err instanceof TelegramApiError && err.status === 400 && body.parse_mode !== undefined) {\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message)) {\n\t\t\t\t\t// Telegram rejected our markup: retry once as plain text.\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\treturn this.callSend(plain);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\tawait this.sleep((err.retryAfter ?? 1) * 1000);\n\t\t\t\ttry {\n\t\t\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t\t\t} catch (retryErr) {\n\t\t\t\t\treturn { success: false, error: errMessage(retryErr), retryable: true };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync editMessage(chatId: string, messageId: string, text: string, buttons?: ButtonSpec[][]): Promise<SendResult> {\n\t\tconst body: Record<string, unknown> = {\n\t\t\tchat_id: chatId,\n\t\t\tmessage_id: Number(messageId),\n\t\t\ttext,\n\t\t\tparse_mode: \"Markdown\",\n\t\t};\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup !== undefined) body.reply_markup = markup;\n\t\ttry {\n\t\t\tawait this.callApi(\"editMessageText\", body);\n\t\t\treturn { success: true, messageId };\n\t\t} catch (err) {\n\t\t\tif (err instanceof TelegramApiError && err.status === 400) {\n\t\t\t\tif (NOT_MODIFIED_RE.test(err.message)) return { success: true, messageId };\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message) && body.parse_mode !== undefined) {\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.callApi(\"editMessageText\", plain);\n\t\t\t\t\t\treturn { success: true, messageId };\n\t\t\t\t\t} catch (retryErr) {\n\t\t\t\t\t\treturn { success: false, error: errMessage(retryErr) };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync sendTyping(chatId: string, threadId?: string): Promise<void> {\n\t\t// Self-contained typing refresher (see file header): re-send the action\n\t\t// every 4s until send() completes for this chat or the 2min safety net\n\t\t// fires — all errors swallowed.\n\t\tconst key = typingKey(chatId, threadId);\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, action: \"typing\" };\n\t\tif (threadId !== undefined) body.message_thread_id = Number(threadId);\n\t\tawait this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\tconst existing = this.typing.get(key);\n\t\tif (existing) {\n\t\t\texisting.lastRefresh = this.now();\n\t\t\treturn;\n\t\t}\n\t\tconst interval = this.scheduler.setInterval(() => {\n\t\t\tconst entry = this.typing.get(key);\n\t\t\tif (!entry) return;\n\t\t\tif (this.now() - entry.lastRefresh > TYPING_SAFETY_MS) {\n\t\t\t\tthis.stopTyping(key);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvoid this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\t}, TYPING_INTERVAL_MS);\n\t\tthis.typing.set(key, { interval, lastRefresh: this.now() });\n\t}\n\n\tprivate stopTyping(key: string): void {\n\t\tconst entry = this.typing.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearInterval(entry.interval);\n\t\tthis.typing.delete(key);\n\t}\n}\n\nfunction typingKey(chatId: string, threadId?: string): string {\n\treturn `${chatId}:${threadId ?? \"\"}`;\n}\n"]}
1
+ {"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../../src/gateway/adapters/telegram.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway Telegram adapter (Phase 2b).\n *\n * Talks to the Telegram Bot API over HTTPS long-polling (zero-dep: global\n * fetch). connect() verifies the token via getMe, caches {botId, botUsername},\n * then starts the poll loop (getUpdates, offset-tracked, 30s long-poll). The\n * loop honors 429 retry_after, backs off exponentially on network/5xx errors\n * (1s→2s→5s→…max 30s, reset on success) and stops on disconnect() via an\n * AbortController.\n *\n * Inbound: updateToEvent() is a pure update → MessageEvent mapping (dm/group/\n * channel chat types, forum thread ids with the General topic normalized away,\n * @-mention / text_mention / `/cmd@bot` detection with the mention stripped,\n * own + all-bot messages dropped, media-only dropped — captions accepted).\n * Consecutive texts from the same chat/thread/user are debounced 300ms and\n * merged (\"\\n\"-joined, latest messageId kept) to reassemble client-split long\n * messages; texts starting with \"/\" flush immediately so commands never merge.\n * Callback queries are polled and mapped via callbackQueryToEvent().\n *\n * Outbound: sendMessage/editMessageText with parse_mode \"Markdown\" and a\n * one-shot plain-text retry when Telegram rejects the markup; send() honors\n * 429 retry_after once and then reports { success: false, retryable: true }.\n * Message splitting is NOT done here — the router owns that (text.ts).\n *\n * Typing: sendTyping() is a self-contained refresher — Telegram typing\n * bubbles expire after ~5s, so the action is re-sent on an unref'd 4s\n * interval per chat. The interval stops when send() completes for that chat\n * or after 2 minutes without a refresh (safety net). Keeping this here means\n * router/agent-bridge stay untouched.\n *\n * Testability: all Bot API HTTP goes through an injectable callApi; timers go\n * through an injectable Scheduler; backoffDelayMs() is a pure function.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { isAuthorized } from \"../authz.ts\";\nimport { loadGatewayConfig, type PlatformConfig } from \"../config.ts\";\nimport { downloadAttachment } from \"../download.ts\";\nimport { createPairingStore } from \"../pairing.ts\";\nimport type {\n\tButtonSpec,\n\tCallbackEvent,\n\tChatType,\n\tInboundAttachment,\n\tMessageEvent,\n\tPlatformAdapter,\n\tSendOptions,\n\tSendResult,\n} from \"../types.ts\";\n\nconst API_BASE = \"https://api.telegram.org\";\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst TYPING_INTERVAL_MS = 4000; // bubbles expire after ~5s\nconst TYPING_SAFETY_MS = 120_000; // auto-stop after 2min without a refresh\nconst REPLY_TO_TEXT_MAX = 500;\nconst MARKDOWN_PARSE_ERROR_RE = /can't parse|parse entities/i;\nconst NOT_MODIFIED_RE = /message is not modified/i;\n\n// ---------------------------------------------------------------------------\n// Bot API shapes (only the fields the adapter reads)\n// ---------------------------------------------------------------------------\n\nexport interface TelegramUser {\n\tid: number;\n\tis_bot?: boolean;\n\tusername?: string;\n\tfirst_name?: string;\n}\n\nexport interface TelegramChat {\n\tid: number;\n\ttype: string;\n\tis_forum?: boolean;\n}\n\nexport interface TelegramEntity {\n\ttype: string;\n\toffset: number; // UTF-16 code units — same indexing as JS strings\n\tlength: number;\n\tuser?: TelegramUser;\n}\n\nexport interface TelegramPhotoSize {\n\tfile_id: string;\n\tfile_unique_id: string;\n\twidth: number;\n\theight: number;\n\tfile_size?: number;\n}\n\nexport interface TelegramDocument {\n\tfile_id: string;\n\tfile_unique_id: string;\n\tfile_name?: string;\n\tmime_type?: string;\n\tfile_size?: number;\n}\n\nexport interface TelegramMessage {\n\tmessage_id: number;\n\tfrom?: TelegramUser;\n\tchat: TelegramChat;\n\ttext?: string;\n\tcaption?: string;\n\tentities?: TelegramEntity[];\n\tcaption_entities?: TelegramEntity[];\n\tmessage_thread_id?: number;\n\treply_to_message?: { text?: string };\n\tphoto?: TelegramPhotoSize[];\n\tdocument?: TelegramDocument;\n}\n\nexport interface TelegramUpdate {\n\tupdate_id: number;\n\tmessage?: TelegramMessage;\n\tcallback_query?: TelegramCallbackQuery;\n}\n\nexport interface TelegramCallbackQuery {\n\tid: string;\n\tfrom: TelegramUser;\n\tmessage?: TelegramMessage;\n\tdata?: string;\n}\n\nexport interface BotInfo {\n\tbotId: number;\n\tbotUsername: string;\n}\n\ninterface TelegramApiResponse {\n\tok: boolean;\n\tresult?: unknown;\n\tdescription?: string;\n\terror_code?: number;\n\tparameters?: { retry_after?: number };\n}\n\n/** Bot API call: resolves with the unwrapped `result`; throws TelegramApiError on API/HTTP errors. */\nexport type CallApi = (method: string, body: Record<string, unknown>, signal?: AbortSignal) => Promise<unknown>;\n\n/** Timer injection seam (production default uses globals, unref'd). */\nexport interface Scheduler {\n\tsetTimeout(fn: () => void, ms: number): ReturnType<typeof setTimeout>;\n\tclearTimeout(handle: ReturnType<typeof setTimeout>): void;\n\tsetInterval(fn: () => void, ms: number): ReturnType<typeof setInterval>;\n\tclearInterval(handle: ReturnType<typeof setInterval>): void;\n}\n\nexport class TelegramApiError extends Error {\n\treadonly status: number;\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, description: string, retryAfter?: number) {\n\t\tsuper(description);\n\t\tthis.name = \"TelegramApiError\";\n\t\tthis.status = status;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n\n/** Exponential poll-error backoff: 1s→2s→5s→10s→30s (max), indexed by consecutive failure count. */\nexport const BACKOFF_DELAYS_MS = [1000, 2000, 5000, 10000, 30000] as const;\n\nexport function backoffDelayMs(consecutiveFailures: number): number {\n\tconst index = Math.min(Math.max(consecutiveFailures, 0), BACKOFF_DELAYS_MS.length - 1);\n\treturn BACKOFF_DELAYS_MS[index];\n}\n\nconst defaultScheduler: Scheduler = {\n\tsetTimeout: (fn, ms) => {\n\t\tconst handle = setTimeout(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearTimeout: (handle) => clearTimeout(handle),\n\tsetInterval: (fn, ms) => {\n\t\tconst handle = setInterval(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearInterval: (handle) => clearInterval(handle),\n};\n\nasync function defaultCallApi(\n\ttoken: string,\n\tmethod: string,\n\tbody: Record<string, unknown>,\n\tsignal?: AbortSignal,\n): Promise<unknown> {\n\tconst res = await fetch(`${API_BASE}/bot${token}/${method}`, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"content-type\": \"application/json\" },\n\t\tbody: JSON.stringify(body),\n\t\tsignal,\n\t});\n\tconst data = (await res.json().catch(() => undefined)) as TelegramApiResponse | undefined;\n\tif (res.status === 429) {\n\t\tthrow new TelegramApiError(429, data?.description ?? \"rate limited\", data?.parameters?.retry_after);\n\t}\n\tif (!res.ok || !data?.ok) {\n\t\tthrow new TelegramApiError(\n\t\t\tdata?.error_code ?? res.status,\n\t\t\tdata?.description ?? `HTTP ${res.status}`,\n\t\t\tdata?.parameters?.retry_after,\n\t\t);\n\t}\n\treturn data.result;\n}\n\n/** lunr: download an inbound file by its file_path from the Telegram file API. */\nasync function defaultDownloadFile(token: string, filePath: string): Promise<Uint8Array> {\n\treturn downloadAttachment(`${API_BASE}/file/bot${token}/${filePath}`);\n}\n\nfunction errMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction chatTypeOf(chat: TelegramChat): ChatType | null {\n\tswitch (chat.type) {\n\t\tcase \"private\":\n\t\t\treturn \"dm\";\n\t\tcase \"group\":\n\t\tcase \"supergroup\":\n\t\t\treturn \"group\";\n\t\tcase \"channel\":\n\t\t\treturn \"channel\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\n/**\n * Detect bot mentions via entities and strip them from the text.\n * - \"mention\" whose slice is `@<botUsername>` (case-insensitive): whole slice removed.\n * - \"text_mention\" whose user is the bot: whole slice removed.\n * - \"bot_command\" containing `@<botUsername>` (e.g. /new@lunrbot): only the\n * \"@bot\" suffix is removed so router slash-command parsing still works.\n */\nfunction extractMention(\n\ttext: string,\n\tentities: TelegramEntity[],\n\tbotInfo: BotInfo,\n): { text: string; mentioned: boolean } {\n\tif (!botInfo.botUsername) return { text: text.trim(), mentioned: false };\n\tconst botAt = `@${botInfo.botUsername}`.toLowerCase();\n\tconst removals: Array<[number, number]> = [];\n\tlet mentioned = false;\n\tfor (const entity of entities) {\n\t\tconst slice = text.slice(entity.offset, entity.offset + entity.length);\n\t\tif (entity.type === \"mention\" && slice.toLowerCase() === botAt) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"text_mention\" && entity.user?.id === botInfo.botId) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"bot_command\" && slice.toLowerCase().includes(botAt)) {\n\t\t\tmentioned = true;\n\t\t\tconst at = slice.toLowerCase().indexOf(botAt);\n\t\t\tremovals.push([entity.offset + at, entity.offset + at + botAt.length]);\n\t\t}\n\t}\n\tif (removals.length === 0) return { text: text.trim(), mentioned };\n\tremovals.sort((a, b) => a[0] - b[0]);\n\tlet out = \"\";\n\tlet pos = 0;\n\tfor (const [start, end] of removals) {\n\t\tout += text.slice(pos, start);\n\t\tpos = Math.max(pos, end);\n\t}\n\tout += text.slice(pos);\n\treturn { text: out.trim(), mentioned };\n}\n\n/**\n * lunr: media descriptor for an inbound image (photo or image document).\n * The adapter downloads the bytes via getFile + downloadFile and base64-encodes them.\n */\nexport interface TelegramMediaDescriptor {\n\tfileId: string;\n\tmimeType: string;\n\tfilename?: string;\n\tfileSize?: number;\n}\n\n/** Collect the largest photo and document attachments. */\nexport function collectTelegramMedia(message: TelegramMessage): TelegramMediaDescriptor[] {\n\tconst descriptors: TelegramMediaDescriptor[] = [];\n\tif (message.photo && message.photo.length > 0) {\n\t\t// Largest size is last per Telegram Bot API ordering.\n\t\tconst largest = message.photo[message.photo.length - 1];\n\t\tdescriptors.push({\n\t\t\tfileId: largest.file_id,\n\t\t\tmimeType: \"image/jpeg\",\n\t\t\t...(largest.file_size !== undefined ? { fileSize: largest.file_size } : {}),\n\t\t});\n\t}\n\tif (message.document) {\n\t\tdescriptors.push({\n\t\t\tfileId: message.document.file_id,\n\t\t\tmimeType: message.document.mime_type ?? \"application/octet-stream\",\n\t\t\t...(message.document.file_name ? { filename: message.document.file_name } : {}),\n\t\t\t...(message.document.file_size !== undefined ? { fileSize: message.document.file_size } : {}),\n\t\t});\n\t}\n\treturn descriptors;\n}\n\n/**\n * Map a Telegram update to a MessageEvent; null = drop (non-message update,\n * own message, any bot message, unknown chat type, or a message with neither\n * text nor inbound image media).\n */\nexport function updateToEvent(update: TelegramUpdate, botInfo: BotInfo): MessageEvent | null {\n\tconst message = update.message;\n\tif (!message) return null;\n\tconst from = message.from;\n\tif (!from) return null; // channel posts without a user signature\n\tif (from.id === botInfo.botId) return null; // own messages\n\tif (from.is_bot) return null; // all bots (hermes exclusive_bot_mentions, simplified)\n\n\tconst hasMedia = collectTelegramMedia(message).length > 0;\n\tconst rawText = message.text ?? message.caption;\n\tif (!rawText && !hasMedia) return null; // no text and no image media\n\tconst rawEntities = message.text !== undefined ? message.entities : message.caption_entities;\n\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\n\t// Forum threads only; the General topic (id 1) is normalized away (hermes).\n\tconst threadId =\n\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t? String(message.message_thread_id)\n\t\t\t: undefined;\n\n\tconst { text, mentioned } = extractMention(rawText ?? \"\", rawEntities ?? [], botInfo);\n\n\tconst replyText = message.reply_to_message?.text;\n\n\treturn {\n\t\ttext,\n\t\tmessageId: String(message.message_id),\n\t\treplyToText: replyText !== undefined ? replyText.slice(0, REPLY_TO_TEXT_MAX) : undefined,\n\t\tsource: {\n\t\t\tplatform: \"telegram\",\n\t\t\tchatId: String(message.chat.id),\n\t\t\tchatType,\n\t\t\tuserId: String(from.id),\n\t\t\tuserName: from.username ?? from.first_name,\n\t\t\tthreadId,\n\t\t},\n\t\tmetadata: mentioned ? { mentionedBot: true } : undefined,\n\t};\n}\n\nexport function callbackQueryToEvent(query: TelegramCallbackQuery): CallbackEvent | null {\n\tconst message = query.message;\n\tif (!message) return null;\n\tconst from = query.from;\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\treturn {\n\t\tid: query.id,\n\t\tchatId: String(message.chat.id),\n\t\tmessageId: String(message.message_id),\n\t\tuserId: String(from.id),\n\t\tuserName: from.username ?? from.first_name,\n\t\tdata: query.data ?? \"\",\n\t\tthreadId:\n\t\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t\t? String(message.message_thread_id)\n\t\t\t\t: undefined,\n\t};\n}\n\nexport interface TelegramAdapterOptions {\n\t/** Injected Bot API transport (tests). When omitted, HTTPS calls are made with cfg.token. */\n\tcallApi?: CallApi;\n\t/** lunr: injected file downloader for inbound media (tests). Downloads from api.telegram.org/file/bot<token>/<file_path>. */\n\tdownloadFile?: (filePath: string) => Promise<Uint8Array>;\n\t/** lunr: skip inbound media larger than this many bytes (pre-download). Defaults to 10MB. */\n\tmaxMediaBytes?: number;\n\tscheduler?: Scheduler;\n\tdebounceMs?: number;\n\tnow?: () => number;\n}\n\ninterface PendingEntry {\n\tevent: MessageEvent;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\ninterface TypingEntry {\n\tinterval: ReturnType<typeof setInterval>;\n\tlastRefresh: number;\n}\n\nexport class TelegramAdapter implements PlatformAdapter {\n\treadonly platform = \"telegram\";\n\treadonly maxMessageLength = 4096;\n\n\tprivate readonly callApi: CallApi;\n\tprivate readonly downloadFile: (filePath: string) => Promise<Uint8Array>;\n\tprivate readonly maxMediaBytes: number;\n\tprivate readonly scheduler: Scheduler;\n\tprivate readonly debounceMs: number;\n\tprivate readonly now: () => number;\n\tprivate handler?: (event: MessageEvent) => void;\n\tprivate callbackHandler?: (event: CallbackEvent) => void;\n\tprivate botInfo?: BotInfo;\n\tprivate offset = 0;\n\tprivate abortController?: AbortController;\n\tprivate readonly pending = new Map<string, PendingEntry>();\n\tprivate readonly typing = new Map<string, TypingEntry>();\n\n\tprivate readonly cfg: PlatformConfig;\n\tconstructor(cfg: PlatformConfig, options: TelegramAdapterOptions = {}) {\n\t\tthis.cfg = cfg;\n\t\tthis.scheduler = options.scheduler ?? defaultScheduler;\n\t\tthis.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\t\tthis.now = options.now ?? Date.now;\n\t\tif (options.callApi) {\n\t\t\tthis.callApi = options.callApi;\n\t\t} else {\n\t\t\tif (!cfg.token) {\n\t\t\t\tthrow new Error(\"TelegramAdapter: no bot token (set telegram.token or LUNR_TELEGRAM_BOT_TOKEN)\");\n\t\t\t}\n\t\t\tconst token = cfg.token;\n\t\t\tthis.callApi = (method, body, signal) => defaultCallApi(token, method, body, signal);\n\t\t}\n\t\t// lunr: inbound media download. When tests inject callApi they also inject\n\t\t// downloadFile; production uses the bot token against the Telegram file API.\n\t\tthis.downloadFile = options.downloadFile ?? ((filePath) => defaultDownloadFile(cfg.token ?? \"\", filePath));\n\t\tthis.maxMediaBytes = options.maxMediaBytes ?? 10 * 1024 * 1024;\n\t}\n\n\tonMessage(handler: (event: MessageEvent) => void): void {\n\t\tthis.handler = handler;\n\t}\n\n\tonCallback(handler: (event: CallbackEvent) => void): void {\n\t\tthis.callbackHandler = handler;\n\t}\n\n\tasync connect(): Promise<boolean> {\n\t\tconst me = (await this.callApi(\"getMe\", {})) as TelegramUser;\n\t\tthis.botInfo = { botId: me.id, botUsername: me.username ?? \"\" };\n\t\tthis.abortController = new AbortController();\n\t\tvoid this.pollLoop(this.abortController.signal); // intentionally not awaited\n\t\treturn true;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tthis.abortController?.abort();\n\t\tfor (const key of [...this.pending.keys()]) this.cancelPending(key);\n\t\tfor (const key of [...this.typing.keys()]) this.stopTyping(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Long-poll loop\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollLoop(signal: AbortSignal): Promise<void> {\n\t\tlet failures = 0;\n\t\twhile (!signal.aborted) {\n\t\t\ttry {\n\t\t\t\tconst updates = (await this.callApi(\n\t\t\t\t\t\"getUpdates\",\n\t\t\t\t\t{ offset: this.offset, timeout: 30, allowed_updates: [\"message\", \"callback_query\"] },\n\t\t\t\t\tsignal,\n\t\t\t\t)) as TelegramUpdate[];\n\t\t\t\tfailures = 0;\n\t\t\t\tfor (const update of updates) {\n\t\t\t\t\tthis.offset = update.update_id + 1;\n\t\t\t\t\tawait this.dispatchUpdate(update);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) return;\n\t\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\t\tconst retryAfter = Math.min(err.retryAfter ?? 1, 30);\n\t\t\t\t\tawait this.sleep(retryAfter * 1000, signal);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst delay = backoffDelayMs(failures);\n\t\t\t\tfailures += 1;\n\t\t\t\tconsole.error(`[gateway] telegram poll error (retry in ${delay}ms): ${errMessage(err)}`);\n\t\t\t\tawait this.sleep(delay, signal);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst timer = this.scheduler.setTimeout(resolve, ms);\n\t\t\tsignal?.addEventListener(\n\t\t\t\t\"abort\",\n\t\t\t\t() => {\n\t\t\t\t\tthis.scheduler.clearTimeout(timer);\n\t\t\t\t\tresolve();\n\t\t\t\t},\n\t\t\t\t{ once: true },\n\t\t\t);\n\t\t});\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Inbound dispatch + debounce\n\t// -----------------------------------------------------------------------\n\n\tprivate debounceKey(event: MessageEvent): string {\n\t\treturn `${event.source.chatId}:${event.source.threadId ?? \"\"}:${event.source.userId}`;\n\t}\n\n\tprivate async dispatchUpdate(update: TelegramUpdate): Promise<void> {\n\t\tif (!this.botInfo) return;\n\t\tif (update.callback_query) {\n\t\t\tconst event = callbackQueryToEvent(update.callback_query);\n\t\t\tif (event) this.callbackHandler?.(event);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.handler) return;\n\t\tconst event = updateToEvent(update, this.botInfo);\n\t\tif (!event) return;\n\t\t// lunr: download inbound image media before dispatch so the bridge can attach\n\t\t// images to the turn. Failures drop the media but keep the text turn going.\n\t\tif (\n\t\t\tupdate.message &&\n\t\t\tisAuthorized(event.source, { ...loadGatewayConfig(), telegram: this.cfg }, createPairingStore())\n\t\t) {\n\t\t\tconst attachments = await this.downloadMedia(update.message);\n\t\t\tif (attachments.length > 0) event.attachments = attachments;\n\t\t}\n\t\tconst key = this.debounceKey(event);\n\t\tif (event.text.trimStart().startsWith(\"/\")) {\n\t\t\t// Commands must not merge: flush any pending text, emit immediately.\n\t\t\tthis.flushPending(key);\n\t\t\tthis.handler(event);\n\t\t\treturn;\n\t\t}\n\t\tconst existing = this.pending.get(key);\n\t\tif (existing) {\n\t\t\tthis.scheduler.clearTimeout(existing.timer);\n\t\t\t// Merge: earlier text first, latest messageId (and other fields) win.\n\t\t\tconst mergedAttachments = [...(existing.event.attachments ?? []), ...(event.attachments ?? [])];\n\t\t\texisting.event = {\n\t\t\t\t...event,\n\t\t\t\ttext: `${existing.event.text}\\n${event.text}`,\n\t\t\t\t...(mergedAttachments.length > 0 ? { attachments: mergedAttachments } : {}),\n\t\t\t};\n\t\t\texisting.timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t} else {\n\t\t\tconst timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t\tthis.pending.set(key, { event, timer });\n\t\t}\n\t}\n\n\t/** lunr: download + base64-encode inbound image media for one Telegram message. */\n\tprivate async downloadMedia(message: TelegramMessage): Promise<InboundAttachment[]> {\n\t\tconst descriptors = collectTelegramMedia(message);\n\t\tif (descriptors.length === 0) return [];\n\t\tconst attachments: InboundAttachment[] = [];\n\t\tfor (const descriptor of descriptors) {\n\t\t\tif (descriptor.fileSize !== undefined && descriptor.fileSize > this.maxMediaBytes) continue;\n\t\t\ttry {\n\t\t\t\tconst fileResult = (await this.callApi(\"getFile\", { file_id: descriptor.fileId })) as\n\t\t\t\t\t| { file_path?: string; file_size?: number }\n\t\t\t\t\t| undefined;\n\t\t\t\tconst filePath = fileResult?.file_path;\n\t\t\t\tif (!filePath) continue;\n\t\t\t\tif (fileResult?.file_size !== undefined && fileResult.file_size > this.maxMediaBytes) continue;\n\t\t\t\tconst bytes = await this.downloadFile(filePath);\n\t\t\t\tif (bytes.byteLength > this.maxMediaBytes) continue;\n\t\t\t\tattachments.push({\n\t\t\t\t\tdata: Buffer.from(bytes).toString(\"base64\"),\n\t\t\t\t\tmimeType: descriptor.mimeType,\n\t\t\t\t\t...(descriptor.filename ? { filename: descriptor.filename } : {}),\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\t// Media download failed — drop this attachment, keep the text turn.\n\t\t\t}\n\t\t}\n\t\treturn attachments;\n\t}\n\n\tprivate flushPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t\tthis.handler?.(entry.event);\n\t}\n\n\tprivate cancelPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Outbound\n\t// -----------------------------------------------------------------------\n\n\tprivate buildSendBody(chatId: string, text: string, opts?: SendOptions): Record<string, unknown> {\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, text, parse_mode: \"Markdown\" };\n\t\tif (opts?.threadId !== undefined) body.message_thread_id = Number(opts.threadId);\n\t\tif (opts?.replyTo !== undefined) body.reply_parameters = { message_id: Number(opts.replyTo) };\n\t\treturn body;\n\t}\n\n\tprivate buildReplyMarkup(\n\t\tbuttons?: ButtonSpec[][],\n\t): { inline_keyboard: { text: string; callback_data: string }[][] } | undefined {\n\t\tif (buttons === undefined) return undefined;\n\t\treturn {\n\t\t\tinline_keyboard: buttons.map((row) => row.map((b) => ({ text: b.label, callback_data: b.data }))),\n\t\t};\n\t}\n\n\tasync send(chatId: string, text: string, opts?: SendOptions): Promise<SendResult> {\n\t\t// A completed send ends the typing bubble — stop the refresher (see sendTyping).\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\treturn this.callSend(body);\n\t}\n\n\tasync sendButtons(chatId: string, text: string, buttons: ButtonSpec[][], opts?: SendOptions): Promise<SendResult> {\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup) body.reply_markup = markup;\n\t\treturn this.callSend(body);\n\t}\n\n\tasync answerCallback(id: string, text?: string): Promise<void> {\n\t\tconst body: Record<string, unknown> = { callback_query_id: id };\n\t\tif (text !== undefined) body.text = text;\n\t\tawait this.callApi(\"answerCallbackQuery\", body).catch(() => {});\n\t}\n\n\t/** lunr: register the bot's slash-command menu (Telegram client autocomplete). Best-effort — caller catches. */\n\tasync sendFile(chatId: string, file: string, opts?: SendOptions): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst data = new FormData();\n\t\t\tdata.set(\"chat_id\", chatId);\n\t\t\tif (opts?.threadId) data.set(\"message_thread_id\", opts.threadId);\n\t\t\tdata.set(\"document\", new Blob([new Uint8Array(readFileSync(file))]), basename(file));\n\t\t\tconst response = await fetch(`${API_BASE}/bot${this.cfg.token}/sendDocument`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: data,\n\t\t\t\tsignal: AbortSignal.timeout(30_000),\n\t\t\t});\n\t\t\tif (!response.ok) return { success: false, error: `Telegram file delivery failed: HTTP ${response.status}` };\n\t\t\treturn { success: true };\n\t\t} catch {\n\t\t\treturn { success: false, error: \"Telegram file delivery failed. Check your connection.\" };\n\t\t}\n\t}\n\n\tasync registerCommands(commands: { name: string; description: string }[]): Promise<void> {\n\t\tconst valid = commands\n\t\t\t.filter((c) => /^[a-z0-9_]{1,32}$/.test(c.name) && c.description.trim().length > 0)\n\t\t\t.map((c) => ({ command: c.name, description: c.description.trim().slice(0, 256) }));\n\t\tif (valid.length === 0) return;\n\t\tawait this.callApi(\"setMyCommands\", { commands: valid });\n\t}\n\n\tprivate async callSend(body: Record<string, unknown>): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t} catch (err) {\n\t\t\tif (\n\t\t\t\terr instanceof TelegramApiError &&\n\t\t\t\terr.status === 400 &&\n\t\t\t\tbody.reply_parameters !== undefined &&\n\t\t\t\t/message to be replied not found/i.test(err.message)\n\t\t\t) {\n\t\t\t\tconst { reply_parameters: _dropped, ...plain } = body;\n\t\t\t\treturn this.callSend(plain);\n\t\t\t}\n\t\t\tif (err instanceof TelegramApiError && err.status === 400 && body.parse_mode !== undefined) {\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message)) {\n\t\t\t\t\t// Telegram rejected our markup: retry once as plain text.\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\treturn this.callSend(plain);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\tawait this.sleep((err.retryAfter ?? 1) * 1000);\n\t\t\t\ttry {\n\t\t\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t\t\t} catch (retryErr) {\n\t\t\t\t\treturn { success: false, error: errMessage(retryErr), retryable: true };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync editMessage(chatId: string, messageId: string, text: string, buttons?: ButtonSpec[][]): Promise<SendResult> {\n\t\tconst body: Record<string, unknown> = {\n\t\t\tchat_id: chatId,\n\t\t\tmessage_id: Number(messageId),\n\t\t\ttext,\n\t\t\tparse_mode: \"Markdown\",\n\t\t};\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup !== undefined) body.reply_markup = markup;\n\t\ttry {\n\t\t\tawait this.callApi(\"editMessageText\", body);\n\t\t\treturn { success: true, messageId };\n\t\t} catch (err) {\n\t\t\tif (err instanceof TelegramApiError && err.status === 400) {\n\t\t\t\tif (NOT_MODIFIED_RE.test(err.message)) return { success: true, messageId };\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message) && body.parse_mode !== undefined) {\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.callApi(\"editMessageText\", plain);\n\t\t\t\t\t\treturn { success: true, messageId };\n\t\t\t\t\t} catch (retryErr) {\n\t\t\t\t\t\treturn { success: false, error: errMessage(retryErr) };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync sendTyping(chatId: string, threadId?: string): Promise<void> {\n\t\t// Self-contained typing refresher (see file header): re-send the action\n\t\t// every 4s until send() completes for this chat or the 2min safety net\n\t\t// fires — all errors swallowed.\n\t\tconst key = typingKey(chatId, threadId);\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, action: \"typing\" };\n\t\tif (threadId !== undefined) body.message_thread_id = Number(threadId);\n\t\tawait this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\tconst existing = this.typing.get(key);\n\t\tif (existing) {\n\t\t\texisting.lastRefresh = this.now();\n\t\t\treturn;\n\t\t}\n\t\tconst interval = this.scheduler.setInterval(() => {\n\t\t\tconst entry = this.typing.get(key);\n\t\t\tif (!entry) return;\n\t\t\tif (this.now() - entry.lastRefresh > TYPING_SAFETY_MS) {\n\t\t\t\tthis.stopTyping(key);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvoid this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\t}, TYPING_INTERVAL_MS);\n\t\tthis.typing.set(key, { interval, lastRefresh: this.now() });\n\t}\n\n\tprivate stopTyping(key: string): void {\n\t\tconst entry = this.typing.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearInterval(entry.interval);\n\t\tthis.typing.delete(key);\n\t}\n}\n\nfunction typingKey(chatId: string, threadId?: string): string {\n\treturn `${chatId}:${threadId ?? \"\"}`;\n}\n"]}
@@ -504,6 +504,13 @@ export class TelegramAdapter {
504
504
  return { success: true, messageId: String(result.message_id) };
505
505
  }
506
506
  catch (err) {
507
+ if (err instanceof TelegramApiError &&
508
+ err.status === 400 &&
509
+ body.reply_parameters !== undefined &&
510
+ /message to be replied not found/i.test(err.message)) {
511
+ const { reply_parameters: _dropped, ...plain } = body;
512
+ return this.callSend(plain);
513
+ }
507
514
  if (err instanceof TelegramApiError && err.status === 400 && body.parse_mode !== undefined) {
508
515
  if (MARKDOWN_PARSE_ERROR_RE.test(err.message)) {
509
516
  // Telegram rejected our markup: retry once as plain text.
@@ -1 +1 @@
1
- {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../../src/gateway/adapters/telegram.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway Telegram adapter (Phase 2b).\n *\n * Talks to the Telegram Bot API over HTTPS long-polling (zero-dep: global\n * fetch). connect() verifies the token via getMe, caches {botId, botUsername},\n * then starts the poll loop (getUpdates, offset-tracked, 30s long-poll). The\n * loop honors 429 retry_after, backs off exponentially on network/5xx errors\n * (1s→2s→5s→…max 30s, reset on success) and stops on disconnect() via an\n * AbortController.\n *\n * Inbound: updateToEvent() is a pure update → MessageEvent mapping (dm/group/\n * channel chat types, forum thread ids with the General topic normalized away,\n * @-mention / text_mention / `/cmd@bot` detection with the mention stripped,\n * own + all-bot messages dropped, media-only dropped — captions accepted).\n * Consecutive texts from the same chat/thread/user are debounced 300ms and\n * merged (\"\\n\"-joined, latest messageId kept) to reassemble client-split long\n * messages; texts starting with \"/\" flush immediately so commands never merge.\n * Callback queries are polled and mapped via callbackQueryToEvent().\n *\n * Outbound: sendMessage/editMessageText with parse_mode \"Markdown\" and a\n * one-shot plain-text retry when Telegram rejects the markup; send() honors\n * 429 retry_after once and then reports { success: false, retryable: true }.\n * Message splitting is NOT done here — the router owns that (text.ts).\n *\n * Typing: sendTyping() is a self-contained refresher — Telegram typing\n * bubbles expire after ~5s, so the action is re-sent on an unref'd 4s\n * interval per chat. The interval stops when send() completes for that chat\n * or after 2 minutes without a refresh (safety net). Keeping this here means\n * router/agent-bridge stay untouched.\n *\n * Testability: all Bot API HTTP goes through an injectable callApi; timers go\n * through an injectable Scheduler; backoffDelayMs() is a pure function.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { isAuthorized } from \"../authz.ts\";\nimport { loadGatewayConfig, type PlatformConfig } from \"../config.ts\";\nimport { downloadAttachment } from \"../download.ts\";\nimport { createPairingStore } from \"../pairing.ts\";\nimport type {\n\tButtonSpec,\n\tCallbackEvent,\n\tChatType,\n\tInboundAttachment,\n\tMessageEvent,\n\tPlatformAdapter,\n\tSendOptions,\n\tSendResult,\n} from \"../types.ts\";\n\nconst API_BASE = \"https://api.telegram.org\";\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst TYPING_INTERVAL_MS = 4000; // bubbles expire after ~5s\nconst TYPING_SAFETY_MS = 120_000; // auto-stop after 2min without a refresh\nconst REPLY_TO_TEXT_MAX = 500;\nconst MARKDOWN_PARSE_ERROR_RE = /can't parse|parse entities/i;\nconst NOT_MODIFIED_RE = /message is not modified/i;\n\n// ---------------------------------------------------------------------------\n// Bot API shapes (only the fields the adapter reads)\n// ---------------------------------------------------------------------------\n\nexport interface TelegramUser {\n\tid: number;\n\tis_bot?: boolean;\n\tusername?: string;\n\tfirst_name?: string;\n}\n\nexport interface TelegramChat {\n\tid: number;\n\ttype: string;\n\tis_forum?: boolean;\n}\n\nexport interface TelegramEntity {\n\ttype: string;\n\toffset: number; // UTF-16 code units — same indexing as JS strings\n\tlength: number;\n\tuser?: TelegramUser;\n}\n\nexport interface TelegramPhotoSize {\n\tfile_id: string;\n\tfile_unique_id: string;\n\twidth: number;\n\theight: number;\n\tfile_size?: number;\n}\n\nexport interface TelegramDocument {\n\tfile_id: string;\n\tfile_unique_id: string;\n\tfile_name?: string;\n\tmime_type?: string;\n\tfile_size?: number;\n}\n\nexport interface TelegramMessage {\n\tmessage_id: number;\n\tfrom?: TelegramUser;\n\tchat: TelegramChat;\n\ttext?: string;\n\tcaption?: string;\n\tentities?: TelegramEntity[];\n\tcaption_entities?: TelegramEntity[];\n\tmessage_thread_id?: number;\n\treply_to_message?: { text?: string };\n\tphoto?: TelegramPhotoSize[];\n\tdocument?: TelegramDocument;\n}\n\nexport interface TelegramUpdate {\n\tupdate_id: number;\n\tmessage?: TelegramMessage;\n\tcallback_query?: TelegramCallbackQuery;\n}\n\nexport interface TelegramCallbackQuery {\n\tid: string;\n\tfrom: TelegramUser;\n\tmessage?: TelegramMessage;\n\tdata?: string;\n}\n\nexport interface BotInfo {\n\tbotId: number;\n\tbotUsername: string;\n}\n\ninterface TelegramApiResponse {\n\tok: boolean;\n\tresult?: unknown;\n\tdescription?: string;\n\terror_code?: number;\n\tparameters?: { retry_after?: number };\n}\n\n/** Bot API call: resolves with the unwrapped `result`; throws TelegramApiError on API/HTTP errors. */\nexport type CallApi = (method: string, body: Record<string, unknown>, signal?: AbortSignal) => Promise<unknown>;\n\n/** Timer injection seam (production default uses globals, unref'd). */\nexport interface Scheduler {\n\tsetTimeout(fn: () => void, ms: number): ReturnType<typeof setTimeout>;\n\tclearTimeout(handle: ReturnType<typeof setTimeout>): void;\n\tsetInterval(fn: () => void, ms: number): ReturnType<typeof setInterval>;\n\tclearInterval(handle: ReturnType<typeof setInterval>): void;\n}\n\nexport class TelegramApiError extends Error {\n\treadonly status: number;\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, description: string, retryAfter?: number) {\n\t\tsuper(description);\n\t\tthis.name = \"TelegramApiError\";\n\t\tthis.status = status;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n\n/** Exponential poll-error backoff: 1s→2s→5s→10s→30s (max), indexed by consecutive failure count. */\nexport const BACKOFF_DELAYS_MS = [1000, 2000, 5000, 10000, 30000] as const;\n\nexport function backoffDelayMs(consecutiveFailures: number): number {\n\tconst index = Math.min(Math.max(consecutiveFailures, 0), BACKOFF_DELAYS_MS.length - 1);\n\treturn BACKOFF_DELAYS_MS[index];\n}\n\nconst defaultScheduler: Scheduler = {\n\tsetTimeout: (fn, ms) => {\n\t\tconst handle = setTimeout(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearTimeout: (handle) => clearTimeout(handle),\n\tsetInterval: (fn, ms) => {\n\t\tconst handle = setInterval(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearInterval: (handle) => clearInterval(handle),\n};\n\nasync function defaultCallApi(\n\ttoken: string,\n\tmethod: string,\n\tbody: Record<string, unknown>,\n\tsignal?: AbortSignal,\n): Promise<unknown> {\n\tconst res = await fetch(`${API_BASE}/bot${token}/${method}`, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"content-type\": \"application/json\" },\n\t\tbody: JSON.stringify(body),\n\t\tsignal,\n\t});\n\tconst data = (await res.json().catch(() => undefined)) as TelegramApiResponse | undefined;\n\tif (res.status === 429) {\n\t\tthrow new TelegramApiError(429, data?.description ?? \"rate limited\", data?.parameters?.retry_after);\n\t}\n\tif (!res.ok || !data?.ok) {\n\t\tthrow new TelegramApiError(\n\t\t\tdata?.error_code ?? res.status,\n\t\t\tdata?.description ?? `HTTP ${res.status}`,\n\t\t\tdata?.parameters?.retry_after,\n\t\t);\n\t}\n\treturn data.result;\n}\n\n/** lunr: download an inbound file by its file_path from the Telegram file API. */\nasync function defaultDownloadFile(token: string, filePath: string): Promise<Uint8Array> {\n\treturn downloadAttachment(`${API_BASE}/file/bot${token}/${filePath}`);\n}\n\nfunction errMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction chatTypeOf(chat: TelegramChat): ChatType | null {\n\tswitch (chat.type) {\n\t\tcase \"private\":\n\t\t\treturn \"dm\";\n\t\tcase \"group\":\n\t\tcase \"supergroup\":\n\t\t\treturn \"group\";\n\t\tcase \"channel\":\n\t\t\treturn \"channel\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\n/**\n * Detect bot mentions via entities and strip them from the text.\n * - \"mention\" whose slice is `@<botUsername>` (case-insensitive): whole slice removed.\n * - \"text_mention\" whose user is the bot: whole slice removed.\n * - \"bot_command\" containing `@<botUsername>` (e.g. /new@lunrbot): only the\n * \"@bot\" suffix is removed so router slash-command parsing still works.\n */\nfunction extractMention(\n\ttext: string,\n\tentities: TelegramEntity[],\n\tbotInfo: BotInfo,\n): { text: string; mentioned: boolean } {\n\tif (!botInfo.botUsername) return { text: text.trim(), mentioned: false };\n\tconst botAt = `@${botInfo.botUsername}`.toLowerCase();\n\tconst removals: Array<[number, number]> = [];\n\tlet mentioned = false;\n\tfor (const entity of entities) {\n\t\tconst slice = text.slice(entity.offset, entity.offset + entity.length);\n\t\tif (entity.type === \"mention\" && slice.toLowerCase() === botAt) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"text_mention\" && entity.user?.id === botInfo.botId) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"bot_command\" && slice.toLowerCase().includes(botAt)) {\n\t\t\tmentioned = true;\n\t\t\tconst at = slice.toLowerCase().indexOf(botAt);\n\t\t\tremovals.push([entity.offset + at, entity.offset + at + botAt.length]);\n\t\t}\n\t}\n\tif (removals.length === 0) return { text: text.trim(), mentioned };\n\tremovals.sort((a, b) => a[0] - b[0]);\n\tlet out = \"\";\n\tlet pos = 0;\n\tfor (const [start, end] of removals) {\n\t\tout += text.slice(pos, start);\n\t\tpos = Math.max(pos, end);\n\t}\n\tout += text.slice(pos);\n\treturn { text: out.trim(), mentioned };\n}\n\n/**\n * lunr: media descriptor for an inbound image (photo or image document).\n * The adapter downloads the bytes via getFile + downloadFile and base64-encodes them.\n */\nexport interface TelegramMediaDescriptor {\n\tfileId: string;\n\tmimeType: string;\n\tfilename?: string;\n\tfileSize?: number;\n}\n\n/** Collect the largest photo and document attachments. */\nexport function collectTelegramMedia(message: TelegramMessage): TelegramMediaDescriptor[] {\n\tconst descriptors: TelegramMediaDescriptor[] = [];\n\tif (message.photo && message.photo.length > 0) {\n\t\t// Largest size is last per Telegram Bot API ordering.\n\t\tconst largest = message.photo[message.photo.length - 1];\n\t\tdescriptors.push({\n\t\t\tfileId: largest.file_id,\n\t\t\tmimeType: \"image/jpeg\",\n\t\t\t...(largest.file_size !== undefined ? { fileSize: largest.file_size } : {}),\n\t\t});\n\t}\n\tif (message.document) {\n\t\tdescriptors.push({\n\t\t\tfileId: message.document.file_id,\n\t\t\tmimeType: message.document.mime_type ?? \"application/octet-stream\",\n\t\t\t...(message.document.file_name ? { filename: message.document.file_name } : {}),\n\t\t\t...(message.document.file_size !== undefined ? { fileSize: message.document.file_size } : {}),\n\t\t});\n\t}\n\treturn descriptors;\n}\n\n/**\n * Map a Telegram update to a MessageEvent; null = drop (non-message update,\n * own message, any bot message, unknown chat type, or a message with neither\n * text nor inbound image media).\n */\nexport function updateToEvent(update: TelegramUpdate, botInfo: BotInfo): MessageEvent | null {\n\tconst message = update.message;\n\tif (!message) return null;\n\tconst from = message.from;\n\tif (!from) return null; // channel posts without a user signature\n\tif (from.id === botInfo.botId) return null; // own messages\n\tif (from.is_bot) return null; // all bots (hermes exclusive_bot_mentions, simplified)\n\n\tconst hasMedia = collectTelegramMedia(message).length > 0;\n\tconst rawText = message.text ?? message.caption;\n\tif (!rawText && !hasMedia) return null; // no text and no image media\n\tconst rawEntities = message.text !== undefined ? message.entities : message.caption_entities;\n\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\n\t// Forum threads only; the General topic (id 1) is normalized away (hermes).\n\tconst threadId =\n\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t? String(message.message_thread_id)\n\t\t\t: undefined;\n\n\tconst { text, mentioned } = extractMention(rawText ?? \"\", rawEntities ?? [], botInfo);\n\n\tconst replyText = message.reply_to_message?.text;\n\n\treturn {\n\t\ttext,\n\t\tmessageId: String(message.message_id),\n\t\treplyToText: replyText !== undefined ? replyText.slice(0, REPLY_TO_TEXT_MAX) : undefined,\n\t\tsource: {\n\t\t\tplatform: \"telegram\",\n\t\t\tchatId: String(message.chat.id),\n\t\t\tchatType,\n\t\t\tuserId: String(from.id),\n\t\t\tuserName: from.username ?? from.first_name,\n\t\t\tthreadId,\n\t\t},\n\t\tmetadata: mentioned ? { mentionedBot: true } : undefined,\n\t};\n}\n\nexport function callbackQueryToEvent(query: TelegramCallbackQuery): CallbackEvent | null {\n\tconst message = query.message;\n\tif (!message) return null;\n\tconst from = query.from;\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\treturn {\n\t\tid: query.id,\n\t\tchatId: String(message.chat.id),\n\t\tmessageId: String(message.message_id),\n\t\tuserId: String(from.id),\n\t\tuserName: from.username ?? from.first_name,\n\t\tdata: query.data ?? \"\",\n\t\tthreadId:\n\t\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t\t? String(message.message_thread_id)\n\t\t\t\t: undefined,\n\t};\n}\n\nexport interface TelegramAdapterOptions {\n\t/** Injected Bot API transport (tests). When omitted, HTTPS calls are made with cfg.token. */\n\tcallApi?: CallApi;\n\t/** lunr: injected file downloader for inbound media (tests). Downloads from api.telegram.org/file/bot<token>/<file_path>. */\n\tdownloadFile?: (filePath: string) => Promise<Uint8Array>;\n\t/** lunr: skip inbound media larger than this many bytes (pre-download). Defaults to 10MB. */\n\tmaxMediaBytes?: number;\n\tscheduler?: Scheduler;\n\tdebounceMs?: number;\n\tnow?: () => number;\n}\n\ninterface PendingEntry {\n\tevent: MessageEvent;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\ninterface TypingEntry {\n\tinterval: ReturnType<typeof setInterval>;\n\tlastRefresh: number;\n}\n\nexport class TelegramAdapter implements PlatformAdapter {\n\treadonly platform = \"telegram\";\n\treadonly maxMessageLength = 4096;\n\n\tprivate readonly callApi: CallApi;\n\tprivate readonly downloadFile: (filePath: string) => Promise<Uint8Array>;\n\tprivate readonly maxMediaBytes: number;\n\tprivate readonly scheduler: Scheduler;\n\tprivate readonly debounceMs: number;\n\tprivate readonly now: () => number;\n\tprivate handler?: (event: MessageEvent) => void;\n\tprivate callbackHandler?: (event: CallbackEvent) => void;\n\tprivate botInfo?: BotInfo;\n\tprivate offset = 0;\n\tprivate abortController?: AbortController;\n\tprivate readonly pending = new Map<string, PendingEntry>();\n\tprivate readonly typing = new Map<string, TypingEntry>();\n\n\tprivate readonly cfg: PlatformConfig;\n\tconstructor(cfg: PlatformConfig, options: TelegramAdapterOptions = {}) {\n\t\tthis.cfg = cfg;\n\t\tthis.scheduler = options.scheduler ?? defaultScheduler;\n\t\tthis.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\t\tthis.now = options.now ?? Date.now;\n\t\tif (options.callApi) {\n\t\t\tthis.callApi = options.callApi;\n\t\t} else {\n\t\t\tif (!cfg.token) {\n\t\t\t\tthrow new Error(\"TelegramAdapter: no bot token (set telegram.token or LUNR_TELEGRAM_BOT_TOKEN)\");\n\t\t\t}\n\t\t\tconst token = cfg.token;\n\t\t\tthis.callApi = (method, body, signal) => defaultCallApi(token, method, body, signal);\n\t\t}\n\t\t// lunr: inbound media download. When tests inject callApi they also inject\n\t\t// downloadFile; production uses the bot token against the Telegram file API.\n\t\tthis.downloadFile = options.downloadFile ?? ((filePath) => defaultDownloadFile(cfg.token ?? \"\", filePath));\n\t\tthis.maxMediaBytes = options.maxMediaBytes ?? 10 * 1024 * 1024;\n\t}\n\n\tonMessage(handler: (event: MessageEvent) => void): void {\n\t\tthis.handler = handler;\n\t}\n\n\tonCallback(handler: (event: CallbackEvent) => void): void {\n\t\tthis.callbackHandler = handler;\n\t}\n\n\tasync connect(): Promise<boolean> {\n\t\tconst me = (await this.callApi(\"getMe\", {})) as TelegramUser;\n\t\tthis.botInfo = { botId: me.id, botUsername: me.username ?? \"\" };\n\t\tthis.abortController = new AbortController();\n\t\tvoid this.pollLoop(this.abortController.signal); // intentionally not awaited\n\t\treturn true;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tthis.abortController?.abort();\n\t\tfor (const key of [...this.pending.keys()]) this.cancelPending(key);\n\t\tfor (const key of [...this.typing.keys()]) this.stopTyping(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Long-poll loop\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollLoop(signal: AbortSignal): Promise<void> {\n\t\tlet failures = 0;\n\t\twhile (!signal.aborted) {\n\t\t\ttry {\n\t\t\t\tconst updates = (await this.callApi(\n\t\t\t\t\t\"getUpdates\",\n\t\t\t\t\t{ offset: this.offset, timeout: 30, allowed_updates: [\"message\", \"callback_query\"] },\n\t\t\t\t\tsignal,\n\t\t\t\t)) as TelegramUpdate[];\n\t\t\t\tfailures = 0;\n\t\t\t\tfor (const update of updates) {\n\t\t\t\t\tthis.offset = update.update_id + 1;\n\t\t\t\t\tawait this.dispatchUpdate(update);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) return;\n\t\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\t\tconst retryAfter = Math.min(err.retryAfter ?? 1, 30);\n\t\t\t\t\tawait this.sleep(retryAfter * 1000, signal);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst delay = backoffDelayMs(failures);\n\t\t\t\tfailures += 1;\n\t\t\t\tconsole.error(`[gateway] telegram poll error (retry in ${delay}ms): ${errMessage(err)}`);\n\t\t\t\tawait this.sleep(delay, signal);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst timer = this.scheduler.setTimeout(resolve, ms);\n\t\t\tsignal?.addEventListener(\n\t\t\t\t\"abort\",\n\t\t\t\t() => {\n\t\t\t\t\tthis.scheduler.clearTimeout(timer);\n\t\t\t\t\tresolve();\n\t\t\t\t},\n\t\t\t\t{ once: true },\n\t\t\t);\n\t\t});\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Inbound dispatch + debounce\n\t// -----------------------------------------------------------------------\n\n\tprivate debounceKey(event: MessageEvent): string {\n\t\treturn `${event.source.chatId}:${event.source.threadId ?? \"\"}:${event.source.userId}`;\n\t}\n\n\tprivate async dispatchUpdate(update: TelegramUpdate): Promise<void> {\n\t\tif (!this.botInfo) return;\n\t\tif (update.callback_query) {\n\t\t\tconst event = callbackQueryToEvent(update.callback_query);\n\t\t\tif (event) this.callbackHandler?.(event);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.handler) return;\n\t\tconst event = updateToEvent(update, this.botInfo);\n\t\tif (!event) return;\n\t\t// lunr: download inbound image media before dispatch so the bridge can attach\n\t\t// images to the turn. Failures drop the media but keep the text turn going.\n\t\tif (\n\t\t\tupdate.message &&\n\t\t\tisAuthorized(event.source, { ...loadGatewayConfig(), telegram: this.cfg }, createPairingStore())\n\t\t) {\n\t\t\tconst attachments = await this.downloadMedia(update.message);\n\t\t\tif (attachments.length > 0) event.attachments = attachments;\n\t\t}\n\t\tconst key = this.debounceKey(event);\n\t\tif (event.text.trimStart().startsWith(\"/\")) {\n\t\t\t// Commands must not merge: flush any pending text, emit immediately.\n\t\t\tthis.flushPending(key);\n\t\t\tthis.handler(event);\n\t\t\treturn;\n\t\t}\n\t\tconst existing = this.pending.get(key);\n\t\tif (existing) {\n\t\t\tthis.scheduler.clearTimeout(existing.timer);\n\t\t\t// Merge: earlier text first, latest messageId (and other fields) win.\n\t\t\tconst mergedAttachments = [...(existing.event.attachments ?? []), ...(event.attachments ?? [])];\n\t\t\texisting.event = {\n\t\t\t\t...event,\n\t\t\t\ttext: `${existing.event.text}\\n${event.text}`,\n\t\t\t\t...(mergedAttachments.length > 0 ? { attachments: mergedAttachments } : {}),\n\t\t\t};\n\t\t\texisting.timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t} else {\n\t\t\tconst timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t\tthis.pending.set(key, { event, timer });\n\t\t}\n\t}\n\n\t/** lunr: download + base64-encode inbound image media for one Telegram message. */\n\tprivate async downloadMedia(message: TelegramMessage): Promise<InboundAttachment[]> {\n\t\tconst descriptors = collectTelegramMedia(message);\n\t\tif (descriptors.length === 0) return [];\n\t\tconst attachments: InboundAttachment[] = [];\n\t\tfor (const descriptor of descriptors) {\n\t\t\tif (descriptor.fileSize !== undefined && descriptor.fileSize > this.maxMediaBytes) continue;\n\t\t\ttry {\n\t\t\t\tconst fileResult = (await this.callApi(\"getFile\", { file_id: descriptor.fileId })) as\n\t\t\t\t\t| { file_path?: string; file_size?: number }\n\t\t\t\t\t| undefined;\n\t\t\t\tconst filePath = fileResult?.file_path;\n\t\t\t\tif (!filePath) continue;\n\t\t\t\tif (fileResult?.file_size !== undefined && fileResult.file_size > this.maxMediaBytes) continue;\n\t\t\t\tconst bytes = await this.downloadFile(filePath);\n\t\t\t\tif (bytes.byteLength > this.maxMediaBytes) continue;\n\t\t\t\tattachments.push({\n\t\t\t\t\tdata: Buffer.from(bytes).toString(\"base64\"),\n\t\t\t\t\tmimeType: descriptor.mimeType,\n\t\t\t\t\t...(descriptor.filename ? { filename: descriptor.filename } : {}),\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\t// Media download failed — drop this attachment, keep the text turn.\n\t\t\t}\n\t\t}\n\t\treturn attachments;\n\t}\n\n\tprivate flushPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t\tthis.handler?.(entry.event);\n\t}\n\n\tprivate cancelPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Outbound\n\t// -----------------------------------------------------------------------\n\n\tprivate buildSendBody(chatId: string, text: string, opts?: SendOptions): Record<string, unknown> {\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, text, parse_mode: \"Markdown\" };\n\t\tif (opts?.threadId !== undefined) body.message_thread_id = Number(opts.threadId);\n\t\tif (opts?.replyTo !== undefined) body.reply_parameters = { message_id: Number(opts.replyTo) };\n\t\treturn body;\n\t}\n\n\tprivate buildReplyMarkup(\n\t\tbuttons?: ButtonSpec[][],\n\t): { inline_keyboard: { text: string; callback_data: string }[][] } | undefined {\n\t\tif (buttons === undefined) return undefined;\n\t\treturn {\n\t\t\tinline_keyboard: buttons.map((row) => row.map((b) => ({ text: b.label, callback_data: b.data }))),\n\t\t};\n\t}\n\n\tasync send(chatId: string, text: string, opts?: SendOptions): Promise<SendResult> {\n\t\t// A completed send ends the typing bubble — stop the refresher (see sendTyping).\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\treturn this.callSend(body);\n\t}\n\n\tasync sendButtons(chatId: string, text: string, buttons: ButtonSpec[][], opts?: SendOptions): Promise<SendResult> {\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup) body.reply_markup = markup;\n\t\treturn this.callSend(body);\n\t}\n\n\tasync answerCallback(id: string, text?: string): Promise<void> {\n\t\tconst body: Record<string, unknown> = { callback_query_id: id };\n\t\tif (text !== undefined) body.text = text;\n\t\tawait this.callApi(\"answerCallbackQuery\", body).catch(() => {});\n\t}\n\n\t/** lunr: register the bot's slash-command menu (Telegram client autocomplete). Best-effort — caller catches. */\n\tasync sendFile(chatId: string, file: string, opts?: SendOptions): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst data = new FormData();\n\t\t\tdata.set(\"chat_id\", chatId);\n\t\t\tif (opts?.threadId) data.set(\"message_thread_id\", opts.threadId);\n\t\t\tdata.set(\"document\", new Blob([new Uint8Array(readFileSync(file))]), basename(file));\n\t\t\tconst response = await fetch(`${API_BASE}/bot${this.cfg.token}/sendDocument`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: data,\n\t\t\t\tsignal: AbortSignal.timeout(30_000),\n\t\t\t});\n\t\t\tif (!response.ok) return { success: false, error: `Telegram file delivery failed: HTTP ${response.status}` };\n\t\t\treturn { success: true };\n\t\t} catch {\n\t\t\treturn { success: false, error: \"Telegram file delivery failed. Check your connection.\" };\n\t\t}\n\t}\n\n\tasync registerCommands(commands: { name: string; description: string }[]): Promise<void> {\n\t\tconst valid = commands\n\t\t\t.filter((c) => /^[a-z0-9_]{1,32}$/.test(c.name) && c.description.trim().length > 0)\n\t\t\t.map((c) => ({ command: c.name, description: c.description.trim().slice(0, 256) }));\n\t\tif (valid.length === 0) return;\n\t\tawait this.callApi(\"setMyCommands\", { commands: valid });\n\t}\n\n\tprivate async callSend(body: Record<string, unknown>): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t} catch (err) {\n\t\t\tif (err instanceof TelegramApiError && err.status === 400 && body.parse_mode !== undefined) {\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message)) {\n\t\t\t\t\t// Telegram rejected our markup: retry once as plain text.\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\treturn this.callSend(plain);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\tawait this.sleep((err.retryAfter ?? 1) * 1000);\n\t\t\t\ttry {\n\t\t\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t\t\t} catch (retryErr) {\n\t\t\t\t\treturn { success: false, error: errMessage(retryErr), retryable: true };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync editMessage(chatId: string, messageId: string, text: string, buttons?: ButtonSpec[][]): Promise<SendResult> {\n\t\tconst body: Record<string, unknown> = {\n\t\t\tchat_id: chatId,\n\t\t\tmessage_id: Number(messageId),\n\t\t\ttext,\n\t\t\tparse_mode: \"Markdown\",\n\t\t};\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup !== undefined) body.reply_markup = markup;\n\t\ttry {\n\t\t\tawait this.callApi(\"editMessageText\", body);\n\t\t\treturn { success: true, messageId };\n\t\t} catch (err) {\n\t\t\tif (err instanceof TelegramApiError && err.status === 400) {\n\t\t\t\tif (NOT_MODIFIED_RE.test(err.message)) return { success: true, messageId };\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message) && body.parse_mode !== undefined) {\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.callApi(\"editMessageText\", plain);\n\t\t\t\t\t\treturn { success: true, messageId };\n\t\t\t\t\t} catch (retryErr) {\n\t\t\t\t\t\treturn { success: false, error: errMessage(retryErr) };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync sendTyping(chatId: string, threadId?: string): Promise<void> {\n\t\t// Self-contained typing refresher (see file header): re-send the action\n\t\t// every 4s until send() completes for this chat or the 2min safety net\n\t\t// fires — all errors swallowed.\n\t\tconst key = typingKey(chatId, threadId);\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, action: \"typing\" };\n\t\tif (threadId !== undefined) body.message_thread_id = Number(threadId);\n\t\tawait this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\tconst existing = this.typing.get(key);\n\t\tif (existing) {\n\t\t\texisting.lastRefresh = this.now();\n\t\t\treturn;\n\t\t}\n\t\tconst interval = this.scheduler.setInterval(() => {\n\t\t\tconst entry = this.typing.get(key);\n\t\t\tif (!entry) return;\n\t\t\tif (this.now() - entry.lastRefresh > TYPING_SAFETY_MS) {\n\t\t\t\tthis.stopTyping(key);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvoid this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\t}, TYPING_INTERVAL_MS);\n\t\tthis.typing.set(key, { interval, lastRefresh: this.now() });\n\t}\n\n\tprivate stopTyping(key: string): void {\n\t\tconst entry = this.typing.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearInterval(entry.interval);\n\t\tthis.typing.delete(key);\n\t}\n}\n\nfunction typingKey(chatId: string, threadId?: string): string {\n\treturn `${chatId}:${threadId ?? \"\"}`;\n}\n"]}
1
+ {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../../src/gateway/adapters/telegram.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway Telegram adapter (Phase 2b).\n *\n * Talks to the Telegram Bot API over HTTPS long-polling (zero-dep: global\n * fetch). connect() verifies the token via getMe, caches {botId, botUsername},\n * then starts the poll loop (getUpdates, offset-tracked, 30s long-poll). The\n * loop honors 429 retry_after, backs off exponentially on network/5xx errors\n * (1s→2s→5s→…max 30s, reset on success) and stops on disconnect() via an\n * AbortController.\n *\n * Inbound: updateToEvent() is a pure update → MessageEvent mapping (dm/group/\n * channel chat types, forum thread ids with the General topic normalized away,\n * @-mention / text_mention / `/cmd@bot` detection with the mention stripped,\n * own + all-bot messages dropped, media-only dropped — captions accepted).\n * Consecutive texts from the same chat/thread/user are debounced 300ms and\n * merged (\"\\n\"-joined, latest messageId kept) to reassemble client-split long\n * messages; texts starting with \"/\" flush immediately so commands never merge.\n * Callback queries are polled and mapped via callbackQueryToEvent().\n *\n * Outbound: sendMessage/editMessageText with parse_mode \"Markdown\" and a\n * one-shot plain-text retry when Telegram rejects the markup; send() honors\n * 429 retry_after once and then reports { success: false, retryable: true }.\n * Message splitting is NOT done here — the router owns that (text.ts).\n *\n * Typing: sendTyping() is a self-contained refresher — Telegram typing\n * bubbles expire after ~5s, so the action is re-sent on an unref'd 4s\n * interval per chat. The interval stops when send() completes for that chat\n * or after 2 minutes without a refresh (safety net). Keeping this here means\n * router/agent-bridge stay untouched.\n *\n * Testability: all Bot API HTTP goes through an injectable callApi; timers go\n * through an injectable Scheduler; backoffDelayMs() is a pure function.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { isAuthorized } from \"../authz.ts\";\nimport { loadGatewayConfig, type PlatformConfig } from \"../config.ts\";\nimport { downloadAttachment } from \"../download.ts\";\nimport { createPairingStore } from \"../pairing.ts\";\nimport type {\n\tButtonSpec,\n\tCallbackEvent,\n\tChatType,\n\tInboundAttachment,\n\tMessageEvent,\n\tPlatformAdapter,\n\tSendOptions,\n\tSendResult,\n} from \"../types.ts\";\n\nconst API_BASE = \"https://api.telegram.org\";\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst TYPING_INTERVAL_MS = 4000; // bubbles expire after ~5s\nconst TYPING_SAFETY_MS = 120_000; // auto-stop after 2min without a refresh\nconst REPLY_TO_TEXT_MAX = 500;\nconst MARKDOWN_PARSE_ERROR_RE = /can't parse|parse entities/i;\nconst NOT_MODIFIED_RE = /message is not modified/i;\n\n// ---------------------------------------------------------------------------\n// Bot API shapes (only the fields the adapter reads)\n// ---------------------------------------------------------------------------\n\nexport interface TelegramUser {\n\tid: number;\n\tis_bot?: boolean;\n\tusername?: string;\n\tfirst_name?: string;\n}\n\nexport interface TelegramChat {\n\tid: number;\n\ttype: string;\n\tis_forum?: boolean;\n}\n\nexport interface TelegramEntity {\n\ttype: string;\n\toffset: number; // UTF-16 code units — same indexing as JS strings\n\tlength: number;\n\tuser?: TelegramUser;\n}\n\nexport interface TelegramPhotoSize {\n\tfile_id: string;\n\tfile_unique_id: string;\n\twidth: number;\n\theight: number;\n\tfile_size?: number;\n}\n\nexport interface TelegramDocument {\n\tfile_id: string;\n\tfile_unique_id: string;\n\tfile_name?: string;\n\tmime_type?: string;\n\tfile_size?: number;\n}\n\nexport interface TelegramMessage {\n\tmessage_id: number;\n\tfrom?: TelegramUser;\n\tchat: TelegramChat;\n\ttext?: string;\n\tcaption?: string;\n\tentities?: TelegramEntity[];\n\tcaption_entities?: TelegramEntity[];\n\tmessage_thread_id?: number;\n\treply_to_message?: { text?: string };\n\tphoto?: TelegramPhotoSize[];\n\tdocument?: TelegramDocument;\n}\n\nexport interface TelegramUpdate {\n\tupdate_id: number;\n\tmessage?: TelegramMessage;\n\tcallback_query?: TelegramCallbackQuery;\n}\n\nexport interface TelegramCallbackQuery {\n\tid: string;\n\tfrom: TelegramUser;\n\tmessage?: TelegramMessage;\n\tdata?: string;\n}\n\nexport interface BotInfo {\n\tbotId: number;\n\tbotUsername: string;\n}\n\ninterface TelegramApiResponse {\n\tok: boolean;\n\tresult?: unknown;\n\tdescription?: string;\n\terror_code?: number;\n\tparameters?: { retry_after?: number };\n}\n\n/** Bot API call: resolves with the unwrapped `result`; throws TelegramApiError on API/HTTP errors. */\nexport type CallApi = (method: string, body: Record<string, unknown>, signal?: AbortSignal) => Promise<unknown>;\n\n/** Timer injection seam (production default uses globals, unref'd). */\nexport interface Scheduler {\n\tsetTimeout(fn: () => void, ms: number): ReturnType<typeof setTimeout>;\n\tclearTimeout(handle: ReturnType<typeof setTimeout>): void;\n\tsetInterval(fn: () => void, ms: number): ReturnType<typeof setInterval>;\n\tclearInterval(handle: ReturnType<typeof setInterval>): void;\n}\n\nexport class TelegramApiError extends Error {\n\treadonly status: number;\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, description: string, retryAfter?: number) {\n\t\tsuper(description);\n\t\tthis.name = \"TelegramApiError\";\n\t\tthis.status = status;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n\n/** Exponential poll-error backoff: 1s→2s→5s→10s→30s (max), indexed by consecutive failure count. */\nexport const BACKOFF_DELAYS_MS = [1000, 2000, 5000, 10000, 30000] as const;\n\nexport function backoffDelayMs(consecutiveFailures: number): number {\n\tconst index = Math.min(Math.max(consecutiveFailures, 0), BACKOFF_DELAYS_MS.length - 1);\n\treturn BACKOFF_DELAYS_MS[index];\n}\n\nconst defaultScheduler: Scheduler = {\n\tsetTimeout: (fn, ms) => {\n\t\tconst handle = setTimeout(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearTimeout: (handle) => clearTimeout(handle),\n\tsetInterval: (fn, ms) => {\n\t\tconst handle = setInterval(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearInterval: (handle) => clearInterval(handle),\n};\n\nasync function defaultCallApi(\n\ttoken: string,\n\tmethod: string,\n\tbody: Record<string, unknown>,\n\tsignal?: AbortSignal,\n): Promise<unknown> {\n\tconst res = await fetch(`${API_BASE}/bot${token}/${method}`, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"content-type\": \"application/json\" },\n\t\tbody: JSON.stringify(body),\n\t\tsignal,\n\t});\n\tconst data = (await res.json().catch(() => undefined)) as TelegramApiResponse | undefined;\n\tif (res.status === 429) {\n\t\tthrow new TelegramApiError(429, data?.description ?? \"rate limited\", data?.parameters?.retry_after);\n\t}\n\tif (!res.ok || !data?.ok) {\n\t\tthrow new TelegramApiError(\n\t\t\tdata?.error_code ?? res.status,\n\t\t\tdata?.description ?? `HTTP ${res.status}`,\n\t\t\tdata?.parameters?.retry_after,\n\t\t);\n\t}\n\treturn data.result;\n}\n\n/** lunr: download an inbound file by its file_path from the Telegram file API. */\nasync function defaultDownloadFile(token: string, filePath: string): Promise<Uint8Array> {\n\treturn downloadAttachment(`${API_BASE}/file/bot${token}/${filePath}`);\n}\n\nfunction errMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction chatTypeOf(chat: TelegramChat): ChatType | null {\n\tswitch (chat.type) {\n\t\tcase \"private\":\n\t\t\treturn \"dm\";\n\t\tcase \"group\":\n\t\tcase \"supergroup\":\n\t\t\treturn \"group\";\n\t\tcase \"channel\":\n\t\t\treturn \"channel\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\n/**\n * Detect bot mentions via entities and strip them from the text.\n * - \"mention\" whose slice is `@<botUsername>` (case-insensitive): whole slice removed.\n * - \"text_mention\" whose user is the bot: whole slice removed.\n * - \"bot_command\" containing `@<botUsername>` (e.g. /new@lunrbot): only the\n * \"@bot\" suffix is removed so router slash-command parsing still works.\n */\nfunction extractMention(\n\ttext: string,\n\tentities: TelegramEntity[],\n\tbotInfo: BotInfo,\n): { text: string; mentioned: boolean } {\n\tif (!botInfo.botUsername) return { text: text.trim(), mentioned: false };\n\tconst botAt = `@${botInfo.botUsername}`.toLowerCase();\n\tconst removals: Array<[number, number]> = [];\n\tlet mentioned = false;\n\tfor (const entity of entities) {\n\t\tconst slice = text.slice(entity.offset, entity.offset + entity.length);\n\t\tif (entity.type === \"mention\" && slice.toLowerCase() === botAt) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"text_mention\" && entity.user?.id === botInfo.botId) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"bot_command\" && slice.toLowerCase().includes(botAt)) {\n\t\t\tmentioned = true;\n\t\t\tconst at = slice.toLowerCase().indexOf(botAt);\n\t\t\tremovals.push([entity.offset + at, entity.offset + at + botAt.length]);\n\t\t}\n\t}\n\tif (removals.length === 0) return { text: text.trim(), mentioned };\n\tremovals.sort((a, b) => a[0] - b[0]);\n\tlet out = \"\";\n\tlet pos = 0;\n\tfor (const [start, end] of removals) {\n\t\tout += text.slice(pos, start);\n\t\tpos = Math.max(pos, end);\n\t}\n\tout += text.slice(pos);\n\treturn { text: out.trim(), mentioned };\n}\n\n/**\n * lunr: media descriptor for an inbound image (photo or image document).\n * The adapter downloads the bytes via getFile + downloadFile and base64-encodes them.\n */\nexport interface TelegramMediaDescriptor {\n\tfileId: string;\n\tmimeType: string;\n\tfilename?: string;\n\tfileSize?: number;\n}\n\n/** Collect the largest photo and document attachments. */\nexport function collectTelegramMedia(message: TelegramMessage): TelegramMediaDescriptor[] {\n\tconst descriptors: TelegramMediaDescriptor[] = [];\n\tif (message.photo && message.photo.length > 0) {\n\t\t// Largest size is last per Telegram Bot API ordering.\n\t\tconst largest = message.photo[message.photo.length - 1];\n\t\tdescriptors.push({\n\t\t\tfileId: largest.file_id,\n\t\t\tmimeType: \"image/jpeg\",\n\t\t\t...(largest.file_size !== undefined ? { fileSize: largest.file_size } : {}),\n\t\t});\n\t}\n\tif (message.document) {\n\t\tdescriptors.push({\n\t\t\tfileId: message.document.file_id,\n\t\t\tmimeType: message.document.mime_type ?? \"application/octet-stream\",\n\t\t\t...(message.document.file_name ? { filename: message.document.file_name } : {}),\n\t\t\t...(message.document.file_size !== undefined ? { fileSize: message.document.file_size } : {}),\n\t\t});\n\t}\n\treturn descriptors;\n}\n\n/**\n * Map a Telegram update to a MessageEvent; null = drop (non-message update,\n * own message, any bot message, unknown chat type, or a message with neither\n * text nor inbound image media).\n */\nexport function updateToEvent(update: TelegramUpdate, botInfo: BotInfo): MessageEvent | null {\n\tconst message = update.message;\n\tif (!message) return null;\n\tconst from = message.from;\n\tif (!from) return null; // channel posts without a user signature\n\tif (from.id === botInfo.botId) return null; // own messages\n\tif (from.is_bot) return null; // all bots (hermes exclusive_bot_mentions, simplified)\n\n\tconst hasMedia = collectTelegramMedia(message).length > 0;\n\tconst rawText = message.text ?? message.caption;\n\tif (!rawText && !hasMedia) return null; // no text and no image media\n\tconst rawEntities = message.text !== undefined ? message.entities : message.caption_entities;\n\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\n\t// Forum threads only; the General topic (id 1) is normalized away (hermes).\n\tconst threadId =\n\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t? String(message.message_thread_id)\n\t\t\t: undefined;\n\n\tconst { text, mentioned } = extractMention(rawText ?? \"\", rawEntities ?? [], botInfo);\n\n\tconst replyText = message.reply_to_message?.text;\n\n\treturn {\n\t\ttext,\n\t\tmessageId: String(message.message_id),\n\t\treplyToText: replyText !== undefined ? replyText.slice(0, REPLY_TO_TEXT_MAX) : undefined,\n\t\tsource: {\n\t\t\tplatform: \"telegram\",\n\t\t\tchatId: String(message.chat.id),\n\t\t\tchatType,\n\t\t\tuserId: String(from.id),\n\t\t\tuserName: from.username ?? from.first_name,\n\t\t\tthreadId,\n\t\t},\n\t\tmetadata: mentioned ? { mentionedBot: true } : undefined,\n\t};\n}\n\nexport function callbackQueryToEvent(query: TelegramCallbackQuery): CallbackEvent | null {\n\tconst message = query.message;\n\tif (!message) return null;\n\tconst from = query.from;\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\treturn {\n\t\tid: query.id,\n\t\tchatId: String(message.chat.id),\n\t\tmessageId: String(message.message_id),\n\t\tuserId: String(from.id),\n\t\tuserName: from.username ?? from.first_name,\n\t\tdata: query.data ?? \"\",\n\t\tthreadId:\n\t\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t\t? String(message.message_thread_id)\n\t\t\t\t: undefined,\n\t};\n}\n\nexport interface TelegramAdapterOptions {\n\t/** Injected Bot API transport (tests). When omitted, HTTPS calls are made with cfg.token. */\n\tcallApi?: CallApi;\n\t/** lunr: injected file downloader for inbound media (tests). Downloads from api.telegram.org/file/bot<token>/<file_path>. */\n\tdownloadFile?: (filePath: string) => Promise<Uint8Array>;\n\t/** lunr: skip inbound media larger than this many bytes (pre-download). Defaults to 10MB. */\n\tmaxMediaBytes?: number;\n\tscheduler?: Scheduler;\n\tdebounceMs?: number;\n\tnow?: () => number;\n}\n\ninterface PendingEntry {\n\tevent: MessageEvent;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\ninterface TypingEntry {\n\tinterval: ReturnType<typeof setInterval>;\n\tlastRefresh: number;\n}\n\nexport class TelegramAdapter implements PlatformAdapter {\n\treadonly platform = \"telegram\";\n\treadonly maxMessageLength = 4096;\n\n\tprivate readonly callApi: CallApi;\n\tprivate readonly downloadFile: (filePath: string) => Promise<Uint8Array>;\n\tprivate readonly maxMediaBytes: number;\n\tprivate readonly scheduler: Scheduler;\n\tprivate readonly debounceMs: number;\n\tprivate readonly now: () => number;\n\tprivate handler?: (event: MessageEvent) => void;\n\tprivate callbackHandler?: (event: CallbackEvent) => void;\n\tprivate botInfo?: BotInfo;\n\tprivate offset = 0;\n\tprivate abortController?: AbortController;\n\tprivate readonly pending = new Map<string, PendingEntry>();\n\tprivate readonly typing = new Map<string, TypingEntry>();\n\n\tprivate readonly cfg: PlatformConfig;\n\tconstructor(cfg: PlatformConfig, options: TelegramAdapterOptions = {}) {\n\t\tthis.cfg = cfg;\n\t\tthis.scheduler = options.scheduler ?? defaultScheduler;\n\t\tthis.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\t\tthis.now = options.now ?? Date.now;\n\t\tif (options.callApi) {\n\t\t\tthis.callApi = options.callApi;\n\t\t} else {\n\t\t\tif (!cfg.token) {\n\t\t\t\tthrow new Error(\"TelegramAdapter: no bot token (set telegram.token or LUNR_TELEGRAM_BOT_TOKEN)\");\n\t\t\t}\n\t\t\tconst token = cfg.token;\n\t\t\tthis.callApi = (method, body, signal) => defaultCallApi(token, method, body, signal);\n\t\t}\n\t\t// lunr: inbound media download. When tests inject callApi they also inject\n\t\t// downloadFile; production uses the bot token against the Telegram file API.\n\t\tthis.downloadFile = options.downloadFile ?? ((filePath) => defaultDownloadFile(cfg.token ?? \"\", filePath));\n\t\tthis.maxMediaBytes = options.maxMediaBytes ?? 10 * 1024 * 1024;\n\t}\n\n\tonMessage(handler: (event: MessageEvent) => void): void {\n\t\tthis.handler = handler;\n\t}\n\n\tonCallback(handler: (event: CallbackEvent) => void): void {\n\t\tthis.callbackHandler = handler;\n\t}\n\n\tasync connect(): Promise<boolean> {\n\t\tconst me = (await this.callApi(\"getMe\", {})) as TelegramUser;\n\t\tthis.botInfo = { botId: me.id, botUsername: me.username ?? \"\" };\n\t\tthis.abortController = new AbortController();\n\t\tvoid this.pollLoop(this.abortController.signal); // intentionally not awaited\n\t\treturn true;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tthis.abortController?.abort();\n\t\tfor (const key of [...this.pending.keys()]) this.cancelPending(key);\n\t\tfor (const key of [...this.typing.keys()]) this.stopTyping(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Long-poll loop\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollLoop(signal: AbortSignal): Promise<void> {\n\t\tlet failures = 0;\n\t\twhile (!signal.aborted) {\n\t\t\ttry {\n\t\t\t\tconst updates = (await this.callApi(\n\t\t\t\t\t\"getUpdates\",\n\t\t\t\t\t{ offset: this.offset, timeout: 30, allowed_updates: [\"message\", \"callback_query\"] },\n\t\t\t\t\tsignal,\n\t\t\t\t)) as TelegramUpdate[];\n\t\t\t\tfailures = 0;\n\t\t\t\tfor (const update of updates) {\n\t\t\t\t\tthis.offset = update.update_id + 1;\n\t\t\t\t\tawait this.dispatchUpdate(update);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) return;\n\t\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\t\tconst retryAfter = Math.min(err.retryAfter ?? 1, 30);\n\t\t\t\t\tawait this.sleep(retryAfter * 1000, signal);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst delay = backoffDelayMs(failures);\n\t\t\t\tfailures += 1;\n\t\t\t\tconsole.error(`[gateway] telegram poll error (retry in ${delay}ms): ${errMessage(err)}`);\n\t\t\t\tawait this.sleep(delay, signal);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst timer = this.scheduler.setTimeout(resolve, ms);\n\t\t\tsignal?.addEventListener(\n\t\t\t\t\"abort\",\n\t\t\t\t() => {\n\t\t\t\t\tthis.scheduler.clearTimeout(timer);\n\t\t\t\t\tresolve();\n\t\t\t\t},\n\t\t\t\t{ once: true },\n\t\t\t);\n\t\t});\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Inbound dispatch + debounce\n\t// -----------------------------------------------------------------------\n\n\tprivate debounceKey(event: MessageEvent): string {\n\t\treturn `${event.source.chatId}:${event.source.threadId ?? \"\"}:${event.source.userId}`;\n\t}\n\n\tprivate async dispatchUpdate(update: TelegramUpdate): Promise<void> {\n\t\tif (!this.botInfo) return;\n\t\tif (update.callback_query) {\n\t\t\tconst event = callbackQueryToEvent(update.callback_query);\n\t\t\tif (event) this.callbackHandler?.(event);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.handler) return;\n\t\tconst event = updateToEvent(update, this.botInfo);\n\t\tif (!event) return;\n\t\t// lunr: download inbound image media before dispatch so the bridge can attach\n\t\t// images to the turn. Failures drop the media but keep the text turn going.\n\t\tif (\n\t\t\tupdate.message &&\n\t\t\tisAuthorized(event.source, { ...loadGatewayConfig(), telegram: this.cfg }, createPairingStore())\n\t\t) {\n\t\t\tconst attachments = await this.downloadMedia(update.message);\n\t\t\tif (attachments.length > 0) event.attachments = attachments;\n\t\t}\n\t\tconst key = this.debounceKey(event);\n\t\tif (event.text.trimStart().startsWith(\"/\")) {\n\t\t\t// Commands must not merge: flush any pending text, emit immediately.\n\t\t\tthis.flushPending(key);\n\t\t\tthis.handler(event);\n\t\t\treturn;\n\t\t}\n\t\tconst existing = this.pending.get(key);\n\t\tif (existing) {\n\t\t\tthis.scheduler.clearTimeout(existing.timer);\n\t\t\t// Merge: earlier text first, latest messageId (and other fields) win.\n\t\t\tconst mergedAttachments = [...(existing.event.attachments ?? []), ...(event.attachments ?? [])];\n\t\t\texisting.event = {\n\t\t\t\t...event,\n\t\t\t\ttext: `${existing.event.text}\\n${event.text}`,\n\t\t\t\t...(mergedAttachments.length > 0 ? { attachments: mergedAttachments } : {}),\n\t\t\t};\n\t\t\texisting.timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t} else {\n\t\t\tconst timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t\tthis.pending.set(key, { event, timer });\n\t\t}\n\t}\n\n\t/** lunr: download + base64-encode inbound image media for one Telegram message. */\n\tprivate async downloadMedia(message: TelegramMessage): Promise<InboundAttachment[]> {\n\t\tconst descriptors = collectTelegramMedia(message);\n\t\tif (descriptors.length === 0) return [];\n\t\tconst attachments: InboundAttachment[] = [];\n\t\tfor (const descriptor of descriptors) {\n\t\t\tif (descriptor.fileSize !== undefined && descriptor.fileSize > this.maxMediaBytes) continue;\n\t\t\ttry {\n\t\t\t\tconst fileResult = (await this.callApi(\"getFile\", { file_id: descriptor.fileId })) as\n\t\t\t\t\t| { file_path?: string; file_size?: number }\n\t\t\t\t\t| undefined;\n\t\t\t\tconst filePath = fileResult?.file_path;\n\t\t\t\tif (!filePath) continue;\n\t\t\t\tif (fileResult?.file_size !== undefined && fileResult.file_size > this.maxMediaBytes) continue;\n\t\t\t\tconst bytes = await this.downloadFile(filePath);\n\t\t\t\tif (bytes.byteLength > this.maxMediaBytes) continue;\n\t\t\t\tattachments.push({\n\t\t\t\t\tdata: Buffer.from(bytes).toString(\"base64\"),\n\t\t\t\t\tmimeType: descriptor.mimeType,\n\t\t\t\t\t...(descriptor.filename ? { filename: descriptor.filename } : {}),\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\t// Media download failed — drop this attachment, keep the text turn.\n\t\t\t}\n\t\t}\n\t\treturn attachments;\n\t}\n\n\tprivate flushPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t\tthis.handler?.(entry.event);\n\t}\n\n\tprivate cancelPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Outbound\n\t// -----------------------------------------------------------------------\n\n\tprivate buildSendBody(chatId: string, text: string, opts?: SendOptions): Record<string, unknown> {\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, text, parse_mode: \"Markdown\" };\n\t\tif (opts?.threadId !== undefined) body.message_thread_id = Number(opts.threadId);\n\t\tif (opts?.replyTo !== undefined) body.reply_parameters = { message_id: Number(opts.replyTo) };\n\t\treturn body;\n\t}\n\n\tprivate buildReplyMarkup(\n\t\tbuttons?: ButtonSpec[][],\n\t): { inline_keyboard: { text: string; callback_data: string }[][] } | undefined {\n\t\tif (buttons === undefined) return undefined;\n\t\treturn {\n\t\t\tinline_keyboard: buttons.map((row) => row.map((b) => ({ text: b.label, callback_data: b.data }))),\n\t\t};\n\t}\n\n\tasync send(chatId: string, text: string, opts?: SendOptions): Promise<SendResult> {\n\t\t// A completed send ends the typing bubble — stop the refresher (see sendTyping).\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\treturn this.callSend(body);\n\t}\n\n\tasync sendButtons(chatId: string, text: string, buttons: ButtonSpec[][], opts?: SendOptions): Promise<SendResult> {\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup) body.reply_markup = markup;\n\t\treturn this.callSend(body);\n\t}\n\n\tasync answerCallback(id: string, text?: string): Promise<void> {\n\t\tconst body: Record<string, unknown> = { callback_query_id: id };\n\t\tif (text !== undefined) body.text = text;\n\t\tawait this.callApi(\"answerCallbackQuery\", body).catch(() => {});\n\t}\n\n\t/** lunr: register the bot's slash-command menu (Telegram client autocomplete). Best-effort — caller catches. */\n\tasync sendFile(chatId: string, file: string, opts?: SendOptions): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst data = new FormData();\n\t\t\tdata.set(\"chat_id\", chatId);\n\t\t\tif (opts?.threadId) data.set(\"message_thread_id\", opts.threadId);\n\t\t\tdata.set(\"document\", new Blob([new Uint8Array(readFileSync(file))]), basename(file));\n\t\t\tconst response = await fetch(`${API_BASE}/bot${this.cfg.token}/sendDocument`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: data,\n\t\t\t\tsignal: AbortSignal.timeout(30_000),\n\t\t\t});\n\t\t\tif (!response.ok) return { success: false, error: `Telegram file delivery failed: HTTP ${response.status}` };\n\t\t\treturn { success: true };\n\t\t} catch {\n\t\t\treturn { success: false, error: \"Telegram file delivery failed. Check your connection.\" };\n\t\t}\n\t}\n\n\tasync registerCommands(commands: { name: string; description: string }[]): Promise<void> {\n\t\tconst valid = commands\n\t\t\t.filter((c) => /^[a-z0-9_]{1,32}$/.test(c.name) && c.description.trim().length > 0)\n\t\t\t.map((c) => ({ command: c.name, description: c.description.trim().slice(0, 256) }));\n\t\tif (valid.length === 0) return;\n\t\tawait this.callApi(\"setMyCommands\", { commands: valid });\n\t}\n\n\tprivate async callSend(body: Record<string, unknown>): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t} catch (err) {\n\t\t\tif (\n\t\t\t\terr instanceof TelegramApiError &&\n\t\t\t\terr.status === 400 &&\n\t\t\t\tbody.reply_parameters !== undefined &&\n\t\t\t\t/message to be replied not found/i.test(err.message)\n\t\t\t) {\n\t\t\t\tconst { reply_parameters: _dropped, ...plain } = body;\n\t\t\t\treturn this.callSend(plain);\n\t\t\t}\n\t\t\tif (err instanceof TelegramApiError && err.status === 400 && body.parse_mode !== undefined) {\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message)) {\n\t\t\t\t\t// Telegram rejected our markup: retry once as plain text.\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\treturn this.callSend(plain);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\tawait this.sleep((err.retryAfter ?? 1) * 1000);\n\t\t\t\ttry {\n\t\t\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t\t\t} catch (retryErr) {\n\t\t\t\t\treturn { success: false, error: errMessage(retryErr), retryable: true };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync editMessage(chatId: string, messageId: string, text: string, buttons?: ButtonSpec[][]): Promise<SendResult> {\n\t\tconst body: Record<string, unknown> = {\n\t\t\tchat_id: chatId,\n\t\t\tmessage_id: Number(messageId),\n\t\t\ttext,\n\t\t\tparse_mode: \"Markdown\",\n\t\t};\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup !== undefined) body.reply_markup = markup;\n\t\ttry {\n\t\t\tawait this.callApi(\"editMessageText\", body);\n\t\t\treturn { success: true, messageId };\n\t\t} catch (err) {\n\t\t\tif (err instanceof TelegramApiError && err.status === 400) {\n\t\t\t\tif (NOT_MODIFIED_RE.test(err.message)) return { success: true, messageId };\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message) && body.parse_mode !== undefined) {\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.callApi(\"editMessageText\", plain);\n\t\t\t\t\t\treturn { success: true, messageId };\n\t\t\t\t\t} catch (retryErr) {\n\t\t\t\t\t\treturn { success: false, error: errMessage(retryErr) };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync sendTyping(chatId: string, threadId?: string): Promise<void> {\n\t\t// Self-contained typing refresher (see file header): re-send the action\n\t\t// every 4s until send() completes for this chat or the 2min safety net\n\t\t// fires — all errors swallowed.\n\t\tconst key = typingKey(chatId, threadId);\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, action: \"typing\" };\n\t\tif (threadId !== undefined) body.message_thread_id = Number(threadId);\n\t\tawait this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\tconst existing = this.typing.get(key);\n\t\tif (existing) {\n\t\t\texisting.lastRefresh = this.now();\n\t\t\treturn;\n\t\t}\n\t\tconst interval = this.scheduler.setInterval(() => {\n\t\t\tconst entry = this.typing.get(key);\n\t\t\tif (!entry) return;\n\t\t\tif (this.now() - entry.lastRefresh > TYPING_SAFETY_MS) {\n\t\t\t\tthis.stopTyping(key);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvoid this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\t}, TYPING_INTERVAL_MS);\n\t\tthis.typing.set(key, { interval, lastRefresh: this.now() });\n\t}\n\n\tprivate stopTyping(key: string): void {\n\t\tconst entry = this.typing.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearInterval(entry.interval);\n\t\tthis.typing.delete(key);\n\t}\n}\n\nfunction typingKey(chatId: string, threadId?: string): string {\n\treturn `${chatId}:${threadId ?? \"\"}`;\n}\n"]}
@@ -120,6 +120,9 @@ export declare class AgentBridge {
120
120
  private readonly redoStack;
121
121
  private readonly creating;
122
122
  private readonly changing;
123
+ private readonly generations;
124
+ private shuttingDown;
125
+ private invalidate;
123
126
  constructor(options?: {
124
127
  cacheCap?: number;
125
128
  sessionFactory?: SessionFactory;
@@ -133,7 +136,7 @@ export declare class AgentBridge {
133
136
  shutdown(): Promise<void>;
134
137
  peekSession(key: string): BridgeSession | undefined;
135
138
  /** Return the live session for a key, or null when none exists yet. */
136
- getSession(key: string): Promise<BridgeSession | null>;
139
+ getSession(key: string, create?: boolean): Promise<BridgeSession | null>;
137
140
  /** Switch the chat to a different persisted session file. */
138
141
  switchSession(key: string, sessionFile: string): Promise<void>;
139
142
  private hasAttachedWork;