@ashx-j/lunr 0.2.23 → 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.
- package/CHANGELOG.md +22 -1
- package/README.md +10 -10
- package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.js +2 -2
- package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
- package/dist/builtin-extensions/lunr-plan-tools.d.ts +7 -7
- package/dist/builtin-extensions/lunr-plan-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-plan-tools.js +12 -12
- package/dist/builtin-extensions/lunr-plan-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.js +2 -0
- package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.js +1 -0
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.js +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -4
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +6 -4
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js +6 -3
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js +7 -2
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +4 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +5 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +3 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +19 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +17 -15
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +3 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +2 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +5 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/browser/schema.d.ts +1 -1
- package/dist/core/browser/schema.d.ts.map +1 -1
- package/dist/core/browser/schema.js +1 -1
- package/dist/core/browser/schema.js.map +1 -1
- package/dist/core/catalog-cache-scope.d.ts.map +1 -1
- package/dist/core/catalog-cache-scope.js +3 -1
- package/dist/core/catalog-cache-scope.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +3 -2
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/permissions.d.ts +16 -23
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +66 -141
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts +10 -12
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +16 -18
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/runtime-bridges.d.ts.map +1 -1
- package/dist/core/runtime-bridges.js +2 -0
- package/dist/core/runtime-bridges.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +13 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +9 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +40 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -4
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-permission-inherit.d.ts +6 -10
- package/dist/core/subagent-permission-inherit.d.ts.map +1 -1
- package/dist/core/subagent-permission-inherit.js +14 -14
- package/dist/core/subagent-permission-inherit.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +9 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/gateway/adapters/discord.d.ts.map +1 -1
- package/dist/gateway/adapters/discord.js +3 -8
- package/dist/gateway/adapters/discord.js.map +1 -1
- package/dist/gateway/adapters/telegram.d.ts.map +1 -1
- package/dist/gateway/adapters/telegram.js +7 -0
- package/dist/gateway/adapters/telegram.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts +4 -1
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +57 -17
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/approval.d.ts +2 -6
- package/dist/gateway/approval.d.ts.map +1 -1
- package/dist/gateway/approval.js +3 -7
- package/dist/gateway/approval.js.map +1 -1
- package/dist/gateway/buttons.d.ts.map +1 -1
- package/dist/gateway/buttons.js +29 -19
- package/dist/gateway/buttons.js.map +1 -1
- package/dist/gateway/commands.d.ts +2 -0
- package/dist/gateway/commands.d.ts.map +1 -1
- package/dist/gateway/commands.js +39 -18
- package/dist/gateway/commands.js.map +1 -1
- package/dist/gateway/config.d.ts +1 -0
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +2 -0
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/cron.d.ts +1 -1
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +2 -2
- package/dist/gateway/cron.js.map +1 -1
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +26 -26
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/mobile-commands.d.ts.map +1 -1
- package/dist/gateway/mobile-commands.js +15 -14
- package/dist/gateway/mobile-commands.js.map +1 -1
- package/dist/gateway/presenter.d.ts +12 -1
- package/dist/gateway/presenter.d.ts.map +1 -1
- package/dist/gateway/presenter.js +146 -48
- package/dist/gateway/presenter.js.map +1 -1
- package/dist/gateway/router.d.ts +5 -5
- package/dist/gateway/router.d.ts.map +1 -1
- package/dist/gateway/router.js +96 -42
- package/dist/gateway/router.js.map +1 -1
- package/dist/gateway/service.d.ts.map +1 -1
- package/dist/gateway/service.js +13 -4
- package/dist/gateway/service.js.map +1 -1
- package/dist/gateway/setup.d.ts +5 -0
- package/dist/gateway/setup.d.ts.map +1 -1
- package/dist/gateway/setup.js +98 -42
- package/dist/gateway/setup.js.map +1 -1
- package/dist/gateway/stream.d.ts +6 -5
- package/dist/gateway/stream.d.ts.map +1 -1
- package/dist/gateway/stream.js +24 -11
- package/dist/gateway/stream.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +23 -3
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.js +2 -2
- package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +76 -90
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/node-runtime/{agent-session-services-GZVSOGYR.js → agent-session-services-YXXFCP5J.js} +12 -12
- package/dist/node-runtime/{builtin-extensions-QVCXNQT4.js → builtin-extensions-ZLAX7TUH.js} +3 -3
- package/dist/node-runtime/{chunk-W64JC4E7.js → chunk-2HHLJWGD.js} +3 -87
- package/dist/node-runtime/{chunk-VWCE2HSZ.js → chunk-2HMIRK7W.js} +4 -4
- package/dist/node-runtime/{chunk-XLTL257W.js → chunk-2KIPT4HJ.js} +5 -1
- package/dist/node-runtime/{chunk-RIR5EDDJ.js → chunk-3XPLBUEM.js} +17 -12
- package/dist/node-runtime/{chunk-QAZOULTU.js → chunk-6XL67VVY.js} +40 -6
- package/dist/node-runtime/chunk-7S3T6YEA.js +97 -0
- package/dist/node-runtime/{chunk-R3K2F5GI.js → chunk-B4HP6ZWM.js} +2 -0
- package/dist/node-runtime/{chunk-TQGPP4SC.js → chunk-CDVHZOOK.js} +1 -1
- package/dist/node-runtime/{chunk-YJMMG7C6.js → chunk-DSIKTUPW.js} +1 -1
- package/dist/node-runtime/{chunk-LFZDVKSP.js → chunk-E4D3IGP2.js} +41 -25
- package/dist/node-runtime/{chunk-OFKSVRV7.js → chunk-GYIXXQAO.js} +1 -1
- package/dist/node-runtime/{chunk-BX2RB477.js → chunk-HFVZUPFJ.js} +1 -1
- package/dist/node-runtime/{chunk-AZJHNQKQ.js → chunk-I54LGHWM.js} +1 -1
- package/dist/node-runtime/{chunk-YHQ3NBB3.js → chunk-IJIFHRTB.js} +20 -18
- package/dist/node-runtime/{chunk-GS66LV4M.js → chunk-KJ6DWS3K.js} +4 -4
- package/dist/node-runtime/{chunk-HPUP7E4T.js → chunk-KWIYUUZK.js} +68 -42
- package/dist/node-runtime/{chunk-TVJUOJZJ.js → chunk-LQP3RB67.js} +184 -1527
- package/dist/node-runtime/{chunk-VSELYKWU.js → chunk-LWYIVUGV.js} +2 -1
- package/dist/node-runtime/{chunk-JHYKXIMM.js → chunk-MPKSOJBT.js} +1 -1
- package/dist/node-runtime/{chunk-GMHJCCEN.js → chunk-NRKGMJYU.js} +4 -1
- package/dist/node-runtime/{chunk-CSUZVNYE.js → chunk-OVX6X377.js} +10 -6
- package/dist/node-runtime/{chunk-DD5YWFSJ.js → chunk-QTU6AKNJ.js} +130 -65
- package/dist/node-runtime/{chunk-LLUIPGDR.js → chunk-RC7DMV45.js} +110 -77
- package/dist/node-runtime/{chunk-UWPJL22I.js → chunk-SLVOAQOY.js} +15 -4
- package/dist/node-runtime/{chunk-EKVFP6YF.js → chunk-TORR5XBV.js} +17 -16
- package/dist/node-runtime/{chunk-UP2XBVH5.js → chunk-UOEDI7RP.js} +1 -1
- package/dist/node-runtime/{chunk-DQMGU675.js → chunk-VQFSUJJG.js} +20 -17
- package/dist/node-runtime/{chunk-X3URCYCU.js → chunk-WF7PGWRZ.js} +14 -14
- package/dist/node-runtime/chunk-YCUZEGJG.js +1361 -0
- package/dist/node-runtime/{chunk-5D7LC2ZQ.js → chunk-ZXSGTGJ6.js} +54 -85
- package/dist/node-runtime/cli-runtime.js +1 -1
- package/dist/node-runtime/{command-PDVTBRWA.js → command-6WDOYKS6.js} +5 -5
- package/dist/node-runtime/{commands-KAMO2T6P.js → commands-NZLSOHPE.js} +2 -2
- package/dist/node-runtime/{commands-XDCFGIGW.js → commands-QC6VUZJQ.js} +9 -9
- package/dist/node-runtime/{config-RORQUQFG.js → config-D4QUPAHX.js} +1 -1
- package/dist/node-runtime/{direct-tool-executor-YAVOLDFY.js → direct-tool-executor-77GW4F5P.js} +3 -3
- package/dist/node-runtime/{export-html-KSOIMVPI.js → export-html-VM4J2MNR.js} +1 -1
- package/dist/node-runtime/{gateway-AYWM5DIO.js → gateway-INAYBF4Q.js} +141 -85
- package/dist/node-runtime/index.js +23 -21
- package/dist/node-runtime/{init-X2LIUB2H.js → init-HS6NQXSK.js} +2 -2
- package/dist/node-runtime/{install-cli-3BO4MMQ6.js → install-cli-KL4UDDAD.js} +3 -3
- package/dist/node-runtime/{interactive-mode-4MFRKAHG.js → interactive-mode-Z5M7WYYZ.js} +14 -14
- package/dist/node-runtime/{interactive-view-TUZHCQM3.js → interactive-view-NGFAMHPW.js} +2 -2
- package/dist/node-runtime/{lunr-browser-RYFIIVJN.js → lunr-browser-IR7TL3J7.js} +4 -4
- package/dist/node-runtime/{lunr-cron-YWXEPZMQ.js → lunr-cron-LWIZJM2N.js} +8 -8
- package/dist/node-runtime/main.js +19 -18
- package/dist/node-runtime/{mobile-commands-LVHDAUDP.js → mobile-commands-J7OJEKFO.js} +6 -6
- package/dist/node-runtime/{model-runtime-PLQKAG4B.js → model-runtime-OO5HFLWE.js} +1 -1
- package/dist/node-runtime/{package-manager-cli-6GT3Z3CK.js → package-manager-cli-VYZM5WHN.js} +10 -8
- package/dist/node-runtime/{pi-intercom-HBPK5HSJ.js → pi-intercom-QM53RLTS.js} +2 -0
- package/dist/node-runtime/{pi-mcp-adapter-E6WULBKF.js → pi-mcp-adapter-5UKBZP44.js} +7 -6
- package/dist/node-runtime/{pi-subagents-CDWK2AKY.js → pi-subagents-DNY7EAIG.js} +23 -13
- package/dist/node-runtime/{proxy-modes-OSQCWRBI.js → proxy-modes-LGEUYD7R.js} +3 -3
- package/dist/node-runtime/rpc-entry.js +19 -18
- package/dist/node-runtime/{runtime-bridges-3XUQ46XW.js → runtime-bridges-VRDDR73P.js} +8 -1
- package/dist/node-runtime/{service-ISSP6VHE.js → service-MDQIPQ26.js} +1 -1
- package/dist/node-runtime/{session-handoff-HC7R5E24.js → session-handoff-GWARVEWT.js} +2 -2
- package/dist/node-runtime/{session-manager-CFN4UFM2.js → session-manager-JB5J6XBN.js} +1 -1
- package/dist/node-runtime/{settings-manager-6VEMXRDL.js → settings-manager-J4SPQPDJ.js} +10 -7
- package/dist/node-runtime/setup-CQ2NLVK5.js +214 -0
- package/dist/node-runtime/{subagent-executor-M5VWB3D3.js → subagent-executor-KZZQGAKV.js} +40 -7
- package/dist/node-runtime/{update-cli-UVKJWRRJ.js → update-cli-GTFJC7XK.js} +4 -5
- package/dist/startup/interactive-view.d.ts.map +1 -1
- package/dist/startup/interactive-view.js +5 -1
- package/dist/startup/interactive-view.js.map +1 -1
- package/docs/browser.md +1 -1
- package/docs/features.md +19 -18
- package/docs/index.md +1 -1
- package/docs/keybindings.md +1 -1
- package/docs/providers.md +6 -2
- package/docs/quickstart.md +2 -2
- package/docs/settings.md +1 -1
- package/docs/usage.md +4 -4
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/plan-mode/README.md +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
- package/dist/node-runtime/chunk-LISUTZS3.js +0 -19
- package/dist/node-runtime/setup-5SR2J35A.js +0 -155
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discord.js","sourceRoot":"","sources":["../../../src/gateway/adapters/discord.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway Discord adapter (Phase 3).\n *\n * Built on discord.js v14 (gateway websocket; the library owns reconnects and\n * REST rate-limit queuing internally, so neither is handled here). No voice,\n * no native slash-command registration — commands arrive as plain message\n * text, same as Telegram.\n *\n * Intents: Guilds, GuildMessages, DirectMessages, MessageContent. GuildMembers\n * is deliberately NOT requested: it is a privileged intent, and asking for it\n * without the dev-portal opt-in makes Discord refuse the identify — the bot\n * connects and immediately goes offline. v1 has no role-based config\n * (SessionSource.roleAuthorized stays undefined), so there is nothing to gain.\n *\n * Inbound: messageToEvent() is the pure message → MessageEvent mapping\n * (dm/group/thread chat types, <@id>/<@!id> mention detection with the mention\n * stripped, thread-participation counts as a mention, own + all-bot + system +\n * attachment-only messages dropped, other-mention-without-us dropped outside\n * DMs — hermes DISCORD_IGNORE_NO_MENTION). A module-level seen-id Set\n * (cap 500, FIFO evict) drops gateway-resume replays. cfg.ignoredChannels is\n * enforced adapter-side (channel id or parent id; blacklist wins over\n * mentions). Consecutive texts from the same chat/thread/user are debounced\n * 600ms and merged (\"\\n\"-joined, latest messageId kept); texts starting with\n * \"/\" flush immediately so commands never merge.\n * Button interactions are routed via interactionCreate → buttonInteractionToEvent().\n *\n * chatId/threadId mapping (pairs with session-keys.ts buildSessionKey):\n * dm → chatId = channel.id → agent:main:discord:dm:<channelId>\n * thread → chatId = PARENT channel id, threadId = → agent:main:discord:thread:<parentId>:<threadId>\n * thread channel id (shared session, no userId)\n * guild text/announcement → chatId = channel.id → agent:main:discord:group:<channelId>[:<userId>]\n * (chatType \"group\"; userId appended when groupSessionsPerUser is on)\n *\n * autoThread (cfg.discord.autoThread, default true): a bot-mentioning message\n * in a guild TEXT channel gets a thread spun off it (startThread, 60min\n * archive); the event is re-targeted to the thread (chatType \"thread\", chatId\n * parent id, threadId thread id). On failure the event stays in-channel.\n *\n * Thread participation: an in-memory Set of thread ids the bot has sent to\n * (recorded on every successful send() into a thread and on autoThread\n * creation); messages in those threads count as mentionedBot so follow-ups\n * don't need a re-mention. NOT persisted in v1 (hermes persists it).\n *\n * Outbound: channel.send({ content, reply: { messageReference } }) with a\n * one-shot plain-send fallback when the reply reference fails. DiscordAPIError\n * 50035 (invalid form body, e.g. overlong content) is reported non-retryable;\n * 429s are queued internally by discord.js; everything else is retryable.\n * editMessage resolves the channel via a messageId → channel map recorded at\n * send() time (router passes only chatId, but thread messages live in their\n * own channel); Unknown Message (10008) is a terminal { success: false }.\n * Message splitting is NOT done here — the router owns that (text.ts).\n *\n * Buttons: ActionRowBuilder rows of ButtonBuilder components (max 5×5).\n * answerCallback acknowledges the interaction; a text argument produces an\n * ephemeral reply (used for unauthorized/expired taps), otherwise deferUpdate().\n *\n * Typing: sendTyping() is a self-contained refresher — Discord typing bubbles\n * last ~10s, so the action is re-sent on an unref'd 8s interval per target.\n * The interval stops when send() completes for that target or after 2 minutes\n * without a refresh (safety net). All errors swallowed.\n *\n * Testability: the discord.js Client goes through an injectable clientFactory\n * (tests inject a minimal fake), timers through an injectable Scheduler, and\n * messageToEvent() operates on the structural DiscordMessageLike — plain mock\n * objects, no real Client construction.\n */\n\nimport {\n\tActionRowBuilder,\n\tApplicationCommandOptionType,\n\tButtonBuilder,\n\tButtonStyle,\n\tChannelType,\n\tClient,\n\tEvents,\n\tGatewayIntentBits,\n\ttype Interaction,\n} from \"discord.js\";\nimport { isAuthorized, isGatewayOwner } from \"../authz.ts\";\nimport { type DiscordConfig, loadGatewayConfig } 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\";\nimport type { Scheduler } from \"./telegram.ts\";\n\nconst DEFAULT_DEBOUNCE_MS = 600;\nconst TYPING_INTERVAL_MS = 8000; // Discord typing lasts ~10s\nconst TYPING_SAFETY_MS = 120_000; // auto-stop after 2min without a refresh\nconst REPLY_TO_TEXT_MAX = 500;\nconst THREAD_NAME_MAX = 40;\nconst SEEN_CAP = 500;\nconst SENT_CHANNEL_CAP = 500;\nconst CALLBACK_INTERACTION_CAP = 500;\nconst CHANNEL_CACHE_CAP = 500;\nconst PARTICIPATING_THREAD_CAP = 500;\nconst ERR_INVALID_FORM_BODY = 50035; // e.g. content over 2000 chars — non-retryable\nconst ERR_UNKNOWN_MESSAGE = 10008; // deleted/unknown message — terminal\n\nconst THREAD_TYPES: readonly number[] = [\n\tChannelType.PublicThread,\n\tChannelType.PrivateThread,\n\tChannelType.AnnouncementThread,\n];\n\n// ---------------------------------------------------------------------------\n// Structural discord.js shapes (only the fields the adapter reads)\n// ---------------------------------------------------------------------------\n\nexport interface DiscordSentMessageLike {\n\tid: string;\n}\n\nexport interface DiscordFetchedMessageLike {\n\tcontent?: string;\n\tedit?(options: string | { content: string; components?: unknown[] }): Promise<unknown>;\n}\n\nexport interface DiscordChannelLike {\n\tid: string;\n\ttype: number;\n\tparentId?: string | null;\n\tsend?(payload: {\n\t\tcontent: string;\n\t\tfiles?: string[];\n\t\treply?: { messageReference: string };\n\t\tcomponents?: unknown[];\n\t}): Promise<DiscordSentMessageLike>;\n\tsendTyping?(): Promise<unknown>;\n\tmessages?: {\n\t\tcache?: { get(id: string): { content?: string } | undefined };\n\t\tfetch(id: string): Promise<DiscordFetchedMessageLike | null>;\n\t};\n}\n\nexport interface DiscordButtonInteractionLike {\n\tid: string;\n\tcustomId: string;\n\tuser: { id: string; username?: string };\n\tmessage: { id: string; channel: DiscordChannelLike };\n\tisButton(): boolean;\n\tdeferUpdate(): Promise<unknown>;\n\treply?(options: { content: string; ephemeral: boolean }): Promise<unknown>;\n\tupdate?(options: { content: string; components?: unknown[] }): Promise<unknown>;\n}\n\nexport interface DiscordAttachmentLike {\n\tid: string;\n\t/** Direct CDN URL for the attachment content. */\n\turl: string;\n\tcontentType?: string;\n\tname?: string;\n\tsize?: number;\n}\n\nexport interface DiscordMessageLike {\n\tid: string;\n\tcontent?: string;\n\tsystem?: boolean;\n\tauthor: { id: string; bot?: boolean; username?: string };\n\tchannel: DiscordChannelLike;\n\treference?: { messageId?: string | null } | null;\n\tmentions?: {\n\t\thas(user: { id: string }): boolean;\n\t\trepliedUser?: { id: string } | null;\n\t};\n\tstartThread?(opts: { name: string; autoArchiveDuration: number }): Promise<DiscordChannelLike>;\n\tattachments?: DiscordAttachmentLike[];\n}\n\n/** Minimal Client surface; the real discord.js Client satisfies this. */\nexport interface DiscordClientLike {\n\tapplication?: Pick<NonNullable<Client[\"application\"]>, \"commands\"> | null;\n\tuser: { id: string } | null;\n\tlogin(token: string): Promise<unknown>;\n\ton(event: string, listener: (arg: unknown) => void): unknown;\n\tonce(event: string, listener: () => void): unknown;\n\tdestroy(): void;\n\tchannels: { fetch(id: string): Promise<DiscordChannelLike | null> };\n}\n\nexport type DiscordClientFactory = () => DiscordClientLike;\n\n// ---------------------------------------------------------------------------\n// Pure mapping + small helpers\n// ---------------------------------------------------------------------------\n\nfunction errMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\n/** lunr: download an inbound Discord attachment from its CDN URL. */\n/** Discord REST error code (DiscordAPIError.code) when present. */\nfunction discordErrorCode(err: unknown): number | undefined {\n\tconst code = (err as { code?: unknown } | null)?.code;\n\treturn typeof code === \"number\" ? code : undefined;\n}\n\nexport interface MessageToEventOptions {\n\t/** Channel ids (or parent ids for threads) to drop outright — blacklist wins over mentions. */\n\tignoredChannels?: readonly string[];\n\t/** Threads the bot has spoken in; messages there count as mentionedBot. */\n\tparticipatingThreads?: ReadonlySet<string>;\n}\n\nfunction isImageContentType(contentType: string | undefined): boolean {\n\treturn typeof contentType === \"string\" && contentType.split(\";\")[0]?.trim().toLowerCase().startsWith(\"image/\");\n}\n\n/** lunr: collect inbound image attachments (contentType image/*) from a Discord message. */\nexport function collectDiscordImageAttachments(message: DiscordMessageLike): DiscordAttachmentLike[] {\n\treturn Array.from(message.attachments?.values() ?? []).filter((attachment) =>\n\t\tisImageContentType(attachment.contentType),\n\t);\n}\n\n/**\n * Map a Discord message to a MessageEvent; null = drop (own message, any bot,\n * system message, ignored channel, unsupported channel type, or a non-DM\n * message mentioning others but not us). Messages with no text but image\n * attachments are kept (v1: images inbound).\n */\nexport function messageToEvent(\n\tmessage: DiscordMessageLike,\n\tbotUser: { id: string },\n\toptions: MessageToEventOptions = {},\n): MessageEvent | null {\n\tconst author = message.author;\n\tif (!author) return null;\n\tif (author.id === botUser.id) return null; // own messages\n\tif (author.bot) return null; // all bots (same simplification as telegram)\n\tif (message.system) return null;\n\tconst content = (message.content ?? \"\").trim();\n\tconst imageAttachments = collectDiscordImageAttachments(message);\n\tif (content === \"\" && imageAttachments.length === 0 && !Array.from(message.attachments?.values() ?? []).length)\n\t\treturn null;\n\n\tconst channel = message.channel;\n\tconst ignored = options.ignoredChannels ?? [];\n\tif (ignored.includes(channel.id) || (channel.parentId != null && ignored.includes(channel.parentId))) {\n\t\treturn null; // blacklist wins over mentions\n\t}\n\n\t// chatId/threadId mapping — see the file header for the session-key pairing.\n\tlet chatType: ChatType;\n\tlet chatId: string;\n\tlet threadId: string | undefined;\n\tif (channel.type === ChannelType.DM) {\n\t\tchatType = \"dm\";\n\t\tchatId = channel.id;\n\t} else if (THREAD_TYPES.includes(channel.type)) {\n\t\tchatType = \"thread\";\n\t\tchatId = channel.parentId ?? channel.id;\n\t\tthreadId = channel.id;\n\t} else if (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement) {\n\t\tchatType = \"group\";\n\t\tchatId = channel.id;\n\t} else {\n\t\treturn null; // voice/category/forum containers etc. — unsupported in v1\n\t}\n\n\tconst explicitMention = message.mentions?.has(botUser) === true;\n\tconst participating = threadId !== undefined && options.participatingThreads?.has(threadId) === true;\n\tconst mentionedBot = explicitMention || participating;\n\n\t// Multi-agent filter (hermes DISCORD_IGNORE_NO_MENTION): outside DMs, a\n\t// message that mentions other users/bots but not us is not for us.\n\tif (!mentionedBot && chatType !== \"dm\") {\n\t\tconst mentionedIds = [...content.matchAll(/<@!?(\\d+)>/g)].map((m) => m[1]);\n\t\tif (mentionedIds.some((id) => id !== botUser.id)) return null;\n\t}\n\n\tlet text = content;\n\tif (explicitMention) {\n\t\ttext = text.replace(new RegExp(`<@!?${botUser.id}>`, \"g\"), \"\").trim();\n\t}\n\n\tconst refId = message.reference?.messageId;\n\tconst cachedReply = refId != null ? channel.messages?.cache?.get(refId)?.content : undefined;\n\n\treturn {\n\t\ttext,\n\t\tmessageId: message.id,\n\t\treplyToText: cachedReply !== undefined ? cachedReply.slice(0, REPLY_TO_TEXT_MAX) : undefined,\n\t\tsource: {\n\t\t\tplatform: \"discord\",\n\t\t\tchatId,\n\t\t\tchatType,\n\t\t\tuserId: author.id,\n\t\t\tuserName: author.username,\n\t\t\tthreadId,\n\t\t},\n\t\tmetadata: mentionedBot ? { mentionedBot: true } : undefined,\n\t};\n}\n\nfunction mapDiscordChannel(\n\tchannel: DiscordChannelLike,\n): { chatType: ChatType; chatId: string; threadId?: string } | null {\n\tif (channel.type === ChannelType.DM) {\n\t\treturn { chatType: \"dm\", chatId: channel.id };\n\t}\n\tif (THREAD_TYPES.includes(channel.type)) {\n\t\treturn { chatType: \"thread\", chatId: channel.parentId ?? channel.id, threadId: channel.id };\n\t}\n\tif (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement) {\n\t\treturn { chatType: \"group\", chatId: channel.id };\n\t}\n\treturn null;\n}\n\nexport function buttonInteractionToEvent(interaction: DiscordButtonInteractionLike): CallbackEvent | null {\n\tconst channel = interaction.message.channel;\n\tconst mapped = mapDiscordChannel(channel);\n\tif (!mapped) return null;\n\treturn {\n\t\tid: interaction.id,\n\t\tchatId: mapped.chatId,\n\t\tmessageId: interaction.message.id,\n\t\tuserId: interaction.user.id,\n\t\tuserName: interaction.user.username,\n\t\tdata: interaction.customId,\n\t\tthreadId: mapped.threadId,\n\t};\n}\n\nfunction buildDiscordComponents(buttons: ButtonSpec[][]): unknown[] {\n\tconst rows: ActionRowBuilder<ButtonBuilder>[] = [];\n\tfor (const row of buttons) {\n\t\tconst actionRow = new ActionRowBuilder<ButtonBuilder>();\n\t\tfor (const button of row) {\n\t\t\tconst style = button.data === \"\" ? ButtonStyle.Secondary : ButtonStyle.Secondary;\n\t\t\tconst builder = new ButtonBuilder().setCustomId(button.data).setLabel(button.label).setStyle(style);\n\t\t\tif (button.data === \"\") builder.setDisabled(true);\n\t\t\tactionRow.addComponents(builder);\n\t\t}\n\t\trows.push(actionRow);\n\t}\n\treturn rows;\n}\n\n// ---------------------------------------------------------------------------\n// Gateway-resume replay dedup (module-level: one process = one bot connection)\n// ---------------------------------------------------------------------------\n\nconst seenMessageIds = new Set<string>();\n\n/** Returns true when this id was already seen (drop the replay). */\nfunction markMessageSeen(id: string): boolean {\n\tif (seenMessageIds.has(id)) return true;\n\tseenMessageIds.add(id);\n\tif (seenMessageIds.size > SEEN_CAP) {\n\t\tconst oldest = seenMessageIds.values().next().value; // Sets iterate in insertion order\n\t\tif (oldest !== undefined) seenMessageIds.delete(oldest);\n\t}\n\treturn false;\n}\n\n/** Test hook: reset RESUME dedup state between tests. */\nexport function resetSeenMessageIds(): void {\n\tseenMessageIds.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Adapter\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\nfunction defaultClientFactory(): DiscordClientLike {\n\t// No GatewayIntentBits.GuildMembers — see the file header for the\n\t// privileged-intent trap (bot stays offline without the portal opt-in).\n\treturn new Client({\n\t\tintents: [\n\t\t\tGatewayIntentBits.Guilds,\n\t\t\tGatewayIntentBits.GuildMessages,\n\t\t\tGatewayIntentBits.DirectMessages,\n\t\t\tGatewayIntentBits.MessageContent,\n\t\t],\n\t}) as unknown as DiscordClientLike;\n}\n\nexport interface DiscordAdapterOptions {\n\t/** Injected Client factory (tests). When omitted, a real discord.js Client is created. */\n\tclientFactory?: DiscordClientFactory;\n\t/** lunr: injected image downloader for inbound attachments (tests). Defaults to fetch(url). */\n\tdownloadAttachment?: (url: string) => Promise<Uint8Array>;\n\t/** lunr: skip inbound media larger than this many bytes. 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\nfunction typingKey(chatId: string, threadId?: string): string {\n\treturn `${chatId}:${threadId ?? \"\"}`;\n}\n\nexport class DiscordAdapter implements PlatformAdapter {\n\treadonly platform = \"discord\";\n\treadonly maxMessageLength = 2000;\n\n\tprivate readonly cfg: DiscordConfig;\n\tprivate readonly clientFactory: DiscordClientFactory;\n\tprivate readonly downloadAttachment: (url: 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 client?: DiscordClientLike;\n\tprivate botUserId?: string;\n\tprivate handler?: (event: MessageEvent) => void;\n\tprivate commandSuggestions?: Parameters<NonNullable<PlatformAdapter[\"setCommandSuggestions\"]>>[0];\n\tprivate callbackHandler?: (event: CallbackEvent) => void;\n\tprivate readonly pending = new Map<string, PendingEntry>();\n\tprivate readonly typing = new Map<string, TypingEntry>();\n\tprivate readonly channelCache = new Map<string, DiscordChannelLike | null>();\n\t/** Threads the bot has spoken in (session memory only; hermes persists this). */\n\tprivate readonly participatingThreads = new Set<string>();\n\t/** messageId → channel it was sent to, so editMessage finds thread messages. */\n\tprivate readonly sentMessageChannels = new Map<string, string>();\n\t/** callbackId → interaction, so answerCallback can acknowledge the click. */\n\tprivate readonly callbackInteractions = new Map<string, DiscordButtonInteractionLike>();\n\n\tconstructor(cfg: DiscordConfig, options: DiscordAdapterOptions = {}) {\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.clientFactory) {\n\t\t\tthis.clientFactory = options.clientFactory;\n\t\t} else {\n\t\t\tif (!cfg.token) {\n\t\t\t\tthrow new Error(\"DiscordAdapter: no bot token (set discord.token or LUNR_DISCORD_BOT_TOKEN)\");\n\t\t\t}\n\t\t\tthis.clientFactory = defaultClientFactory;\n\t\t}\n\t\tthis.downloadAttachment = options.downloadAttachment ?? downloadAttachment;\n\t\tthis.maxMediaBytes = options.maxMediaBytes ?? 10 * 1024 * 1024;\n\t}\n\n\tsetCommandSuggestions(\n\t\thandler: (source: MessageEvent[\"source\"], command: string) => Promise<readonly string[]>,\n\t): void {\n\t\tthis.commandSuggestions = handler;\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\tif (!this.cfg.token) {\n\t\t\tthrow new Error(\"DiscordAdapter: no bot token (set discord.token or LUNR_DISCORD_BOT_TOKEN)\");\n\t\t}\n\t\tconst client = this.clientFactory();\n\t\tthis.client = client;\n\t\tclient.on(Events.MessageCreate, (message) => {\n\t\t\tvoid this.handleMessage(message as DiscordMessageLike).catch((err) => {\n\t\t\t\tconsole.error(`[gateway] discord message handling error: ${errMessage(err)}`);\n\t\t\t});\n\t\t});\n\t\tclient.on(Events.InteractionCreate, (interaction) => {\n\t\t\tconst event = interaction as Interaction;\n\t\t\tconst handling = event.isButton()\n\t\t\t\t? this.handleInteraction(interaction as DiscordButtonInteractionLike)\n\t\t\t\t: this.handleNativeCommand(event);\n\t\t\tvoid handling.catch((err) => {\n\t\t\t\tconsole.error(`[gateway] discord interaction handling error: ${errMessage(err)}`);\n\t\t\t});\n\t\t});\n\t\tconst ready = new Promise<void>((resolve) => client.once(Events.ClientReady, () => resolve()));\n\t\ttry {\n\t\t\tawait client.login(this.cfg.token);\n\t\t} catch (err) {\n\t\t\tconsole.error(`[gateway] discord login failed: ${errMessage(err)}`);\n\t\t\treturn false;\n\t\t}\n\t\tawait ready;\n\t\tthis.botUserId = client.user?.id;\n\t\tif (this.botUserId === undefined) {\n\t\t\tconsole.error(\"[gateway] discord: client ready without a user — cannot map messages\");\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tasync disconnect(): Promise<void> {\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\tthis.channelCache.clear();\n\t\tthis.callbackInteractions.clear();\n\t\tthis.client?.destroy();\n\t\tthis.client = undefined;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Inbound dispatch + debounce\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleMessage(message: DiscordMessageLike): Promise<void> {\n\t\tif (!this.botUserId || !this.handler) return;\n\t\tif (markMessageSeen(message.id)) return; // gateway-resume replay\n\t\tconst event = messageToEvent(\n\t\t\tmessage,\n\t\t\t{ id: this.botUserId },\n\t\t\t{\n\t\t\t\tignoredChannels: this.cfg.ignoredChannels,\n\t\t\t\tparticipatingThreads: this.participatingThreads,\n\t\t\t},\n\t\t);\n\t\tif (!event) return;\n\t\tif (!isAuthorized(event.source, { ...loadGatewayConfig(), discord: this.cfg }, createPairingStore())) {\n\t\t\tthis.dispatchEvent(event);\n\t\t\treturn;\n\t\t}\n\t\tawait this.resolveReplyToText(message, event);\n\t\tawait this.maybeAutoThread(message, event);\n\t\tawait this.downloadAttachments(message, event);\n\t\tthis.dispatchEvent(event);\n\t}\n\n\t/** Download and encode attachments after authorization. */\n\tprivate async downloadAttachments(message: DiscordMessageLike, event: MessageEvent): Promise<void> {\n\t\tconst imageAttachments = Array.from(message.attachments?.values() ?? []);\n\t\tif (imageAttachments.length === 0) return;\n\t\tconst attachments: InboundAttachment[] = [];\n\t\tfor (const attachment of imageAttachments) {\n\t\t\tif (attachment.size !== undefined && attachment.size > this.maxMediaBytes) continue;\n\t\t\ttry {\n\t\t\t\tconst bytes = await this.downloadAttachment(attachment.url);\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: attachment.contentType ?? \"application/octet-stream\",\n\t\t\t\t\t...(attachment.name ? { filename: attachment.name } : {}),\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\tif (attachments.length > 0) event.attachments = attachments;\n\t}\n\n\tasync registerCommands(commands: { name: string; description: string }[]): Promise<void> {\n\t\tif (!this.client?.application) return;\n\t\tawait this.client.application.commands.set(\n\t\t\tcommands.slice(0, 95).map((command) => ({\n\t\t\t\tname: command.name,\n\t\t\t\tdescription: command.description.slice(0, 100),\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"args\",\n\t\t\t\t\t\tdescription: \"Command arguments or search\",\n\t\t\t\t\t\ttype: ApplicationCommandOptionType.String,\n\t\t\t\t\t\trequired: false,\n\t\t\t\t\t\tautocomplete: [\"mode\", \"thinking\", \"project\", \"sessions\"].includes(command.name),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})),\n\t\t);\n\t}\n\n\tprivate async handleNativeCommand(interaction: Interaction): Promise<boolean> {\n\t\tif (!interaction.isChatInputCommand?.() && !interaction.isAutocomplete?.()) return false;\n\t\tif (!interaction.isChatInputCommand() && !interaction.isAutocomplete()) return false;\n\t\tconst thread = interaction.channel?.isThread() ? interaction.channel : undefined;\n\t\tconst source = {\n\t\t\tplatform: \"discord\",\n\t\t\tchatId: thread?.parentId ?? interaction.channelId ?? interaction.user.id,\n\t\t\tthreadId: thread?.id,\n\t\t\tchatType: thread ? (\"thread\" as const) : interaction.inGuild() ? (\"channel\" as const) : (\"dm\" as const),\n\t\t\tuserId: interaction.user.id,\n\t\t\tuserName: interaction.user.username,\n\t\t};\n\t\tconst cfg = loadGatewayConfig();\n\t\tconst authorized = isAuthorized(source, cfg, createPairingStore());\n\t\tif (interaction.isAutocomplete()) {\n\t\t\tif (!authorized || !isGatewayOwner(source, cfg)) {\n\t\t\t\tawait interaction.respond([]);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst query = String(interaction.options.getFocused()).toLowerCase();\n\t\t\tlet values: string[] = [];\n\t\t\tif (interaction.commandName === \"mode\") values = [\"manual\", \"yolo\", \"plan\", \"auto\"];\n\t\t\tif (this.commandSuggestions && [\"thinking\", \"model\"].includes(interaction.commandName)) {\n\t\t\t\tvalues = [...(await this.commandSuggestions(source, interaction.commandName))];\n\t\t\t}\n\t\t\tif (interaction.commandName === \"project\") values = cfg.projectRoots ?? [];\n\t\t\tif (interaction.commandName === \"sessions\") {\n\t\t\t\tconst { SessionManager } = await import(\"../../core/session-manager.ts\");\n\t\t\t\tvalues = (\n\t\t\t\t\tawait Promise.race([\n\t\t\t\t\t\tSessionManager.listAll(),\n\t\t\t\t\t\tnew Promise<[]>((resolve) => setTimeout(() => resolve([]), 1500)),\n\t\t\t\t\t])\n\t\t\t\t).map((s) => s.name || s.id);\n\t\t\t}\n\t\t\tawait interaction.respond(\n\t\t\t\tvalues\n\t\t\t\t\t.filter((s) => s.toLowerCase().includes(query))\n\t\t\t\t\t.slice(0, 25)\n\t\t\t\t\t.map((value) => ({ name: value.slice(0, 100), value: value.slice(0, 100) })),\n\t\t\t);\n\t\t\treturn true;\n\t\t}\n\t\tawait interaction.reply({\n\t\t\tcontent: authorized\n\t\t\t\t? \"Command received.\"\n\t\t\t\t: \"Pair your account before using this command. Send the bot a private message for a pairing code.\",\n\t\t\tephemeral: true,\n\t\t});\n\t\tif (authorized)\n\t\t\tthis.handler?.({\n\t\t\t\ttext: `/${interaction.commandName}${interaction.options.getString(\"args\") ? ` ${interaction.options.getString(\"args\")}` : \"\"}`,\n\t\t\t\tsource,\n\t\t\t\tmessageId: \"\",\n\t\t\t\tmetadata: { mentionedBot: true, nativeCommand: true },\n\t\t\t});\n\t\treturn true;\n\t}\n\n\tasync sendFile(chatId: string, file: string, opts?: SendOptions): Promise<SendResult> {\n\t\tconst channel = await this.fetchChannel(opts?.threadId ?? chatId);\n\t\tif (!channel?.send) return { success: false, error: \"Channel is unavailable.\" };\n\t\ttry {\n\t\t\tconst message = await channel.send({ content: \"\", files: [file] });\n\t\t\treturn { success: true, messageId: message.id };\n\t\t} catch (error) {\n\t\t\treturn { success: false, error: errMessage(error) };\n\t\t}\n\t}\n\n\tprivate async handleInteraction(interaction: DiscordButtonInteractionLike): Promise<void> {\n\t\tif (!this.callbackHandler) return;\n\t\tif (!interaction.isButton()) return;\n\t\tconst event = buttonInteractionToEvent(interaction);\n\t\tif (!event) return;\n\t\tthis.callbackInteractions.set(interaction.id, interaction);\n\t\tif (this.callbackInteractions.size > CALLBACK_INTERACTION_CAP) {\n\t\t\tconst oldest = this.callbackInteractions.keys().next().value;\n\t\t\tif (oldest !== undefined) this.callbackInteractions.delete(oldest);\n\t\t}\n\t\tthis.callbackHandler(event);\n\t}\n\n\t/** Best-effort: fill replyToText from the referenced message when it wasn't cached. */\n\tprivate async resolveReplyToText(message: DiscordMessageLike, event: MessageEvent): Promise<void> {\n\t\tif (event.replyToText !== undefined) return;\n\t\tconst refId = message.reference?.messageId;\n\t\tif (refId == null) return;\n\t\tconst messages = message.channel.messages;\n\t\tif (!messages) return;\n\t\ttry {\n\t\t\tconst referenced = await messages.fetch(refId);\n\t\t\tif (referenced?.content) event.replyToText = referenced.content.slice(0, REPLY_TO_TEXT_MAX);\n\t\t} catch {\n\t\t\t// unknown/deleted reference — no reply context\n\t\t}\n\t}\n\n\t/** autoThread: spin a thread off a bot-mentioning guild-text message and re-target the event. */\n\tprivate async maybeAutoThread(message: DiscordMessageLike, event: MessageEvent): Promise<void> {\n\t\tif (!this.cfg.autoThread) return;\n\t\tif (event.metadata?.mentionedBot !== true) return;\n\t\t// Only guild TEXT channels: DMs can't thread and messages already inside\n\t\t// a thread don't need another one.\n\t\tif (message.channel.type !== ChannelType.GuildText) return;\n\t\tif (typeof message.startThread !== \"function\") return;\n\t\ttry {\n\t\t\tconst thread = await message.startThread({\n\t\t\t\tname: (event.text || \"conversation\").slice(0, THREAD_NAME_MAX),\n\t\t\t\tautoArchiveDuration: 60,\n\t\t\t});\n\t\t\tthis.participatingThreads.add(thread.id);\n\t\t\tif (this.participatingThreads.size > PARTICIPATING_THREAD_CAP) {\n\t\t\t\tconst oldest = this.participatingThreads.values().next().value;\n\t\t\t\tif (oldest !== undefined) this.participatingThreads.delete(oldest);\n\t\t\t}\n\t\t\tevent.source = { ...event.source, chatType: \"thread\", chatId: message.channel.id, threadId: thread.id };\n\t\t} catch {\n\t\t\t// e.g. missing CREATE_THREADS permission — respond in-channel instead.\n\t\t}\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 dispatchEvent(event: MessageEvent): void {\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\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 async fetchChannel(id: string): Promise<DiscordChannelLike | null> {\n\t\tif (this.channelCache.has(id)) return this.channelCache.get(id) ?? null;\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\tconst channel = await this.client.channels.fetch(id);\n\t\t\tthis.channelCache.set(id, channel);\n\t\t\tif (this.channelCache.size > CHANNEL_CACHE_CAP) {\n\t\t\t\tconst oldest = this.channelCache.keys().next().value;\n\t\t\t\tif (oldest !== undefined) this.channelCache.delete(oldest);\n\t\t\t}\n\t\t\treturn channel;\n\t\t} catch {\n\t\t\tthis.channelCache.set(id, null);\n\t\t\tif (this.channelCache.size > CHANNEL_CACHE_CAP) {\n\t\t\t\tconst oldest = this.channelCache.keys().next().value;\n\t\t\t\tif (oldest !== undefined) this.channelCache.delete(oldest);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate recordSent(messageId: string, channel: DiscordChannelLike): void {\n\t\tif (THREAD_TYPES.includes(channel.type)) this.participatingThreads.add(channel.id);\n\t\tif (this.participatingThreads.size > PARTICIPATING_THREAD_CAP) {\n\t\t\tconst oldest = this.participatingThreads.values().next().value;\n\t\t\tif (oldest !== undefined) this.participatingThreads.delete(oldest);\n\t\t}\n\t\tthis.sentMessageChannels.set(messageId, channel.id);\n\t\tif (this.sentMessageChannels.size > SENT_CHANNEL_CAP) {\n\t\t\tconst oldest = this.sentMessageChannels.keys().next().value;\n\t\t\tif (oldest !== undefined) this.sentMessageChannels.delete(oldest);\n\t\t}\n\t}\n\n\tprivate async trySend(\n\t\tchannel: DiscordChannelLike,\n\t\tpayload: { content: string; reply?: { messageReference: string }; components?: unknown[] },\n\t): Promise<SendResult> {\n\t\tif (typeof channel.send !== \"function\") {\n\t\t\treturn { success: false, error: `channel ${channel.id} is not sendable` };\n\t\t}\n\t\ttry {\n\t\t\tconst sent = await channel.send(payload);\n\t\t\tthis.recordSent(sent.id, channel);\n\t\t\treturn { success: true, messageId: sent.id };\n\t\t} catch (err) {\n\t\t\t// 429s are queued internally by discord.js and don't surface here.\n\t\t\treturn { success: false, error: errMessage(err), retryable: discordErrorCode(err) !== ERR_INVALID_FORM_BODY };\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 channel = await this.fetchChannel(opts?.threadId ?? chatId);\n\t\tif (!channel) return { success: false, error: `channel ${opts?.threadId ?? chatId} not found` };\n\t\tconst result = await this.trySend(channel, {\n\t\t\tcontent: text,\n\t\t\treply: opts?.replyTo !== undefined ? { messageReference: opts.replyTo } : undefined,\n\t\t});\n\t\tif (!result.success && opts?.replyTo !== undefined) {\n\t\t\t// The reply target may be deleted/unknown — fall back to a plain send.\n\t\t\treturn this.trySend(channel, { content: text });\n\t\t}\n\t\treturn result;\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 channel = await this.fetchChannel(opts?.threadId ?? chatId);\n\t\tif (!channel) return { success: false, error: `channel ${opts?.threadId ?? chatId} not found` };\n\t\tconst result = await this.trySend(channel, {\n\t\t\tcontent: text,\n\t\t\treply: opts?.replyTo !== undefined ? { messageReference: opts.replyTo } : undefined,\n\t\t\tcomponents: buildDiscordComponents(buttons),\n\t\t});\n\t\tif (!result.success && opts?.replyTo !== undefined) {\n\t\t\treturn this.trySend(channel, { content: text, components: buildDiscordComponents(buttons) });\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync answerCallback(id: string, text?: string): Promise<void> {\n\t\tconst interaction = this.callbackInteractions.get(id);\n\t\tif (!interaction) return;\n\t\tthis.callbackInteractions.delete(id);\n\t\ttry {\n\t\t\tif (text !== undefined && interaction.reply) {\n\t\t\t\tawait interaction.reply({ content: text, ephemeral: true });\n\t\t\t} else {\n\t\t\t\tawait interaction.deferUpdate();\n\t\t\t}\n\t\t} catch {\n\t\t\t// best-effort acknowledgment\n\t\t}\n\t}\n\n\tasync editMessage(chatId: string, messageId: string, text: string, buttons?: ButtonSpec[][]): Promise<SendResult> {\n\t\t// The router passes only chatId; thread messages live in their own\n\t\t// channel, so prefer the channel recorded at send() time.\n\t\tconst channel = await this.fetchChannel(this.sentMessageChannels.get(messageId) ?? chatId);\n\t\tif (!channel?.messages) {\n\t\t\treturn { success: false, error: `channel ${this.sentMessageChannels.get(messageId) ?? chatId} not found` };\n\t\t}\n\t\ttry {\n\t\t\tconst message = await channel.messages.fetch(messageId);\n\t\t\tif (!message || typeof message.edit !== \"function\") {\n\t\t\t\treturn { success: false, error: `message ${messageId} not found` };\n\t\t\t}\n\t\t\tconst editOptions: { content: string; components?: unknown[] } = { content: text };\n\t\t\tif (buttons !== undefined) editOptions.components = buildDiscordComponents(buttons);\n\t\t\tawait message.edit(editOptions);\n\t\t\treturn { success: true, messageId };\n\t\t} catch (err) {\n\t\t\tconst code = discordErrorCode(err);\n\t\t\tif (code === ERR_UNKNOWN_MESSAGE) return { success: false, error: errMessage(err) }; // terminal\n\t\t\treturn { success: false, error: errMessage(err), retryable: true };\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 8s until send() completes for this target or the 2min safety net\n\t\t// fires — all errors swallowed.\n\t\tconst key = typingKey(chatId, threadId);\n\t\tconst targetId = threadId ?? chatId;\n\t\tawait this.doTyping(targetId);\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.doTyping(targetId);\n\t\t}, TYPING_INTERVAL_MS);\n\t\tthis.typing.set(key, { interval, lastRefresh: this.now() });\n\t}\n\n\tprivate async doTyping(channelId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst channel = await this.fetchChannel(channelId);\n\t\t\tawait channel?.sendTyping?.();\n\t\t} catch {\n\t\t\t// typing is cosmetic — never surface\n\t\t}\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"]}
|
|
1
|
+
{"version":3,"file":"discord.js","sourceRoot":"","sources":["../../../src/gateway/adapters/discord.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway Discord adapter (Phase 3).\n *\n * Built on discord.js v14 (gateway websocket; the library owns reconnects and\n * REST rate-limit queuing internally, so neither is handled here). No voice,\n * no native slash-command registration — commands arrive as plain message\n * text, same as Telegram.\n *\n * Intents: Guilds, GuildMessages, DirectMessages, MessageContent. GuildMembers\n * is deliberately NOT requested: it is a privileged intent, and asking for it\n * without the dev-portal opt-in makes Discord refuse the identify — the bot\n * connects and immediately goes offline. v1 has no role-based config\n * (SessionSource.roleAuthorized stays undefined), so there is nothing to gain.\n *\n * Inbound: messageToEvent() is the pure message → MessageEvent mapping\n * (dm/group/thread chat types, <@id>/<@!id> mention detection with the mention\n * stripped, thread-participation counts as a mention, own + all-bot + system +\n * attachment-only messages dropped, other-mention-without-us dropped outside\n * DMs — hermes DISCORD_IGNORE_NO_MENTION). A module-level seen-id Set\n * (cap 500, FIFO evict) drops gateway-resume replays. cfg.ignoredChannels is\n * enforced adapter-side (channel id or parent id; blacklist wins over\n * mentions). Consecutive texts from the same chat/thread/user are debounced\n * 600ms and merged (\"\\n\"-joined, latest messageId kept); texts starting with\n * \"/\" flush immediately so commands never merge.\n * Button interactions are routed via interactionCreate → buttonInteractionToEvent().\n *\n * chatId/threadId mapping (pairs with session-keys.ts buildSessionKey):\n * dm → chatId = channel.id → agent:main:discord:dm:<channelId>\n * thread → chatId = PARENT channel id, threadId = → agent:main:discord:thread:<parentId>:<threadId>\n * thread channel id (shared session, no userId)\n * guild text/announcement → chatId = channel.id → agent:main:discord:group:<channelId>[:<userId>]\n * (chatType \"group\"; userId appended when groupSessionsPerUser is on)\n *\n * autoThread (cfg.discord.autoThread, default true): a bot-mentioning message\n * in a guild TEXT channel gets a thread spun off it (startThread, 60min\n * archive); the event is re-targeted to the thread (chatType \"thread\", chatId\n * parent id, threadId thread id). On failure the event stays in-channel.\n *\n * Thread participation: an in-memory Set of thread ids the bot has sent to\n * (recorded on every successful send() into a thread and on autoThread\n * creation); messages in those threads count as mentionedBot so follow-ups\n * don't need a re-mention. NOT persisted in v1 (hermes persists it).\n *\n * Outbound: channel.send({ content, reply: { messageReference } }) with a\n * one-shot plain-send fallback when the reply reference fails. DiscordAPIError\n * 50035 (invalid form body, e.g. overlong content) is reported non-retryable;\n * 429s are queued internally by discord.js; everything else is retryable.\n * editMessage resolves the channel via a messageId → channel map recorded at\n * send() time (router passes only chatId, but thread messages live in their\n * own channel); Unknown Message (10008) is a terminal { success: false }.\n * Message splitting is NOT done here — the router owns that (text.ts).\n *\n * Buttons: ActionRowBuilder rows of ButtonBuilder components (max 5×5).\n * answerCallback acknowledges the interaction; a text argument produces an\n * ephemeral reply (used for unauthorized/expired taps), otherwise deferUpdate().\n *\n * Typing: sendTyping() is a self-contained refresher — Discord typing bubbles\n * last ~10s, so the action is re-sent on an unref'd 8s interval per target.\n * The interval stops when send() completes for that target or after 2 minutes\n * without a refresh (safety net). All errors swallowed.\n *\n * Testability: the discord.js Client goes through an injectable clientFactory\n * (tests inject a minimal fake), timers through an injectable Scheduler, and\n * messageToEvent() operates on the structural DiscordMessageLike — plain mock\n * objects, no real Client construction.\n */\n\nimport {\n\tActionRowBuilder,\n\tApplicationCommandOptionType,\n\tButtonBuilder,\n\tButtonStyle,\n\tChannelType,\n\tClient,\n\tEvents,\n\tGatewayIntentBits,\n\ttype Interaction,\n} from \"discord.js\";\nimport { isAuthorized, isGatewayOwner } from \"../authz.ts\";\nimport { type DiscordConfig, loadGatewayConfig } 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\";\nimport type { Scheduler } from \"./telegram.ts\";\n\nconst DEFAULT_DEBOUNCE_MS = 600;\nconst TYPING_INTERVAL_MS = 8000; // Discord typing lasts ~10s\nconst TYPING_SAFETY_MS = 120_000; // auto-stop after 2min without a refresh\nconst REPLY_TO_TEXT_MAX = 500;\nconst THREAD_NAME_MAX = 40;\nconst SEEN_CAP = 500;\nconst SENT_CHANNEL_CAP = 500;\nconst CALLBACK_INTERACTION_CAP = 500;\nconst CHANNEL_CACHE_CAP = 500;\nconst PARTICIPATING_THREAD_CAP = 500;\nconst ERR_INVALID_FORM_BODY = 50035; // e.g. content over 2000 chars — non-retryable\nconst ERR_UNKNOWN_MESSAGE = 10008; // deleted/unknown message — terminal\n\nconst THREAD_TYPES: readonly number[] = [\n\tChannelType.PublicThread,\n\tChannelType.PrivateThread,\n\tChannelType.AnnouncementThread,\n];\n\n// ---------------------------------------------------------------------------\n// Structural discord.js shapes (only the fields the adapter reads)\n// ---------------------------------------------------------------------------\n\nexport interface DiscordSentMessageLike {\n\tid: string;\n}\n\nexport interface DiscordFetchedMessageLike {\n\tcontent?: string;\n\tedit?(options: string | { content: string; components?: unknown[] }): Promise<unknown>;\n}\n\nexport interface DiscordChannelLike {\n\tid: string;\n\ttype: number;\n\tparentId?: string | null;\n\tsend?(payload: {\n\t\tcontent: string;\n\t\tfiles?: string[];\n\t\treply?: { messageReference: string };\n\t\tcomponents?: unknown[];\n\t}): Promise<DiscordSentMessageLike>;\n\tsendTyping?(): Promise<unknown>;\n\tmessages?: {\n\t\tcache?: { get(id: string): { content?: string } | undefined };\n\t\tfetch(id: string): Promise<DiscordFetchedMessageLike | null>;\n\t};\n}\n\nexport interface DiscordButtonInteractionLike {\n\tid: string;\n\tcustomId: string;\n\tuser: { id: string; username?: string };\n\tmessage: { id: string; channel: DiscordChannelLike };\n\tisButton(): boolean;\n\tdeferUpdate(): Promise<unknown>;\n\treply?(options: { content: string; ephemeral: boolean }): Promise<unknown>;\n\tupdate?(options: { content: string; components?: unknown[] }): Promise<unknown>;\n}\n\nexport interface DiscordAttachmentLike {\n\tid: string;\n\t/** Direct CDN URL for the attachment content. */\n\turl: string;\n\tcontentType?: string;\n\tname?: string;\n\tsize?: number;\n}\n\nexport interface DiscordMessageLike {\n\tid: string;\n\tcontent?: string;\n\tsystem?: boolean;\n\tauthor: { id: string; bot?: boolean; username?: string };\n\tchannel: DiscordChannelLike;\n\treference?: { messageId?: string | null } | null;\n\tmentions?: {\n\t\thas(user: { id: string }): boolean;\n\t\trepliedUser?: { id: string } | null;\n\t};\n\tstartThread?(opts: { name: string; autoArchiveDuration: number }): Promise<DiscordChannelLike>;\n\tattachments?: DiscordAttachmentLike[];\n}\n\n/** Minimal Client surface; the real discord.js Client satisfies this. */\nexport interface DiscordClientLike {\n\tapplication?: Pick<NonNullable<Client[\"application\"]>, \"commands\"> | null;\n\tuser: { id: string } | null;\n\tlogin(token: string): Promise<unknown>;\n\ton(event: string, listener: (arg: unknown) => void): unknown;\n\tonce(event: string, listener: () => void): unknown;\n\tdestroy(): void;\n\tchannels: { fetch(id: string): Promise<DiscordChannelLike | null> };\n}\n\nexport type DiscordClientFactory = () => DiscordClientLike;\n\n// ---------------------------------------------------------------------------\n// Pure mapping + small helpers\n// ---------------------------------------------------------------------------\n\nfunction errMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\n/** lunr: download an inbound Discord attachment from its CDN URL. */\n/** Discord REST error code (DiscordAPIError.code) when present. */\nfunction discordErrorCode(err: unknown): number | undefined {\n\tconst code = (err as { code?: unknown } | null)?.code;\n\treturn typeof code === \"number\" ? code : undefined;\n}\n\nexport interface MessageToEventOptions {\n\t/** Channel ids (or parent ids for threads) to drop outright — blacklist wins over mentions. */\n\tignoredChannels?: readonly string[];\n\t/** Threads the bot has spoken in; messages there count as mentionedBot. */\n\tparticipatingThreads?: ReadonlySet<string>;\n}\n\nfunction isImageContentType(contentType: string | undefined): boolean {\n\treturn typeof contentType === \"string\" && contentType.split(\";\")[0]?.trim().toLowerCase().startsWith(\"image/\");\n}\n\n/** lunr: collect inbound image attachments (contentType image/*) from a Discord message. */\nexport function collectDiscordImageAttachments(message: DiscordMessageLike): DiscordAttachmentLike[] {\n\treturn Array.from(message.attachments?.values() ?? []).filter((attachment) =>\n\t\tisImageContentType(attachment.contentType),\n\t);\n}\n\n/**\n * Map a Discord message to a MessageEvent; null = drop (own message, any bot,\n * system message, ignored channel, unsupported channel type, or a non-DM\n * message mentioning others but not us). Messages with no text but image\n * attachments are kept (v1: images inbound).\n */\nexport function messageToEvent(\n\tmessage: DiscordMessageLike,\n\tbotUser: { id: string },\n\toptions: MessageToEventOptions = {},\n): MessageEvent | null {\n\tconst author = message.author;\n\tif (!author) return null;\n\tif (author.id === botUser.id) return null; // own messages\n\tif (author.bot) return null; // all bots (same simplification as telegram)\n\tif (message.system) return null;\n\tconst content = (message.content ?? \"\").trim();\n\tconst imageAttachments = collectDiscordImageAttachments(message);\n\tif (content === \"\" && imageAttachments.length === 0 && !Array.from(message.attachments?.values() ?? []).length)\n\t\treturn null;\n\n\tconst channel = message.channel;\n\tconst ignored = options.ignoredChannels ?? [];\n\tif (ignored.includes(channel.id) || (channel.parentId != null && ignored.includes(channel.parentId))) {\n\t\treturn null; // blacklist wins over mentions\n\t}\n\n\t// chatId/threadId mapping — see the file header for the session-key pairing.\n\tlet chatType: ChatType;\n\tlet chatId: string;\n\tlet threadId: string | undefined;\n\tif (channel.type === ChannelType.DM) {\n\t\tchatType = \"dm\";\n\t\tchatId = channel.id;\n\t} else if (THREAD_TYPES.includes(channel.type)) {\n\t\tchatType = \"thread\";\n\t\tchatId = channel.parentId ?? channel.id;\n\t\tthreadId = channel.id;\n\t} else if (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement) {\n\t\tchatType = \"group\";\n\t\tchatId = channel.id;\n\t} else {\n\t\treturn null; // voice/category/forum containers etc. — unsupported in v1\n\t}\n\n\tconst explicitMention = message.mentions?.has(botUser) === true;\n\tconst participating = threadId !== undefined && options.participatingThreads?.has(threadId) === true;\n\tconst mentionedBot = explicitMention || participating;\n\n\t// Multi-agent filter (hermes DISCORD_IGNORE_NO_MENTION): outside DMs, a\n\t// message that mentions other users/bots but not us is not for us.\n\tif (!mentionedBot && chatType !== \"dm\") {\n\t\tconst mentionedIds = [...content.matchAll(/<@!?(\\d+)>/g)].map((m) => m[1]);\n\t\tif (mentionedIds.some((id) => id !== botUser.id)) return null;\n\t}\n\n\tlet text = content;\n\tif (explicitMention) {\n\t\ttext = text.replace(new RegExp(`<@!?${botUser.id}>`, \"g\"), \"\").trim();\n\t}\n\n\tconst refId = message.reference?.messageId;\n\tconst cachedReply = refId != null ? channel.messages?.cache?.get(refId)?.content : undefined;\n\n\treturn {\n\t\ttext,\n\t\tmessageId: message.id,\n\t\treplyToText: cachedReply !== undefined ? cachedReply.slice(0, REPLY_TO_TEXT_MAX) : undefined,\n\t\tsource: {\n\t\t\tplatform: \"discord\",\n\t\t\tchatId,\n\t\t\tchatType,\n\t\t\tuserId: author.id,\n\t\t\tuserName: author.username,\n\t\t\tthreadId,\n\t\t},\n\t\tmetadata: mentionedBot ? { mentionedBot: true } : undefined,\n\t};\n}\n\nfunction mapDiscordChannel(\n\tchannel: DiscordChannelLike,\n): { chatType: ChatType; chatId: string; threadId?: string } | null {\n\tif (channel.type === ChannelType.DM) {\n\t\treturn { chatType: \"dm\", chatId: channel.id };\n\t}\n\tif (THREAD_TYPES.includes(channel.type)) {\n\t\treturn { chatType: \"thread\", chatId: channel.parentId ?? channel.id, threadId: channel.id };\n\t}\n\tif (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement) {\n\t\treturn { chatType: \"group\", chatId: channel.id };\n\t}\n\treturn null;\n}\n\nexport function buttonInteractionToEvent(interaction: DiscordButtonInteractionLike): CallbackEvent | null {\n\tconst channel = interaction.message.channel;\n\tconst mapped = mapDiscordChannel(channel);\n\tif (!mapped) return null;\n\treturn {\n\t\tid: interaction.id,\n\t\tchatId: mapped.chatId,\n\t\tmessageId: interaction.message.id,\n\t\tuserId: interaction.user.id,\n\t\tuserName: interaction.user.username,\n\t\tdata: interaction.customId,\n\t\tthreadId: mapped.threadId,\n\t};\n}\n\nfunction buildDiscordComponents(buttons: ButtonSpec[][]): unknown[] {\n\tconst rows: ActionRowBuilder<ButtonBuilder>[] = [];\n\tfor (const row of buttons) {\n\t\tconst actionRow = new ActionRowBuilder<ButtonBuilder>();\n\t\tfor (const button of row) {\n\t\t\tconst style = button.data === \"\" ? ButtonStyle.Secondary : ButtonStyle.Secondary;\n\t\t\tconst builder = new ButtonBuilder().setCustomId(button.data).setLabel(button.label).setStyle(style);\n\t\t\tif (button.data === \"\") builder.setDisabled(true);\n\t\t\tactionRow.addComponents(builder);\n\t\t}\n\t\trows.push(actionRow);\n\t}\n\treturn rows;\n}\n\n// ---------------------------------------------------------------------------\n// Gateway-resume replay dedup (module-level: one process = one bot connection)\n// ---------------------------------------------------------------------------\n\nconst seenMessageIds = new Set<string>();\n\n/** Returns true when this id was already seen (drop the replay). */\nfunction markMessageSeen(id: string): boolean {\n\tif (seenMessageIds.has(id)) return true;\n\tseenMessageIds.add(id);\n\tif (seenMessageIds.size > SEEN_CAP) {\n\t\tconst oldest = seenMessageIds.values().next().value; // Sets iterate in insertion order\n\t\tif (oldest !== undefined) seenMessageIds.delete(oldest);\n\t}\n\treturn false;\n}\n\n/** Test hook: reset RESUME dedup state between tests. */\nexport function resetSeenMessageIds(): void {\n\tseenMessageIds.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Adapter\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\nfunction defaultClientFactory(): DiscordClientLike {\n\t// No GatewayIntentBits.GuildMembers — see the file header for the\n\t// privileged-intent trap (bot stays offline without the portal opt-in).\n\treturn new Client({\n\t\tintents: [\n\t\t\tGatewayIntentBits.Guilds,\n\t\t\tGatewayIntentBits.GuildMessages,\n\t\t\tGatewayIntentBits.DirectMessages,\n\t\t\tGatewayIntentBits.MessageContent,\n\t\t],\n\t}) as unknown as DiscordClientLike;\n}\n\nexport interface DiscordAdapterOptions {\n\t/** Injected Client factory (tests). When omitted, a real discord.js Client is created. */\n\tclientFactory?: DiscordClientFactory;\n\t/** lunr: injected image downloader for inbound attachments (tests). Defaults to fetch(url). */\n\tdownloadAttachment?: (url: string) => Promise<Uint8Array>;\n\t/** lunr: skip inbound media larger than this many bytes. 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\nfunction typingKey(chatId: string, threadId?: string): string {\n\treturn `${chatId}:${threadId ?? \"\"}`;\n}\n\nexport class DiscordAdapter implements PlatformAdapter {\n\treadonly platform = \"discord\";\n\treadonly maxMessageLength = 2000;\n\n\tprivate readonly cfg: DiscordConfig;\n\tprivate readonly clientFactory: DiscordClientFactory;\n\tprivate readonly downloadAttachment: (url: 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 client?: DiscordClientLike;\n\tprivate botUserId?: string;\n\tprivate handler?: (event: MessageEvent) => void;\n\tprivate commandSuggestions?: Parameters<NonNullable<PlatformAdapter[\"setCommandSuggestions\"]>>[0];\n\tprivate callbackHandler?: (event: CallbackEvent) => void;\n\tprivate readonly pending = new Map<string, PendingEntry>();\n\tprivate readonly typing = new Map<string, TypingEntry>();\n\tprivate readonly channelCache = new Map<string, DiscordChannelLike>();\n\t/** Threads the bot has spoken in (session memory only; hermes persists this). */\n\tprivate readonly participatingThreads = new Set<string>();\n\t/** messageId → channel it was sent to, so editMessage finds thread messages. */\n\tprivate readonly sentMessageChannels = new Map<string, string>();\n\t/** callbackId → interaction, so answerCallback can acknowledge the click. */\n\tprivate readonly callbackInteractions = new Map<string, DiscordButtonInteractionLike>();\n\n\tconstructor(cfg: DiscordConfig, options: DiscordAdapterOptions = {}) {\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.clientFactory) {\n\t\t\tthis.clientFactory = options.clientFactory;\n\t\t} else {\n\t\t\tif (!cfg.token) {\n\t\t\t\tthrow new Error(\"DiscordAdapter: no bot token (set discord.token or LUNR_DISCORD_BOT_TOKEN)\");\n\t\t\t}\n\t\t\tthis.clientFactory = defaultClientFactory;\n\t\t}\n\t\tthis.downloadAttachment = options.downloadAttachment ?? downloadAttachment;\n\t\tthis.maxMediaBytes = options.maxMediaBytes ?? 10 * 1024 * 1024;\n\t}\n\n\tsetCommandSuggestions(\n\t\thandler: (source: MessageEvent[\"source\"], command: string) => Promise<readonly string[]>,\n\t): void {\n\t\tthis.commandSuggestions = handler;\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\tif (!this.cfg.token) {\n\t\t\tthrow new Error(\"DiscordAdapter: no bot token (set discord.token or LUNR_DISCORD_BOT_TOKEN)\");\n\t\t}\n\t\tconst client = this.clientFactory();\n\t\tthis.client = client;\n\t\tclient.on(Events.MessageCreate, (message) => {\n\t\t\tvoid this.handleMessage(message as DiscordMessageLike).catch((err) => {\n\t\t\t\tconsole.error(`[gateway] discord message handling error: ${errMessage(err)}`);\n\t\t\t});\n\t\t});\n\t\tclient.on(Events.InteractionCreate, (interaction) => {\n\t\t\tconst event = interaction as Interaction;\n\t\t\tconst handling = event.isButton()\n\t\t\t\t? this.handleInteraction(interaction as DiscordButtonInteractionLike)\n\t\t\t\t: this.handleNativeCommand(event);\n\t\t\tvoid handling.catch((err) => {\n\t\t\t\tconsole.error(`[gateway] discord interaction handling error: ${errMessage(err)}`);\n\t\t\t});\n\t\t});\n\t\tconst ready = new Promise<void>((resolve) => client.once(Events.ClientReady, () => resolve()));\n\t\ttry {\n\t\t\tawait client.login(this.cfg.token);\n\t\t} catch (err) {\n\t\t\tconsole.error(`[gateway] discord login failed: ${errMessage(err)}`);\n\t\t\treturn false;\n\t\t}\n\t\tawait ready;\n\t\tthis.botUserId = client.user?.id;\n\t\tif (this.botUserId === undefined) {\n\t\t\tconsole.error(\"[gateway] discord: client ready without a user — cannot map messages\");\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tasync disconnect(): Promise<void> {\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\tthis.channelCache.clear();\n\t\tthis.callbackInteractions.clear();\n\t\tthis.client?.destroy();\n\t\tthis.client = undefined;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Inbound dispatch + debounce\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleMessage(message: DiscordMessageLike): Promise<void> {\n\t\tif (!this.botUserId || !this.handler) return;\n\t\tif (markMessageSeen(message.id)) return; // gateway-resume replay\n\t\tconst event = messageToEvent(\n\t\t\tmessage,\n\t\t\t{ id: this.botUserId },\n\t\t\t{\n\t\t\t\tignoredChannels: this.cfg.ignoredChannels,\n\t\t\t\tparticipatingThreads: this.participatingThreads,\n\t\t\t},\n\t\t);\n\t\tif (!event) return;\n\t\tif (!isAuthorized(event.source, { ...loadGatewayConfig(), discord: this.cfg }, createPairingStore())) {\n\t\t\tthis.dispatchEvent(event);\n\t\t\treturn;\n\t\t}\n\t\tawait this.resolveReplyToText(message, event);\n\t\tawait this.maybeAutoThread(message, event);\n\t\tawait this.downloadAttachments(message, event);\n\t\tthis.dispatchEvent(event);\n\t}\n\n\t/** Download and encode attachments after authorization. */\n\tprivate async downloadAttachments(message: DiscordMessageLike, event: MessageEvent): Promise<void> {\n\t\tconst imageAttachments = Array.from(message.attachments?.values() ?? []);\n\t\tif (imageAttachments.length === 0) return;\n\t\tconst attachments: InboundAttachment[] = [];\n\t\tfor (const attachment of imageAttachments) {\n\t\t\tif (attachment.size !== undefined && attachment.size > this.maxMediaBytes) continue;\n\t\t\ttry {\n\t\t\t\tconst bytes = await this.downloadAttachment(attachment.url);\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: attachment.contentType ?? \"application/octet-stream\",\n\t\t\t\t\t...(attachment.name ? { filename: attachment.name } : {}),\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\tif (attachments.length > 0) event.attachments = attachments;\n\t}\n\n\tasync registerCommands(commands: { name: string; description: string }[]): Promise<void> {\n\t\tif (!this.client?.application) return;\n\t\tawait this.client.application.commands.set(\n\t\t\tcommands.slice(0, 95).map((command) => ({\n\t\t\t\tname: command.name,\n\t\t\t\tdescription: command.description.slice(0, 100),\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"args\",\n\t\t\t\t\t\tdescription: \"Command arguments or search\",\n\t\t\t\t\t\ttype: ApplicationCommandOptionType.String,\n\t\t\t\t\t\trequired: false,\n\t\t\t\t\t\tautocomplete: [\"mode\", \"thinking\", \"project\", \"sessions\"].includes(command.name),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})),\n\t\t);\n\t}\n\n\tprivate async handleNativeCommand(interaction: Interaction): Promise<boolean> {\n\t\tif (!interaction.isChatInputCommand?.() && !interaction.isAutocomplete?.()) return false;\n\t\tif (!interaction.isChatInputCommand() && !interaction.isAutocomplete()) return false;\n\t\tconst thread = interaction.channel?.isThread() ? interaction.channel : undefined;\n\t\tconst source = {\n\t\t\tplatform: \"discord\",\n\t\t\tchatId: thread?.parentId ?? interaction.channelId ?? interaction.user.id,\n\t\t\tthreadId: thread?.id,\n\t\t\tchatType: thread ? (\"thread\" as const) : interaction.inGuild() ? (\"channel\" as const) : (\"dm\" as const),\n\t\t\tuserId: interaction.user.id,\n\t\t\tuserName: interaction.user.username,\n\t\t};\n\t\tconst cfg = loadGatewayConfig();\n\t\tconst authorized = isAuthorized(source, cfg, createPairingStore());\n\t\tif (interaction.isAutocomplete()) {\n\t\t\tif (!authorized || !isGatewayOwner(source, cfg)) {\n\t\t\t\tawait interaction.respond([]);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst query = String(interaction.options.getFocused()).toLowerCase();\n\t\t\tlet values: string[] = [];\n\t\t\tif (interaction.commandName === \"mode\") values = [\"yolo\", \"auto\", \"read-only\"];\n\t\t\tif (this.commandSuggestions && [\"thinking\", \"model\"].includes(interaction.commandName)) {\n\t\t\t\tvalues = [...(await this.commandSuggestions(source, interaction.commandName))];\n\t\t\t}\n\t\t\tif (interaction.commandName === \"project\") values = cfg.projectRoots ?? [];\n\t\t\tif (interaction.commandName === \"sessions\") {\n\t\t\t\tconst { SessionManager } = await import(\"../../core/session-manager.ts\");\n\t\t\t\tvalues = (\n\t\t\t\t\tawait Promise.race([\n\t\t\t\t\t\tSessionManager.listAll(),\n\t\t\t\t\t\tnew Promise<[]>((resolve) => setTimeout(() => resolve([]), 1500)),\n\t\t\t\t\t])\n\t\t\t\t).map((s) => s.name || s.id);\n\t\t\t}\n\t\t\tawait interaction.respond(\n\t\t\t\tvalues\n\t\t\t\t\t.filter((s) => s.toLowerCase().includes(query))\n\t\t\t\t\t.slice(0, 25)\n\t\t\t\t\t.map((value) => ({ name: value.slice(0, 100), value: value.slice(0, 100) })),\n\t\t\t);\n\t\t\treturn true;\n\t\t}\n\t\tawait interaction.reply({\n\t\t\tcontent: authorized\n\t\t\t\t? \"Command received.\"\n\t\t\t\t: \"Pair your account before using this command. Send the bot a private message for a pairing code.\",\n\t\t\tephemeral: true,\n\t\t});\n\t\tif (authorized)\n\t\t\tthis.handler?.({\n\t\t\t\ttext: `/${interaction.commandName}${interaction.options.getString(\"args\") ? ` ${interaction.options.getString(\"args\")}` : \"\"}`,\n\t\t\t\tsource,\n\t\t\t\tmessageId: \"\",\n\t\t\t\tmetadata: { mentionedBot: true, nativeCommand: true },\n\t\t\t});\n\t\treturn true;\n\t}\n\n\tasync sendFile(chatId: string, file: string, opts?: SendOptions): Promise<SendResult> {\n\t\tconst channel = await this.fetchChannel(opts?.threadId ?? chatId);\n\t\tif (!channel?.send) return { success: false, error: \"Channel is unavailable.\" };\n\t\ttry {\n\t\t\tconst message = await channel.send({ content: \"\", files: [file] });\n\t\t\treturn { success: true, messageId: message.id };\n\t\t} catch (error) {\n\t\t\treturn { success: false, error: errMessage(error) };\n\t\t}\n\t}\n\n\tprivate async handleInteraction(interaction: DiscordButtonInteractionLike): Promise<void> {\n\t\tif (!this.callbackHandler) return;\n\t\tif (!interaction.isButton()) return;\n\t\tconst event = buttonInteractionToEvent(interaction);\n\t\tif (!event) return;\n\t\tthis.callbackInteractions.set(interaction.id, interaction);\n\t\tif (this.callbackInteractions.size > CALLBACK_INTERACTION_CAP) {\n\t\t\tconst oldest = this.callbackInteractions.keys().next().value;\n\t\t\tif (oldest !== undefined) this.callbackInteractions.delete(oldest);\n\t\t}\n\t\tthis.callbackHandler(event);\n\t}\n\n\t/** Best-effort: fill replyToText from the referenced message when it wasn't cached. */\n\tprivate async resolveReplyToText(message: DiscordMessageLike, event: MessageEvent): Promise<void> {\n\t\tif (event.replyToText !== undefined) return;\n\t\tconst refId = message.reference?.messageId;\n\t\tif (refId == null) return;\n\t\tconst messages = message.channel.messages;\n\t\tif (!messages) return;\n\t\ttry {\n\t\t\tconst referenced = await messages.fetch(refId);\n\t\t\tif (referenced?.content) event.replyToText = referenced.content.slice(0, REPLY_TO_TEXT_MAX);\n\t\t} catch {\n\t\t\t// unknown/deleted reference — no reply context\n\t\t}\n\t}\n\n\t/** autoThread: spin a thread off a bot-mentioning guild-text message and re-target the event. */\n\tprivate async maybeAutoThread(message: DiscordMessageLike, event: MessageEvent): Promise<void> {\n\t\tif (!this.cfg.autoThread) return;\n\t\tif (event.metadata?.mentionedBot !== true) return;\n\t\t// Only guild TEXT channels: DMs can't thread and messages already inside\n\t\t// a thread don't need another one.\n\t\tif (message.channel.type !== ChannelType.GuildText) return;\n\t\tif (typeof message.startThread !== \"function\") return;\n\t\ttry {\n\t\t\tconst thread = await message.startThread({\n\t\t\t\tname: (event.text || \"conversation\").slice(0, THREAD_NAME_MAX),\n\t\t\t\tautoArchiveDuration: 60,\n\t\t\t});\n\t\t\tthis.participatingThreads.add(thread.id);\n\t\t\tif (this.participatingThreads.size > PARTICIPATING_THREAD_CAP) {\n\t\t\t\tconst oldest = this.participatingThreads.values().next().value;\n\t\t\t\tif (oldest !== undefined) this.participatingThreads.delete(oldest);\n\t\t\t}\n\t\t\tevent.source = { ...event.source, chatType: \"thread\", chatId: message.channel.id, threadId: thread.id };\n\t\t} catch {\n\t\t\t// e.g. missing CREATE_THREADS permission — respond in-channel instead.\n\t\t}\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 dispatchEvent(event: MessageEvent): void {\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\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 async fetchChannel(id: string): Promise<DiscordChannelLike | null> {\n\t\tif (this.channelCache.has(id)) return this.channelCache.get(id) ?? null;\n\t\tif (!this.client) return null;\n\t\ttry {\n\t\t\tconst channel = await this.client.channels.fetch(id);\n\t\t\tif (channel) this.channelCache.set(id, channel);\n\t\t\tif (this.channelCache.size > CHANNEL_CACHE_CAP) {\n\t\t\t\tconst oldest = this.channelCache.keys().next().value;\n\t\t\t\tif (oldest !== undefined) this.channelCache.delete(oldest);\n\t\t\t}\n\t\t\treturn channel;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate recordSent(messageId: string, channel: DiscordChannelLike): void {\n\t\tif (THREAD_TYPES.includes(channel.type)) this.participatingThreads.add(channel.id);\n\t\tif (this.participatingThreads.size > PARTICIPATING_THREAD_CAP) {\n\t\t\tconst oldest = this.participatingThreads.values().next().value;\n\t\t\tif (oldest !== undefined) this.participatingThreads.delete(oldest);\n\t\t}\n\t\tthis.sentMessageChannels.set(messageId, channel.id);\n\t\tif (this.sentMessageChannels.size > SENT_CHANNEL_CAP) {\n\t\t\tconst oldest = this.sentMessageChannels.keys().next().value;\n\t\t\tif (oldest !== undefined) this.sentMessageChannels.delete(oldest);\n\t\t}\n\t}\n\n\tprivate async trySend(\n\t\tchannel: DiscordChannelLike,\n\t\tpayload: { content: string; reply?: { messageReference: string }; components?: unknown[] },\n\t): Promise<SendResult> {\n\t\tif (typeof channel.send !== \"function\") {\n\t\t\treturn { success: false, error: `channel ${channel.id} is not sendable` };\n\t\t}\n\t\ttry {\n\t\t\tconst sent = await channel.send(payload);\n\t\t\tthis.recordSent(sent.id, channel);\n\t\t\treturn { success: true, messageId: sent.id };\n\t\t} catch (err) {\n\t\t\t// 429s are queued internally by discord.js and don't surface here.\n\t\t\treturn { success: false, error: errMessage(err), retryable: discordErrorCode(err) !== ERR_INVALID_FORM_BODY };\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 channel = await this.fetchChannel(opts?.threadId ?? chatId);\n\t\tif (!channel) return { success: false, error: `channel ${opts?.threadId ?? chatId} not found` };\n\t\tconst result = await this.trySend(channel, {\n\t\t\tcontent: text,\n\t\t\treply: opts?.replyTo !== undefined ? { messageReference: opts.replyTo } : undefined,\n\t\t});\n\t\tif (!result.success && opts?.replyTo !== undefined) {\n\t\t\t// The reply target may be deleted/unknown — fall back to a plain send.\n\t\t\treturn this.trySend(channel, { content: text });\n\t\t}\n\t\treturn result;\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 channel = await this.fetchChannel(opts?.threadId ?? chatId);\n\t\tif (!channel) return { success: false, error: `channel ${opts?.threadId ?? chatId} not found` };\n\t\tconst result = await this.trySend(channel, {\n\t\t\tcontent: text,\n\t\t\treply: opts?.replyTo !== undefined ? { messageReference: opts.replyTo } : undefined,\n\t\t\tcomponents: buildDiscordComponents(buttons),\n\t\t});\n\t\tif (!result.success && opts?.replyTo !== undefined) {\n\t\t\treturn this.trySend(channel, { content: text, components: buildDiscordComponents(buttons) });\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync answerCallback(id: string, text?: string): Promise<void> {\n\t\tconst interaction = this.callbackInteractions.get(id);\n\t\tif (!interaction) return;\n\t\tthis.callbackInteractions.delete(id);\n\t\ttry {\n\t\t\tif (text !== undefined && interaction.reply) {\n\t\t\t\tawait interaction.reply({ content: text, ephemeral: true });\n\t\t\t} else {\n\t\t\t\tawait interaction.deferUpdate();\n\t\t\t}\n\t\t} catch {\n\t\t\t// best-effort acknowledgment\n\t\t}\n\t}\n\n\tasync editMessage(chatId: string, messageId: string, text: string, buttons?: ButtonSpec[][]): Promise<SendResult> {\n\t\t// The router passes only chatId; thread messages live in their own\n\t\t// channel, so prefer the channel recorded at send() time.\n\t\tconst channel = await this.fetchChannel(this.sentMessageChannels.get(messageId) ?? chatId);\n\t\tif (!channel?.messages) {\n\t\t\treturn { success: false, error: `channel ${this.sentMessageChannels.get(messageId) ?? chatId} not found` };\n\t\t}\n\t\ttry {\n\t\t\tconst message = await channel.messages.fetch(messageId);\n\t\t\tif (!message || typeof message.edit !== \"function\") {\n\t\t\t\treturn { success: false, error: `message ${messageId} not found` };\n\t\t\t}\n\t\t\tconst editOptions: { content: string; components?: unknown[] } = { content: text };\n\t\t\tif (buttons !== undefined) editOptions.components = buildDiscordComponents(buttons);\n\t\t\tawait message.edit(editOptions);\n\t\t\treturn { success: true, messageId };\n\t\t} catch (err) {\n\t\t\tconst code = discordErrorCode(err);\n\t\t\tif (code === ERR_UNKNOWN_MESSAGE) return { success: false, error: errMessage(err) }; // terminal\n\t\t\treturn { success: false, error: errMessage(err), retryable: true };\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 8s until send() completes for this target or the 2min safety net\n\t\t// fires — all errors swallowed.\n\t\tconst key = typingKey(chatId, threadId);\n\t\tconst targetId = threadId ?? chatId;\n\t\tawait this.doTyping(targetId);\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.doTyping(targetId);\n\t\t}, TYPING_INTERVAL_MS);\n\t\tthis.typing.set(key, { interval, lastRefresh: this.now() });\n\t}\n\n\tprivate async doTyping(channelId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst channel = await this.fetchChannel(channelId);\n\t\t\tawait channel?.sendTyping?.();\n\t\t} catch {\n\t\t\t// typing is cosmetic — never surface\n\t\t}\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"]}
|
|
@@ -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.
|