@ashx-j/lunr 0.2.21 → 0.2.23
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 +4 -2
- package/README.md +23 -5
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +1 -0
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-browser.d.ts +9 -0
- package/dist/builtin-extensions/lunr-browser.d.ts.map +1 -0
- package/dist/builtin-extensions/lunr-browser.js +169 -0
- package/dist/builtin-extensions/lunr-browser.js.map +1 -0
- package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-todos.js +15 -0
- package/dist/builtin-extensions/lunr-todos.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.js +3 -0
- package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.js +5 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +18 -5
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -3
- 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 +3 -0
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/communication.d.ts +17 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/communication.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/communication.js +45 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/communication.js.map +1 -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 +4 -11
- 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 +4 -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 +160 -51
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js +5 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js +3 -2
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/auto-drain.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/auto-drain.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/auto-drain.js +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/auto-drain.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts +3 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js +11 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +41 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.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 +4 -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/pi-args.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +3 -0
- 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-control.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-control.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-control.js +3 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-control.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 +1 -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/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/builtin-extensions/pi-subagents/src/tui/communication.d.ts +18 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.js +48 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.js.map +1 -0
- package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.js +11 -7
- package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/install-cli.d.ts.map +1 -1
- package/dist/cli/install-cli.js +15 -45
- package/dist/cli/install-cli.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +9 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +126 -29
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +22 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +250 -105
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/browser/network.d.ts +9 -0
- package/dist/core/browser/network.d.ts.map +1 -0
- package/dist/core/browser/network.js +146 -0
- package/dist/core/browser/network.js.map +1 -0
- package/dist/core/browser/runtime.d.ts +24 -0
- package/dist/core/browser/runtime.d.ts.map +1 -0
- package/dist/core/browser/runtime.js +289 -0
- package/dist/core/browser/runtime.js.map +1 -0
- package/dist/core/browser/schema.d.ts +16 -0
- package/dist/core/browser/schema.d.ts.map +1 -0
- package/dist/core/browser/schema.js +36 -0
- package/dist/core/browser/schema.js.map +1 -0
- package/dist/core/browser/settings.d.ts +8 -0
- package/dist/core/browser/settings.d.ts.map +1 -0
- package/dist/core/browser/settings.js +30 -0
- package/dist/core/browser/settings.js.map +1 -0
- package/dist/core/browser/setup.d.ts +2 -0
- package/dist/core/browser/setup.d.ts.map +1 -0
- package/dist/core/browser/setup.js +16 -0
- package/dist/core/browser/setup.js.map +1 -0
- package/dist/core/customize.d.ts.map +1 -1
- package/dist/core/customize.js +15 -14
- package/dist/core/customize.js.map +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +1 -1
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +1 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +1 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/install-features.d.ts.map +1 -1
- package/dist/core/install-features.js +10 -8
- package/dist/core/install-features.js.map +1 -1
- package/dist/core/memory-cap.d.ts.map +1 -1
- package/dist/core/memory-cap.js +5 -3
- package/dist/core/memory-cap.js.map +1 -1
- package/dist/core/model-tiers.d.ts.map +1 -1
- package/dist/core/model-tiers.js +9 -6
- package/dist/core/model-tiers.js.map +1 -1
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +9 -0
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +3 -0
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/process-registry.d.ts.map +1 -1
- package/dist/core/process-registry.js +6 -4
- package/dist/core/process-registry.js.map +1 -1
- package/dist/core/runtime-scope.d.ts +11 -0
- package/dist/core/runtime-scope.d.ts.map +1 -0
- package/dist/core/runtime-scope.js +3 -0
- package/dist/core/runtime-scope.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +13 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-handoff.d.ts +35 -0
- package/dist/core/session-handoff.d.ts.map +1 -0
- package/dist/core/session-handoff.js +214 -0
- package/dist/core/session-handoff.js.map +1 -0
- package/dist/core/session-manager.d.ts +20 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +184 -22
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-ownership.d.ts +28 -0
- package/dist/core/session-ownership.d.ts.map +1 -0
- package/dist/core/session-ownership.js +138 -0
- package/dist/core/session-ownership.js.map +1 -0
- package/dist/core/session-retention.d.ts.map +1 -1
- package/dist/core/session-retention.js +11 -2
- package/dist/core/session-retention.js.map +1 -1
- package/dist/core/settings-manager.d.ts +11 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +27 -0
- 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 +2 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-wait-interruption.d.ts +11 -0
- package/dist/core/subagent-wait-interruption.d.ts.map +1 -0
- package/dist/core/subagent-wait-interruption.js +19 -0
- package/dist/core/subagent-wait-interruption.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +7 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/todo-settings.d.ts +5 -0
- package/dist/core/todo-settings.d.ts.map +1 -0
- package/dist/core/todo-settings.js +7 -0
- package/dist/core/todo-settings.js.map +1 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +4 -4
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +5 -3
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +4 -4
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +4 -4
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +4 -4
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -4
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +14 -5
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +34 -7
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +4 -4
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/usage-service.d.ts.map +1 -1
- package/dist/core/usage-service.js +5 -3
- package/dist/core/usage-service.js.map +1 -1
- package/dist/gateway/adapters/discord.d.ts +12 -1
- package/dist/gateway/adapters/discord.d.ts.map +1 -1
- package/dist/gateway/adapters/discord.js +112 -17
- package/dist/gateway/adapters/discord.js.map +1 -1
- package/dist/gateway/adapters/telegram.d.ts +4 -2
- package/dist/gateway/adapters/telegram.d.ts.map +1 -1
- package/dist/gateway/adapters/telegram.js +34 -11
- package/dist/gateway/adapters/telegram.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts +14 -3
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +252 -60
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/approval.d.ts +1 -0
- package/dist/gateway/approval.d.ts.map +1 -1
- package/dist/gateway/approval.js +60 -18
- package/dist/gateway/approval.js.map +1 -1
- package/dist/gateway/authz.d.ts +2 -0
- package/dist/gateway/authz.d.ts.map +1 -1
- package/dist/gateway/authz.js +13 -0
- package/dist/gateway/authz.js.map +1 -1
- package/dist/gateway/buttons.d.ts +3 -1
- package/dist/gateway/buttons.d.ts.map +1 -1
- package/dist/gateway/buttons.js +18 -3
- package/dist/gateway/buttons.js.map +1 -1
- package/dist/gateway/command.d.ts.map +1 -1
- package/dist/gateway/command.js +49 -4
- package/dist/gateway/command.js.map +1 -1
- package/dist/gateway/commands.d.ts.map +1 -1
- package/dist/gateway/commands.js +18 -3
- package/dist/gateway/commands.js.map +1 -1
- package/dist/gateway/config.d.ts +5 -0
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +5 -0
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/conversations.d.ts +14 -0
- package/dist/gateway/conversations.d.ts.map +1 -0
- package/dist/gateway/conversations.js +30 -0
- package/dist/gateway/conversations.js.map +1 -0
- package/dist/gateway/download.d.ts +2 -0
- package/dist/gateway/download.d.ts.map +1 -0
- package/dist/gateway/download.js +36 -0
- package/dist/gateway/download.js.map +1 -0
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +85 -26
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/mobile-commands.d.ts +24 -0
- package/dist/gateway/mobile-commands.d.ts.map +1 -0
- package/dist/gateway/mobile-commands.js +422 -0
- package/dist/gateway/mobile-commands.js.map +1 -0
- package/dist/gateway/presenter.d.ts +16 -0
- package/dist/gateway/presenter.d.ts.map +1 -0
- package/dist/gateway/presenter.js +210 -0
- package/dist/gateway/presenter.js.map +1 -0
- package/dist/gateway/router.d.ts +1 -0
- package/dist/gateway/router.d.ts.map +1 -1
- package/dist/gateway/router.js +32 -3
- package/dist/gateway/router.js.map +1 -1
- package/dist/gateway/service.d.ts +46 -0
- package/dist/gateway/service.d.ts.map +1 -0
- package/dist/gateway/service.js +395 -0
- package/dist/gateway/service.js.map +1 -0
- package/dist/gateway/setup.d.ts +4 -0
- package/dist/gateway/setup.d.ts.map +1 -0
- package/dist/gateway/setup.js +146 -0
- package/dist/gateway/setup.js.map +1 -0
- package/dist/gateway/types.d.ts +4 -2
- package/dist/gateway/types.d.ts.map +1 -1
- package/dist/gateway/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +19 -2
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +78 -17
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/copyable-text.d.ts +27 -0
- package/dist/modes/interactive/components/copyable-text.d.ts.map +1 -0
- package/dist/modes/interactive/components/copyable-text.js +156 -0
- package/dist/modes/interactive/components/copyable-text.js.map +1 -0
- package/dist/modes/interactive/components/custom-entry.d.ts +1 -0
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-entry.js +8 -0
- package/dist/modes/interactive/components/custom-entry.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +1 -0
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +8 -0
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +16 -0
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +8 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +51 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.d.ts +9 -2
- package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.js +43 -1
- package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +4 -3
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +34 -45
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +9 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +267 -44
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +1 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +23 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/node-runtime/agent-session-services-GZVSOGYR.js +49 -0
- package/dist/node-runtime/{builtin-extensions-JCOPAAB5.js → builtin-extensions-QVCXNQT4.js} +6 -5
- package/dist/node-runtime/{chunk-LTYWTR3V.js → chunk-2MI5OPMN.js} +17 -2
- package/dist/node-runtime/{chunk-W3CVH5RX.js → chunk-2TKODGBZ.js} +11 -6
- package/dist/node-runtime/chunk-4EULYYR4.js +62 -0
- package/dist/node-runtime/{chunk-6N22HULO.js → chunk-5D7LC2ZQ.js} +7 -2
- package/dist/node-runtime/{chunk-KBKMCJDU.js → chunk-5ZQXRPMC.js} +19 -4
- package/dist/node-runtime/{chunk-BOHIDQT4.js → chunk-6VYZ5UFP.js} +1 -1
- package/dist/node-runtime/chunk-AZJHNQKQ.js +205 -0
- package/dist/node-runtime/{chunk-A3KMVBZV.js → chunk-BBUVIEZB.js} +1 -10
- package/dist/node-runtime/{chunk-IRPJXI6U.js → chunk-BX2RB477.js} +1 -1
- package/dist/node-runtime/chunk-CCCN2UCX.js +14 -0
- package/dist/node-runtime/{chunk-25I2BHAA.js → chunk-CSUZVNYE.js} +1 -1
- package/dist/node-runtime/chunk-DD5YWFSJ.js +708 -0
- package/dist/node-runtime/chunk-DQMGU675.js +3409 -0
- package/dist/node-runtime/{chunk-QZMODZQ2.js → chunk-E24EO34J.js} +2 -1
- package/dist/node-runtime/chunk-EKVFP6YF.js +355 -0
- package/dist/node-runtime/chunk-GJ2RDBRL.js +672 -0
- package/dist/node-runtime/{chunk-W3SILANP.js → chunk-GMHJCCEN.js} +8 -8
- package/dist/node-runtime/{chunk-DDWHZ633.js → chunk-GS42T6MX.js} +1 -1
- package/dist/node-runtime/chunk-GS66LV4M.js +2830 -0
- package/dist/node-runtime/chunk-H4K3JDRB.js +47 -0
- package/dist/node-runtime/{chunk-2SJB2AWP.js → chunk-HMBGROQK.js} +3 -3
- package/dist/node-runtime/{chunk-GB2MGFLI.js → chunk-HPUP7E4T.js} +216 -201
- package/dist/node-runtime/{chunk-YMEAYRBE.js → chunk-HTFRRDSF.js} +2 -7
- package/dist/node-runtime/{chunk-OPEK5RRY.js → chunk-IF24A3FH.js} +1 -1
- package/dist/node-runtime/chunk-IMLCWLPI.js +9 -0
- package/dist/node-runtime/chunk-KKUTQQV7.js +144 -0
- package/dist/node-runtime/chunk-LFZDVKSP.js +521 -0
- package/dist/node-runtime/{chunk-2BUAYZ7K.js → chunk-LLUIPGDR.js} +581 -197
- package/dist/node-runtime/{chunk-7NBZ5P7I.js → chunk-NAY6UUQZ.js} +1 -1
- package/dist/node-runtime/chunk-OAR4TYZE.js +146 -0
- package/dist/node-runtime/{chunk-STERBGUB.js → chunk-OFKSVRV7.js} +2 -2
- package/dist/node-runtime/{chunk-JZQ7F36T.js → chunk-PCEW76PM.js} +19 -3
- package/dist/node-runtime/{chunk-WLLDXJRT.js → chunk-QAZOULTU.js} +29 -1014
- package/dist/node-runtime/{chunk-6KDIOJL3.js → chunk-QH6YL7VK.js} +3 -3
- package/dist/node-runtime/chunk-R3K2F5GI.js +122 -0
- package/dist/node-runtime/{chunk-RNBHABBA.js → chunk-RDDAZOMU.js} +23 -0
- package/dist/node-runtime/{chunk-SY2TFMLO.js → chunk-RIR5EDDJ.js} +46 -151
- package/dist/node-runtime/chunk-RMQZU2YU.js +117 -0
- package/dist/node-runtime/{chunk-6BEIBLQ3.js → chunk-RWTKOC3B.js} +1 -1
- package/dist/node-runtime/{chunk-G6LRAP36.js → chunk-TQGPP4SC.js} +1 -1
- package/dist/node-runtime/{chunk-Y5KOEMBH.js → chunk-TVJUOJZJ.js} +9 -674
- package/dist/node-runtime/chunk-UAEOTO52.js +119 -0
- package/dist/node-runtime/{chunk-AY6CAJSL.js → chunk-UJOCPOMX.js} +6 -3
- package/dist/node-runtime/chunk-UP2XBVH5.js +40 -0
- package/dist/node-runtime/{chunk-WJVFOIB5.js → chunk-UQUCFAJ3.js} +1 -1
- package/dist/node-runtime/chunk-URD2SLIT.js +1019 -0
- package/dist/node-runtime/chunk-US544NN6.js +13 -0
- package/dist/node-runtime/chunk-UWPJL22I.js +341 -0
- package/dist/node-runtime/{chunk-4I23VTC4.js → chunk-VWCE2HSZ.js} +21 -124
- package/dist/node-runtime/{chunk-VZR3NCM7.js → chunk-W64JC4E7.js} +18 -14
- package/dist/node-runtime/{chunk-AWVD5IEY.js → chunk-X3URCYCU.js} +23 -12
- package/dist/node-runtime/{chunk-GKLEQ3I4.js → chunk-XLTL257W.js} +126 -27
- package/dist/node-runtime/{chunk-WKDK4C5P.js → chunk-YDO2F2IA.js} +38 -164
- package/dist/node-runtime/{chunk-PDNXVXMD.js → chunk-YHQ3NBB3.js} +58 -48
- package/dist/node-runtime/chunk-YPHAIRZT.js +10 -0
- package/dist/node-runtime/cli-runtime.js +2 -2
- package/dist/node-runtime/command-PDVTBRWA.js +45 -0
- package/dist/node-runtime/{commands-MV5KOZ64.js → commands-KAMO2T6P.js} +10 -10
- package/dist/node-runtime/commands-XDCFGIGW.js +36 -0
- package/dist/node-runtime/config-RORQUQFG.js +20 -0
- package/dist/node-runtime/{customize-LF5VST47.js → customize-VFJV3U6A.js} +2 -1
- package/dist/node-runtime/{direct-tool-executor-BK7CLEBX.js → direct-tool-executor-YAVOLDFY.js} +8 -8
- package/dist/node-runtime/{export-html-TEXVV5RP.js → export-html-KSOIMVPI.js} +4 -3
- package/dist/node-runtime/{extension-input-O4HSAEM4.js → extension-input-E7BJZ35H.js} +4 -4
- package/dist/node-runtime/{extension-selector-X5FCX43D.js → extension-selector-WU42XDTU.js} +4 -4
- package/dist/node-runtime/{extract-XLQSJN4V.js → extract-EQJE7FZZ.js} +2 -2
- package/dist/node-runtime/{gateway-BM6NLOH4.js → gateway-AYWM5DIO.js} +275 -994
- package/dist/node-runtime/{gemini-search-U7SUMPQA.js → gemini-search-35OCRFLN.js} +1 -1
- package/dist/node-runtime/{gemini-url-context-PXWCBQHK.js → gemini-url-context-UQY3PHHW.js} +2 -2
- package/dist/node-runtime/index.js +104 -65
- package/dist/node-runtime/{init-3C7Z24OI.js → init-X2LIUB2H.js} +3 -3
- package/dist/node-runtime/{install-cli-EXMJQESX.js → install-cli-3BO4MMQ6.js} +21 -82
- package/dist/node-runtime/interactive-mode-4MFRKAHG.js +72 -0
- package/dist/node-runtime/{interactive-view-ILS6TAN5.js → interactive-view-TUZHCQM3.js} +8 -8
- package/dist/node-runtime/lunr-browser-RYFIIVJN.js +174 -0
- package/dist/node-runtime/{lunr-cron-4VHP4E4B.js → lunr-cron-YWXEPZMQ.js} +19 -5
- package/dist/node-runtime/main.js +44 -31
- package/dist/node-runtime/{memory-cap-3S4XRLNP.js → memory-cap-EL3KL4JJ.js} +2 -1
- package/dist/node-runtime/mobile-commands-LVHDAUDP.js +32 -0
- package/dist/node-runtime/model-runtime-PLQKAG4B.js +16 -0
- package/dist/node-runtime/{model-tiers-NJLHUEGK.js → model-tiers-OU3U5CZG.js} +2 -1
- package/dist/node-runtime/{package-manager-cli-N37546V5.js → package-manager-cli-6GT3Z3CK.js} +22 -15
- package/dist/node-runtime/{pi-intercom-NV2LW6X7.js → pi-intercom-HBPK5HSJ.js} +4 -1
- package/dist/node-runtime/{pi-mcp-adapter-GUFZWW54.js → pi-mcp-adapter-E6WULBKF.js} +4 -4
- package/dist/node-runtime/{pi-subagents-6MUIFNDM.js → pi-subagents-CDWK2AKY.js} +236 -79
- package/dist/node-runtime/{pi-web-access-2CQGFLT2.js → pi-web-access-WTGMNZ32.js} +15 -15
- package/dist/node-runtime/process-registry-4FAKZQOL.js +31 -0
- package/dist/node-runtime/{proxy-modes-V6YVFH3C.js → proxy-modes-OSQCWRBI.js} +9 -9
- package/dist/node-runtime/rpc-entry.js +44 -31
- package/dist/node-runtime/{rpc-mode-W5ZTLTMZ.js → rpc-mode-O3XV4SID.js} +2 -2
- package/dist/node-runtime/runtime-4VA6JKNF.js +373 -0
- package/dist/node-runtime/{runtime-bridges-2VQTLPOZ.js → runtime-bridges-3XUQ46XW.js} +6 -5
- package/dist/node-runtime/service-ISSP6VHE.js +36 -0
- package/dist/node-runtime/session-handoff-HC7R5E24.js +33 -0
- package/dist/node-runtime/{session-manager-E7AREDTV.js → session-manager-CFN4UFM2.js} +2 -1
- package/dist/node-runtime/{session-selector-FJ7M2AIX.js → session-selector-GIM4DHQ2.js} +5 -4
- package/dist/node-runtime/{settings-manager-CUGAZWAB.js → settings-manager-6VEMXRDL.js} +7 -2
- package/dist/node-runtime/setup-5SR2J35A.js +155 -0
- package/dist/node-runtime/setup-JBKOLJ6F.js +18 -0
- package/dist/node-runtime/{subagent-executor-MH4A7CCF.js → subagent-executor-M5VWB3D3.js} +26 -18
- package/dist/node-runtime/theme-UOO5RVFN.js +68 -0
- package/dist/node-runtime/trust-manager-MMISNCYO.js +17 -0
- package/dist/node-runtime/{update-cli-XTZMZGOL.js → update-cli-UVKJWRRJ.js} +7 -2
- package/dist/node-runtime/usage-service-LFB2SWYV.js +37 -0
- package/dist/node-runtime/{video-extract-OYT5WKS5.js → video-extract-OOZHR7SU.js} +2 -2
- package/dist/node-runtime/{youtube-extract-TQYKGFGS.js → youtube-extract-FNPAUNHU.js} +6 -6
- package/docs/browser.md +86 -0
- package/docs/extensions.md +2 -0
- package/docs/features.md +96 -16
- package/docs/interactive-startup.md +2 -1
- package/docs/sessions.md +10 -0
- package/docs/settings.md +2 -0
- package/docs/tui.md +2 -0
- package/docs/usage.md +16 -0
- 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/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 +7 -4
- package/scripts/install-browser.mjs +20 -0
- package/dist/node-runtime/agent-session-services-C743TYQE.js +0 -37
- package/dist/node-runtime/chunk-VGYZ3APQ.js +0 -59
- package/dist/node-runtime/chunk-XI77KVKA.js +0 -6009
- package/dist/node-runtime/command-P4NYCJ2M.js +0 -13
- package/dist/node-runtime/interactive-mode-EVVBA64D.js +0 -59
- package/dist/node-runtime/{chunk-UWQYRSYQ.js → chunk-YJMMG7C6.js} +25 -25
- package/dist/node-runtime/{mcp-panel-IIGMXQGP.js → mcp-panel-KTTFXJZ4.js} +3 -3
- package/dist/node-runtime/{src-BPQFJR76.js → src-IVPNZ2BX.js} +9 -9
|
@@ -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 type { PlatformConfig } from \"../config.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\tconst res = await fetch(`${API_BASE}/file/bot${token}/${filePath}`);\n\tif (!res.ok) throw new TelegramApiError(res.status, `file download HTTP ${res.status}`);\n\treturn new Uint8Array(await res.arrayBuffer());\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\nfunction isImageMimeType(mimeType: string | undefined): boolean {\n\treturn typeof mimeType === \"string\" && mimeType.split(\";\")[0]?.trim().toLowerCase().startsWith(\"image/\");\n}\n\n/** lunr: collect inbound image media from a Telegram message (largest photo + image documents). */\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 && isImageMimeType(message.document.mime_type)) {\n\t\tdescriptors.push({\n\t\t\tfileId: message.document.file_id,\n\t\t\tmimeType: message.document.mime_type ?? \"image/jpeg\",\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\tconstructor(cfg: PlatformConfig, options: TelegramAdapterOptions = {}) {\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 (update.message) {\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 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 (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"]}
|
|
@@ -31,6 +31,12 @@
|
|
|
31
31
|
* Testability: all Bot API HTTP goes through an injectable callApi; timers go
|
|
32
32
|
* through an injectable Scheduler; backoffDelayMs() is a pure function.
|
|
33
33
|
*/
|
|
34
|
+
import { readFileSync } from "node:fs";
|
|
35
|
+
import { basename } from "node:path";
|
|
36
|
+
import { isAuthorized } from "../authz.js";
|
|
37
|
+
import { loadGatewayConfig } from "../config.js";
|
|
38
|
+
import { downloadAttachment } from "../download.js";
|
|
39
|
+
import { createPairingStore } from "../pairing.js";
|
|
34
40
|
const API_BASE = "https://api.telegram.org";
|
|
35
41
|
const DEFAULT_DEBOUNCE_MS = 300;
|
|
36
42
|
const TYPING_INTERVAL_MS = 4000; // bubbles expire after ~5s
|
|
@@ -86,10 +92,7 @@ async function defaultCallApi(token, method, body, signal) {
|
|
|
86
92
|
}
|
|
87
93
|
/** lunr: download an inbound file by its file_path from the Telegram file API. */
|
|
88
94
|
async function defaultDownloadFile(token, filePath) {
|
|
89
|
-
|
|
90
|
-
if (!res.ok)
|
|
91
|
-
throw new TelegramApiError(res.status, `file download HTTP ${res.status}`);
|
|
92
|
-
return new Uint8Array(await res.arrayBuffer());
|
|
95
|
+
return downloadAttachment(`${API_BASE}/file/bot${token}/${filePath}`);
|
|
93
96
|
}
|
|
94
97
|
function errMessage(err) {
|
|
95
98
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -148,10 +151,7 @@ function extractMention(text, entities, botInfo) {
|
|
|
148
151
|
out += text.slice(pos);
|
|
149
152
|
return { text: out.trim(), mentioned };
|
|
150
153
|
}
|
|
151
|
-
|
|
152
|
-
return typeof mimeType === "string" && mimeType.split(";")[0]?.trim().toLowerCase().startsWith("image/");
|
|
153
|
-
}
|
|
154
|
-
/** lunr: collect inbound image media from a Telegram message (largest photo + image documents). */
|
|
154
|
+
/** Collect the largest photo and document attachments. */
|
|
155
155
|
export function collectTelegramMedia(message) {
|
|
156
156
|
const descriptors = [];
|
|
157
157
|
if (message.photo && message.photo.length > 0) {
|
|
@@ -163,10 +163,10 @@ export function collectTelegramMedia(message) {
|
|
|
163
163
|
...(largest.file_size !== undefined ? { fileSize: largest.file_size } : {}),
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
|
-
if (message.document
|
|
166
|
+
if (message.document) {
|
|
167
167
|
descriptors.push({
|
|
168
168
|
fileId: message.document.file_id,
|
|
169
|
-
mimeType: message.document.mime_type ?? "
|
|
169
|
+
mimeType: message.document.mime_type ?? "application/octet-stream",
|
|
170
170
|
...(message.document.file_name ? { filename: message.document.file_name } : {}),
|
|
171
171
|
...(message.document.file_size !== undefined ? { fileSize: message.document.file_size } : {}),
|
|
172
172
|
});
|
|
@@ -254,7 +254,9 @@ export class TelegramAdapter {
|
|
|
254
254
|
abortController;
|
|
255
255
|
pending = new Map();
|
|
256
256
|
typing = new Map();
|
|
257
|
+
cfg;
|
|
257
258
|
constructor(cfg, options = {}) {
|
|
259
|
+
this.cfg = cfg;
|
|
258
260
|
this.scheduler = options.scheduler ?? defaultScheduler;
|
|
259
261
|
this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
260
262
|
this.now = options.now ?? Date.now;
|
|
@@ -353,7 +355,8 @@ export class TelegramAdapter {
|
|
|
353
355
|
return;
|
|
354
356
|
// lunr: download inbound image media before dispatch so the bridge can attach
|
|
355
357
|
// images to the turn. Failures drop the media but keep the text turn going.
|
|
356
|
-
if (update.message
|
|
358
|
+
if (update.message &&
|
|
359
|
+
isAuthorized(event.source, { ...loadGatewayConfig(), telegram: this.cfg }, createPairingStore())) {
|
|
357
360
|
const attachments = await this.downloadMedia(update.message);
|
|
358
361
|
if (attachments.length > 0)
|
|
359
362
|
event.attachments = attachments;
|
|
@@ -467,6 +470,26 @@ export class TelegramAdapter {
|
|
|
467
470
|
await this.callApi("answerCallbackQuery", body).catch(() => { });
|
|
468
471
|
}
|
|
469
472
|
/** lunr: register the bot's slash-command menu (Telegram client autocomplete). Best-effort — caller catches. */
|
|
473
|
+
async sendFile(chatId, file, opts) {
|
|
474
|
+
try {
|
|
475
|
+
const data = new FormData();
|
|
476
|
+
data.set("chat_id", chatId);
|
|
477
|
+
if (opts?.threadId)
|
|
478
|
+
data.set("message_thread_id", opts.threadId);
|
|
479
|
+
data.set("document", new Blob([new Uint8Array(readFileSync(file))]), basename(file));
|
|
480
|
+
const response = await fetch(`${API_BASE}/bot${this.cfg.token}/sendDocument`, {
|
|
481
|
+
method: "POST",
|
|
482
|
+
body: data,
|
|
483
|
+
signal: AbortSignal.timeout(30_000),
|
|
484
|
+
});
|
|
485
|
+
if (!response.ok)
|
|
486
|
+
return { success: false, error: `Telegram file delivery failed: HTTP ${response.status}` };
|
|
487
|
+
return { success: true };
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return { success: false, error: "Telegram file delivery failed. Check your connection." };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
470
493
|
async registerCommands(commands) {
|
|
471
494
|
const valid = commands
|
|
472
495
|
.filter((c) => /^[a-z0-9_]{1,32}$/.test(c.name) && c.description.trim().length > 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../../src/gateway/adapters/telegram.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway Telegram adapter (Phase 2b).\n *\n * Talks to the Telegram Bot API over HTTPS long-polling (zero-dep: global\n * fetch). connect() verifies the token via getMe, caches {botId, botUsername},\n * then starts the poll loop (getUpdates, offset-tracked, 30s long-poll). The\n * loop honors 429 retry_after, backs off exponentially on network/5xx errors\n * (1s→2s→5s→…max 30s, reset on success) and stops on disconnect() via an\n * AbortController.\n *\n * Inbound: updateToEvent() is a pure update → MessageEvent mapping (dm/group/\n * channel chat types, forum thread ids with the General topic normalized away,\n * @-mention / text_mention / `/cmd@bot` detection with the mention stripped,\n * own + all-bot messages dropped, media-only dropped — captions accepted).\n * Consecutive texts from the same chat/thread/user are debounced 300ms and\n * merged (\"\\n\"-joined, latest messageId kept) to reassemble client-split long\n * messages; texts starting with \"/\" flush immediately so commands never merge.\n * Callback queries are polled and mapped via callbackQueryToEvent().\n *\n * Outbound: sendMessage/editMessageText with parse_mode \"Markdown\" and a\n * one-shot plain-text retry when Telegram rejects the markup; send() honors\n * 429 retry_after once and then reports { success: false, retryable: true }.\n * Message splitting is NOT done here — the router owns that (text.ts).\n *\n * Typing: sendTyping() is a self-contained refresher — Telegram typing\n * bubbles expire after ~5s, so the action is re-sent on an unref'd 4s\n * interval per chat. The interval stops when send() completes for that chat\n * or after 2 minutes without a refresh (safety net). Keeping this here means\n * router/agent-bridge stay untouched.\n *\n * Testability: all Bot API HTTP goes through an injectable callApi; timers go\n * through an injectable Scheduler; backoffDelayMs() is a pure function.\n */\n\nimport type { PlatformConfig } from \"../config.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\tconst res = await fetch(`${API_BASE}/file/bot${token}/${filePath}`);\n\tif (!res.ok) throw new TelegramApiError(res.status, `file download HTTP ${res.status}`);\n\treturn new Uint8Array(await res.arrayBuffer());\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\nfunction isImageMimeType(mimeType: string | undefined): boolean {\n\treturn typeof mimeType === \"string\" && mimeType.split(\";\")[0]?.trim().toLowerCase().startsWith(\"image/\");\n}\n\n/** lunr: collect inbound image media from a Telegram message (largest photo + image documents). */\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 && isImageMimeType(message.document.mime_type)) {\n\t\tdescriptors.push({\n\t\t\tfileId: message.document.file_id,\n\t\t\tmimeType: message.document.mime_type ?? \"image/jpeg\",\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\tconstructor(cfg: PlatformConfig, options: TelegramAdapterOptions = {}) {\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 (update.message) {\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 registerCommands(commands: { name: string; description: string }[]): Promise<void> {\n\t\tconst valid = commands\n\t\t\t.filter((c) => /^[a-z0-9_]{1,32}$/.test(c.name) && c.description.trim().length > 0)\n\t\t\t.map((c) => ({ command: c.name, description: c.description.trim().slice(0, 256) }));\n\t\tif (valid.length === 0) return;\n\t\tawait this.callApi(\"setMyCommands\", { commands: valid });\n\t}\n\n\tprivate async callSend(body: Record<string, unknown>): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t} catch (err) {\n\t\t\tif (err instanceof TelegramApiError && err.status === 400 && body.parse_mode !== undefined) {\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message)) {\n\t\t\t\t\t// Telegram rejected our markup: retry once as plain text.\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\treturn this.callSend(plain);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\tawait this.sleep((err.retryAfter ?? 1) * 1000);\n\t\t\t\ttry {\n\t\t\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t\t\t} catch (retryErr) {\n\t\t\t\t\treturn { success: false, error: errMessage(retryErr), retryable: true };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync editMessage(chatId: string, messageId: string, text: string, buttons?: ButtonSpec[][]): Promise<SendResult> {\n\t\tconst body: Record<string, unknown> = {\n\t\t\tchat_id: chatId,\n\t\t\tmessage_id: Number(messageId),\n\t\t\ttext,\n\t\t\tparse_mode: \"Markdown\",\n\t\t};\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup !== undefined) body.reply_markup = markup;\n\t\ttry {\n\t\t\tawait this.callApi(\"editMessageText\", body);\n\t\t\treturn { success: true, messageId };\n\t\t} catch (err) {\n\t\t\tif (err instanceof TelegramApiError && err.status === 400) {\n\t\t\t\tif (NOT_MODIFIED_RE.test(err.message)) return { success: true, messageId };\n\t\t\t\tif (MARKDOWN_PARSE_ERROR_RE.test(err.message) && body.parse_mode !== undefined) {\n\t\t\t\t\tconst { parse_mode: _dropped, ...plain } = body;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.callApi(\"editMessageText\", plain);\n\t\t\t\t\t\treturn { success: true, messageId };\n\t\t\t\t\t} catch (retryErr) {\n\t\t\t\t\t\treturn { success: false, error: errMessage(retryErr) };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: errMessage(err),\n\t\t\t\tretryable: !(err instanceof TelegramApiError) || err.status >= 500,\n\t\t\t};\n\t\t}\n\t}\n\n\tasync sendTyping(chatId: string, threadId?: string): Promise<void> {\n\t\t// Self-contained typing refresher (see file header): re-send the action\n\t\t// every 4s until send() completes for this chat or the 2min safety net\n\t\t// fires — all errors swallowed.\n\t\tconst key = typingKey(chatId, threadId);\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, action: \"typing\" };\n\t\tif (threadId !== undefined) body.message_thread_id = Number(threadId);\n\t\tawait this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\tconst existing = this.typing.get(key);\n\t\tif (existing) {\n\t\t\texisting.lastRefresh = this.now();\n\t\t\treturn;\n\t\t}\n\t\tconst interval = this.scheduler.setInterval(() => {\n\t\t\tconst entry = this.typing.get(key);\n\t\t\tif (!entry) return;\n\t\t\tif (this.now() - entry.lastRefresh > TYPING_SAFETY_MS) {\n\t\t\t\tthis.stopTyping(key);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvoid this.callApi(\"sendChatAction\", body).catch(() => {});\n\t\t}, TYPING_INTERVAL_MS);\n\t\tthis.typing.set(key, { interval, lastRefresh: this.now() });\n\t}\n\n\tprivate stopTyping(key: string): void {\n\t\tconst entry = this.typing.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearInterval(entry.interval);\n\t\tthis.typing.delete(key);\n\t}\n}\n\nfunction typingKey(chatId: string, threadId?: string): string {\n\treturn `${chatId}:${threadId ?? \"\"}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../../src/gateway/adapters/telegram.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway Telegram adapter (Phase 2b).\n *\n * Talks to the Telegram Bot API over HTTPS long-polling (zero-dep: global\n * fetch). connect() verifies the token via getMe, caches {botId, botUsername},\n * then starts the poll loop (getUpdates, offset-tracked, 30s long-poll). The\n * loop honors 429 retry_after, backs off exponentially on network/5xx errors\n * (1s→2s→5s→…max 30s, reset on success) and stops on disconnect() via an\n * AbortController.\n *\n * Inbound: updateToEvent() is a pure update → MessageEvent mapping (dm/group/\n * channel chat types, forum thread ids with the General topic normalized away,\n * @-mention / text_mention / `/cmd@bot` detection with the mention stripped,\n * own + all-bot messages dropped, media-only dropped — captions accepted).\n * Consecutive texts from the same chat/thread/user are debounced 300ms and\n * merged (\"\\n\"-joined, latest messageId kept) to reassemble client-split long\n * messages; texts starting with \"/\" flush immediately so commands never merge.\n * Callback queries are polled and mapped via callbackQueryToEvent().\n *\n * Outbound: sendMessage/editMessageText with parse_mode \"Markdown\" and a\n * one-shot plain-text retry when Telegram rejects the markup; send() honors\n * 429 retry_after once and then reports { success: false, retryable: true }.\n * Message splitting is NOT done here — the router owns that (text.ts).\n *\n * Typing: sendTyping() is a self-contained refresher — Telegram typing\n * bubbles expire after ~5s, so the action is re-sent on an unref'd 4s\n * interval per chat. The interval stops when send() completes for that chat\n * or after 2 minutes without a refresh (safety net). Keeping this here means\n * router/agent-bridge stay untouched.\n *\n * Testability: all Bot API HTTP goes through an injectable callApi; timers go\n * through an injectable Scheduler; backoffDelayMs() is a pure function.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { isAuthorized } from \"../authz.ts\";\nimport { loadGatewayConfig, type PlatformConfig } from \"../config.ts\";\nimport { downloadAttachment } from \"../download.ts\";\nimport { createPairingStore } from \"../pairing.ts\";\nimport type {\n\tButtonSpec,\n\tCallbackEvent,\n\tChatType,\n\tInboundAttachment,\n\tMessageEvent,\n\tPlatformAdapter,\n\tSendOptions,\n\tSendResult,\n} from \"../types.ts\";\n\nconst API_BASE = \"https://api.telegram.org\";\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst TYPING_INTERVAL_MS = 4000; // bubbles expire after ~5s\nconst TYPING_SAFETY_MS = 120_000; // auto-stop after 2min without a refresh\nconst REPLY_TO_TEXT_MAX = 500;\nconst MARKDOWN_PARSE_ERROR_RE = /can't parse|parse entities/i;\nconst NOT_MODIFIED_RE = /message is not modified/i;\n\n// ---------------------------------------------------------------------------\n// Bot API shapes (only the fields the adapter reads)\n// ---------------------------------------------------------------------------\n\nexport interface TelegramUser {\n\tid: number;\n\tis_bot?: boolean;\n\tusername?: string;\n\tfirst_name?: string;\n}\n\nexport interface TelegramChat {\n\tid: number;\n\ttype: string;\n\tis_forum?: boolean;\n}\n\nexport interface TelegramEntity {\n\ttype: string;\n\toffset: number; // UTF-16 code units — same indexing as JS strings\n\tlength: number;\n\tuser?: TelegramUser;\n}\n\nexport interface TelegramPhotoSize {\n\tfile_id: string;\n\tfile_unique_id: string;\n\twidth: number;\n\theight: number;\n\tfile_size?: number;\n}\n\nexport interface TelegramDocument {\n\tfile_id: string;\n\tfile_unique_id: string;\n\tfile_name?: string;\n\tmime_type?: string;\n\tfile_size?: number;\n}\n\nexport interface TelegramMessage {\n\tmessage_id: number;\n\tfrom?: TelegramUser;\n\tchat: TelegramChat;\n\ttext?: string;\n\tcaption?: string;\n\tentities?: TelegramEntity[];\n\tcaption_entities?: TelegramEntity[];\n\tmessage_thread_id?: number;\n\treply_to_message?: { text?: string };\n\tphoto?: TelegramPhotoSize[];\n\tdocument?: TelegramDocument;\n}\n\nexport interface TelegramUpdate {\n\tupdate_id: number;\n\tmessage?: TelegramMessage;\n\tcallback_query?: TelegramCallbackQuery;\n}\n\nexport interface TelegramCallbackQuery {\n\tid: string;\n\tfrom: TelegramUser;\n\tmessage?: TelegramMessage;\n\tdata?: string;\n}\n\nexport interface BotInfo {\n\tbotId: number;\n\tbotUsername: string;\n}\n\ninterface TelegramApiResponse {\n\tok: boolean;\n\tresult?: unknown;\n\tdescription?: string;\n\terror_code?: number;\n\tparameters?: { retry_after?: number };\n}\n\n/** Bot API call: resolves with the unwrapped `result`; throws TelegramApiError on API/HTTP errors. */\nexport type CallApi = (method: string, body: Record<string, unknown>, signal?: AbortSignal) => Promise<unknown>;\n\n/** Timer injection seam (production default uses globals, unref'd). */\nexport interface Scheduler {\n\tsetTimeout(fn: () => void, ms: number): ReturnType<typeof setTimeout>;\n\tclearTimeout(handle: ReturnType<typeof setTimeout>): void;\n\tsetInterval(fn: () => void, ms: number): ReturnType<typeof setInterval>;\n\tclearInterval(handle: ReturnType<typeof setInterval>): void;\n}\n\nexport class TelegramApiError extends Error {\n\treadonly status: number;\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, description: string, retryAfter?: number) {\n\t\tsuper(description);\n\t\tthis.name = \"TelegramApiError\";\n\t\tthis.status = status;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n\n/** Exponential poll-error backoff: 1s→2s→5s→10s→30s (max), indexed by consecutive failure count. */\nexport const BACKOFF_DELAYS_MS = [1000, 2000, 5000, 10000, 30000] as const;\n\nexport function backoffDelayMs(consecutiveFailures: number): number {\n\tconst index = Math.min(Math.max(consecutiveFailures, 0), BACKOFF_DELAYS_MS.length - 1);\n\treturn BACKOFF_DELAYS_MS[index];\n}\n\nconst defaultScheduler: Scheduler = {\n\tsetTimeout: (fn, ms) => {\n\t\tconst handle = setTimeout(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearTimeout: (handle) => clearTimeout(handle),\n\tsetInterval: (fn, ms) => {\n\t\tconst handle = setInterval(fn, ms);\n\t\thandle.unref?.();\n\t\treturn handle;\n\t},\n\tclearInterval: (handle) => clearInterval(handle),\n};\n\nasync function defaultCallApi(\n\ttoken: string,\n\tmethod: string,\n\tbody: Record<string, unknown>,\n\tsignal?: AbortSignal,\n): Promise<unknown> {\n\tconst res = await fetch(`${API_BASE}/bot${token}/${method}`, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"content-type\": \"application/json\" },\n\t\tbody: JSON.stringify(body),\n\t\tsignal,\n\t});\n\tconst data = (await res.json().catch(() => undefined)) as TelegramApiResponse | undefined;\n\tif (res.status === 429) {\n\t\tthrow new TelegramApiError(429, data?.description ?? \"rate limited\", data?.parameters?.retry_after);\n\t}\n\tif (!res.ok || !data?.ok) {\n\t\tthrow new TelegramApiError(\n\t\t\tdata?.error_code ?? res.status,\n\t\t\tdata?.description ?? `HTTP ${res.status}`,\n\t\t\tdata?.parameters?.retry_after,\n\t\t);\n\t}\n\treturn data.result;\n}\n\n/** lunr: download an inbound file by its file_path from the Telegram file API. */\nasync function defaultDownloadFile(token: string, filePath: string): Promise<Uint8Array> {\n\treturn downloadAttachment(`${API_BASE}/file/bot${token}/${filePath}`);\n}\n\nfunction errMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction chatTypeOf(chat: TelegramChat): ChatType | null {\n\tswitch (chat.type) {\n\t\tcase \"private\":\n\t\t\treturn \"dm\";\n\t\tcase \"group\":\n\t\tcase \"supergroup\":\n\t\t\treturn \"group\";\n\t\tcase \"channel\":\n\t\t\treturn \"channel\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\n/**\n * Detect bot mentions via entities and strip them from the text.\n * - \"mention\" whose slice is `@<botUsername>` (case-insensitive): whole slice removed.\n * - \"text_mention\" whose user is the bot: whole slice removed.\n * - \"bot_command\" containing `@<botUsername>` (e.g. /new@lunrbot): only the\n * \"@bot\" suffix is removed so router slash-command parsing still works.\n */\nfunction extractMention(\n\ttext: string,\n\tentities: TelegramEntity[],\n\tbotInfo: BotInfo,\n): { text: string; mentioned: boolean } {\n\tif (!botInfo.botUsername) return { text: text.trim(), mentioned: false };\n\tconst botAt = `@${botInfo.botUsername}`.toLowerCase();\n\tconst removals: Array<[number, number]> = [];\n\tlet mentioned = false;\n\tfor (const entity of entities) {\n\t\tconst slice = text.slice(entity.offset, entity.offset + entity.length);\n\t\tif (entity.type === \"mention\" && slice.toLowerCase() === botAt) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"text_mention\" && entity.user?.id === botInfo.botId) {\n\t\t\tmentioned = true;\n\t\t\tremovals.push([entity.offset, entity.offset + entity.length]);\n\t\t} else if (entity.type === \"bot_command\" && slice.toLowerCase().includes(botAt)) {\n\t\t\tmentioned = true;\n\t\t\tconst at = slice.toLowerCase().indexOf(botAt);\n\t\t\tremovals.push([entity.offset + at, entity.offset + at + botAt.length]);\n\t\t}\n\t}\n\tif (removals.length === 0) return { text: text.trim(), mentioned };\n\tremovals.sort((a, b) => a[0] - b[0]);\n\tlet out = \"\";\n\tlet pos = 0;\n\tfor (const [start, end] of removals) {\n\t\tout += text.slice(pos, start);\n\t\tpos = Math.max(pos, end);\n\t}\n\tout += text.slice(pos);\n\treturn { text: out.trim(), mentioned };\n}\n\n/**\n * lunr: media descriptor for an inbound image (photo or image document).\n * The adapter downloads the bytes via getFile + downloadFile and base64-encodes them.\n */\nexport interface TelegramMediaDescriptor {\n\tfileId: string;\n\tmimeType: string;\n\tfilename?: string;\n\tfileSize?: number;\n}\n\n/** Collect the largest photo and document attachments. */\nexport function collectTelegramMedia(message: TelegramMessage): TelegramMediaDescriptor[] {\n\tconst descriptors: TelegramMediaDescriptor[] = [];\n\tif (message.photo && message.photo.length > 0) {\n\t\t// Largest size is last per Telegram Bot API ordering.\n\t\tconst largest = message.photo[message.photo.length - 1];\n\t\tdescriptors.push({\n\t\t\tfileId: largest.file_id,\n\t\t\tmimeType: \"image/jpeg\",\n\t\t\t...(largest.file_size !== undefined ? { fileSize: largest.file_size } : {}),\n\t\t});\n\t}\n\tif (message.document) {\n\t\tdescriptors.push({\n\t\t\tfileId: message.document.file_id,\n\t\t\tmimeType: message.document.mime_type ?? \"application/octet-stream\",\n\t\t\t...(message.document.file_name ? { filename: message.document.file_name } : {}),\n\t\t\t...(message.document.file_size !== undefined ? { fileSize: message.document.file_size } : {}),\n\t\t});\n\t}\n\treturn descriptors;\n}\n\n/**\n * Map a Telegram update to a MessageEvent; null = drop (non-message update,\n * own message, any bot message, unknown chat type, or a message with neither\n * text nor inbound image media).\n */\nexport function updateToEvent(update: TelegramUpdate, botInfo: BotInfo): MessageEvent | null {\n\tconst message = update.message;\n\tif (!message) return null;\n\tconst from = message.from;\n\tif (!from) return null; // channel posts without a user signature\n\tif (from.id === botInfo.botId) return null; // own messages\n\tif (from.is_bot) return null; // all bots (hermes exclusive_bot_mentions, simplified)\n\n\tconst hasMedia = collectTelegramMedia(message).length > 0;\n\tconst rawText = message.text ?? message.caption;\n\tif (!rawText && !hasMedia) return null; // no text and no image media\n\tconst rawEntities = message.text !== undefined ? message.entities : message.caption_entities;\n\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\n\t// Forum threads only; the General topic (id 1) is normalized away (hermes).\n\tconst threadId =\n\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t? String(message.message_thread_id)\n\t\t\t: undefined;\n\n\tconst { text, mentioned } = extractMention(rawText ?? \"\", rawEntities ?? [], botInfo);\n\n\tconst replyText = message.reply_to_message?.text;\n\n\treturn {\n\t\ttext,\n\t\tmessageId: String(message.message_id),\n\t\treplyToText: replyText !== undefined ? replyText.slice(0, REPLY_TO_TEXT_MAX) : undefined,\n\t\tsource: {\n\t\t\tplatform: \"telegram\",\n\t\t\tchatId: String(message.chat.id),\n\t\t\tchatType,\n\t\t\tuserId: String(from.id),\n\t\t\tuserName: from.username ?? from.first_name,\n\t\t\tthreadId,\n\t\t},\n\t\tmetadata: mentioned ? { mentionedBot: true } : undefined,\n\t};\n}\n\nexport function callbackQueryToEvent(query: TelegramCallbackQuery): CallbackEvent | null {\n\tconst message = query.message;\n\tif (!message) return null;\n\tconst from = query.from;\n\tconst chatType = chatTypeOf(message.chat);\n\tif (chatType === null) return null;\n\treturn {\n\t\tid: query.id,\n\t\tchatId: String(message.chat.id),\n\t\tmessageId: String(message.message_id),\n\t\tuserId: String(from.id),\n\t\tuserName: from.username ?? from.first_name,\n\t\tdata: query.data ?? \"\",\n\t\tthreadId:\n\t\t\tmessage.chat.is_forum === true && message.message_thread_id !== undefined && message.message_thread_id !== 1\n\t\t\t\t? String(message.message_thread_id)\n\t\t\t\t: undefined,\n\t};\n}\n\nexport interface TelegramAdapterOptions {\n\t/** Injected Bot API transport (tests). When omitted, HTTPS calls are made with cfg.token. */\n\tcallApi?: CallApi;\n\t/** lunr: injected file downloader for inbound media (tests). Downloads from api.telegram.org/file/bot<token>/<file_path>. */\n\tdownloadFile?: (filePath: string) => Promise<Uint8Array>;\n\t/** lunr: skip inbound media larger than this many bytes (pre-download). Defaults to 10MB. */\n\tmaxMediaBytes?: number;\n\tscheduler?: Scheduler;\n\tdebounceMs?: number;\n\tnow?: () => number;\n}\n\ninterface PendingEntry {\n\tevent: MessageEvent;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\ninterface TypingEntry {\n\tinterval: ReturnType<typeof setInterval>;\n\tlastRefresh: number;\n}\n\nexport class TelegramAdapter implements PlatformAdapter {\n\treadonly platform = \"telegram\";\n\treadonly maxMessageLength = 4096;\n\n\tprivate readonly callApi: CallApi;\n\tprivate readonly downloadFile: (filePath: string) => Promise<Uint8Array>;\n\tprivate readonly maxMediaBytes: number;\n\tprivate readonly scheduler: Scheduler;\n\tprivate readonly debounceMs: number;\n\tprivate readonly now: () => number;\n\tprivate handler?: (event: MessageEvent) => void;\n\tprivate callbackHandler?: (event: CallbackEvent) => void;\n\tprivate botInfo?: BotInfo;\n\tprivate offset = 0;\n\tprivate abortController?: AbortController;\n\tprivate readonly pending = new Map<string, PendingEntry>();\n\tprivate readonly typing = new Map<string, TypingEntry>();\n\n\tprivate readonly cfg: PlatformConfig;\n\tconstructor(cfg: PlatformConfig, options: TelegramAdapterOptions = {}) {\n\t\tthis.cfg = cfg;\n\t\tthis.scheduler = options.scheduler ?? defaultScheduler;\n\t\tthis.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\t\tthis.now = options.now ?? Date.now;\n\t\tif (options.callApi) {\n\t\t\tthis.callApi = options.callApi;\n\t\t} else {\n\t\t\tif (!cfg.token) {\n\t\t\t\tthrow new Error(\"TelegramAdapter: no bot token (set telegram.token or LUNR_TELEGRAM_BOT_TOKEN)\");\n\t\t\t}\n\t\t\tconst token = cfg.token;\n\t\t\tthis.callApi = (method, body, signal) => defaultCallApi(token, method, body, signal);\n\t\t}\n\t\t// lunr: inbound media download. When tests inject callApi they also inject\n\t\t// downloadFile; production uses the bot token against the Telegram file API.\n\t\tthis.downloadFile = options.downloadFile ?? ((filePath) => defaultDownloadFile(cfg.token ?? \"\", filePath));\n\t\tthis.maxMediaBytes = options.maxMediaBytes ?? 10 * 1024 * 1024;\n\t}\n\n\tonMessage(handler: (event: MessageEvent) => void): void {\n\t\tthis.handler = handler;\n\t}\n\n\tonCallback(handler: (event: CallbackEvent) => void): void {\n\t\tthis.callbackHandler = handler;\n\t}\n\n\tasync connect(): Promise<boolean> {\n\t\tconst me = (await this.callApi(\"getMe\", {})) as TelegramUser;\n\t\tthis.botInfo = { botId: me.id, botUsername: me.username ?? \"\" };\n\t\tthis.abortController = new AbortController();\n\t\tvoid this.pollLoop(this.abortController.signal); // intentionally not awaited\n\t\treturn true;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tthis.abortController?.abort();\n\t\tfor (const key of [...this.pending.keys()]) this.cancelPending(key);\n\t\tfor (const key of [...this.typing.keys()]) this.stopTyping(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Long-poll loop\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollLoop(signal: AbortSignal): Promise<void> {\n\t\tlet failures = 0;\n\t\twhile (!signal.aborted) {\n\t\t\ttry {\n\t\t\t\tconst updates = (await this.callApi(\n\t\t\t\t\t\"getUpdates\",\n\t\t\t\t\t{ offset: this.offset, timeout: 30, allowed_updates: [\"message\", \"callback_query\"] },\n\t\t\t\t\tsignal,\n\t\t\t\t)) as TelegramUpdate[];\n\t\t\t\tfailures = 0;\n\t\t\t\tfor (const update of updates) {\n\t\t\t\t\tthis.offset = update.update_id + 1;\n\t\t\t\t\tawait this.dispatchUpdate(update);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) return;\n\t\t\t\tif (err instanceof TelegramApiError && err.status === 429) {\n\t\t\t\t\tconst retryAfter = Math.min(err.retryAfter ?? 1, 30);\n\t\t\t\t\tawait this.sleep(retryAfter * 1000, signal);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst delay = backoffDelayMs(failures);\n\t\t\t\tfailures += 1;\n\t\t\t\tconsole.error(`[gateway] telegram poll error (retry in ${delay}ms): ${errMessage(err)}`);\n\t\t\t\tawait this.sleep(delay, signal);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst timer = this.scheduler.setTimeout(resolve, ms);\n\t\t\tsignal?.addEventListener(\n\t\t\t\t\"abort\",\n\t\t\t\t() => {\n\t\t\t\t\tthis.scheduler.clearTimeout(timer);\n\t\t\t\t\tresolve();\n\t\t\t\t},\n\t\t\t\t{ once: true },\n\t\t\t);\n\t\t});\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Inbound dispatch + debounce\n\t// -----------------------------------------------------------------------\n\n\tprivate debounceKey(event: MessageEvent): string {\n\t\treturn `${event.source.chatId}:${event.source.threadId ?? \"\"}:${event.source.userId}`;\n\t}\n\n\tprivate async dispatchUpdate(update: TelegramUpdate): Promise<void> {\n\t\tif (!this.botInfo) return;\n\t\tif (update.callback_query) {\n\t\t\tconst event = callbackQueryToEvent(update.callback_query);\n\t\t\tif (event) this.callbackHandler?.(event);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.handler) return;\n\t\tconst event = updateToEvent(update, this.botInfo);\n\t\tif (!event) return;\n\t\t// lunr: download inbound image media before dispatch so the bridge can attach\n\t\t// images to the turn. Failures drop the media but keep the text turn going.\n\t\tif (\n\t\t\tupdate.message &&\n\t\t\tisAuthorized(event.source, { ...loadGatewayConfig(), telegram: this.cfg }, createPairingStore())\n\t\t) {\n\t\t\tconst attachments = await this.downloadMedia(update.message);\n\t\t\tif (attachments.length > 0) event.attachments = attachments;\n\t\t}\n\t\tconst key = this.debounceKey(event);\n\t\tif (event.text.trimStart().startsWith(\"/\")) {\n\t\t\t// Commands must not merge: flush any pending text, emit immediately.\n\t\t\tthis.flushPending(key);\n\t\t\tthis.handler(event);\n\t\t\treturn;\n\t\t}\n\t\tconst existing = this.pending.get(key);\n\t\tif (existing) {\n\t\t\tthis.scheduler.clearTimeout(existing.timer);\n\t\t\t// Merge: earlier text first, latest messageId (and other fields) win.\n\t\t\tconst mergedAttachments = [...(existing.event.attachments ?? []), ...(event.attachments ?? [])];\n\t\t\texisting.event = {\n\t\t\t\t...event,\n\t\t\t\ttext: `${existing.event.text}\\n${event.text}`,\n\t\t\t\t...(mergedAttachments.length > 0 ? { attachments: mergedAttachments } : {}),\n\t\t\t};\n\t\t\texisting.timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t} else {\n\t\t\tconst timer = this.scheduler.setTimeout(() => this.flushPending(key), this.debounceMs);\n\t\t\tthis.pending.set(key, { event, timer });\n\t\t}\n\t}\n\n\t/** lunr: download + base64-encode inbound image media for one Telegram message. */\n\tprivate async downloadMedia(message: TelegramMessage): Promise<InboundAttachment[]> {\n\t\tconst descriptors = collectTelegramMedia(message);\n\t\tif (descriptors.length === 0) return [];\n\t\tconst attachments: InboundAttachment[] = [];\n\t\tfor (const descriptor of descriptors) {\n\t\t\tif (descriptor.fileSize !== undefined && descriptor.fileSize > this.maxMediaBytes) continue;\n\t\t\ttry {\n\t\t\t\tconst fileResult = (await this.callApi(\"getFile\", { file_id: descriptor.fileId })) as\n\t\t\t\t\t| { file_path?: string; file_size?: number }\n\t\t\t\t\t| undefined;\n\t\t\t\tconst filePath = fileResult?.file_path;\n\t\t\t\tif (!filePath) continue;\n\t\t\t\tif (fileResult?.file_size !== undefined && fileResult.file_size > this.maxMediaBytes) continue;\n\t\t\t\tconst bytes = await this.downloadFile(filePath);\n\t\t\t\tif (bytes.byteLength > this.maxMediaBytes) continue;\n\t\t\t\tattachments.push({\n\t\t\t\t\tdata: Buffer.from(bytes).toString(\"base64\"),\n\t\t\t\t\tmimeType: descriptor.mimeType,\n\t\t\t\t\t...(descriptor.filename ? { filename: descriptor.filename } : {}),\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\t// Media download failed — drop this attachment, keep the text turn.\n\t\t\t}\n\t\t}\n\t\treturn attachments;\n\t}\n\n\tprivate flushPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t\tthis.handler?.(entry.event);\n\t}\n\n\tprivate cancelPending(key: string): void {\n\t\tconst entry = this.pending.get(key);\n\t\tif (!entry) return;\n\t\tthis.scheduler.clearTimeout(entry.timer);\n\t\tthis.pending.delete(key);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Outbound\n\t// -----------------------------------------------------------------------\n\n\tprivate buildSendBody(chatId: string, text: string, opts?: SendOptions): Record<string, unknown> {\n\t\tconst body: Record<string, unknown> = { chat_id: chatId, text, parse_mode: \"Markdown\" };\n\t\tif (opts?.threadId !== undefined) body.message_thread_id = Number(opts.threadId);\n\t\tif (opts?.replyTo !== undefined) body.reply_parameters = { message_id: Number(opts.replyTo) };\n\t\treturn body;\n\t}\n\n\tprivate buildReplyMarkup(\n\t\tbuttons?: ButtonSpec[][],\n\t): { inline_keyboard: { text: string; callback_data: string }[][] } | undefined {\n\t\tif (buttons === undefined) return undefined;\n\t\treturn {\n\t\t\tinline_keyboard: buttons.map((row) => row.map((b) => ({ text: b.label, callback_data: b.data }))),\n\t\t};\n\t}\n\n\tasync send(chatId: string, text: string, opts?: SendOptions): Promise<SendResult> {\n\t\t// A completed send ends the typing bubble — stop the refresher (see sendTyping).\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\treturn this.callSend(body);\n\t}\n\n\tasync sendButtons(chatId: string, text: string, buttons: ButtonSpec[][], opts?: SendOptions): Promise<SendResult> {\n\t\tthis.stopTyping(typingKey(chatId, opts?.threadId));\n\t\tconst body = this.buildSendBody(chatId, text, opts);\n\t\tconst markup = this.buildReplyMarkup(buttons);\n\t\tif (markup) body.reply_markup = markup;\n\t\treturn this.callSend(body);\n\t}\n\n\tasync answerCallback(id: string, text?: string): Promise<void> {\n\t\tconst body: Record<string, unknown> = { callback_query_id: id };\n\t\tif (text !== undefined) body.text = text;\n\t\tawait this.callApi(\"answerCallbackQuery\", body).catch(() => {});\n\t}\n\n\t/** lunr: register the bot's slash-command menu (Telegram client autocomplete). Best-effort — caller catches. */\n\tasync sendFile(chatId: string, file: string, opts?: SendOptions): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst data = new FormData();\n\t\t\tdata.set(\"chat_id\", chatId);\n\t\t\tif (opts?.threadId) data.set(\"message_thread_id\", opts.threadId);\n\t\t\tdata.set(\"document\", new Blob([new Uint8Array(readFileSync(file))]), basename(file));\n\t\t\tconst response = await fetch(`${API_BASE}/bot${this.cfg.token}/sendDocument`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: data,\n\t\t\t\tsignal: AbortSignal.timeout(30_000),\n\t\t\t});\n\t\t\tif (!response.ok) return { success: false, error: `Telegram file delivery failed: HTTP ${response.status}` };\n\t\t\treturn { success: true };\n\t\t} catch {\n\t\t\treturn { success: false, error: \"Telegram file delivery failed. Check your connection.\" };\n\t\t}\n\t}\n\n\tasync registerCommands(commands: { name: string; description: string }[]): Promise<void> {\n\t\tconst valid = commands\n\t\t\t.filter((c) => /^[a-z0-9_]{1,32}$/.test(c.name) && c.description.trim().length > 0)\n\t\t\t.map((c) => ({ command: c.name, description: c.description.trim().slice(0, 256) }));\n\t\tif (valid.length === 0) return;\n\t\tawait this.callApi(\"setMyCommands\", { commands: valid });\n\t}\n\n\tprivate async callSend(body: Record<string, unknown>): Promise<SendResult> {\n\t\ttry {\n\t\t\tconst result = (await this.callApi(\"sendMessage\", body)) as { message_id: number };\n\t\t\treturn { success: true, messageId: String(result.message_id) };\n\t\t} catch (err) {\n\t\t\tif (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"]}
|
|
@@ -30,11 +30,12 @@
|
|
|
30
30
|
import type { AgentMessage, ThinkingLevel } from "@ashx-j/lunr-agent";
|
|
31
31
|
import type { ImageContent } from "@ashx-j/lunr-ai";
|
|
32
32
|
import type { Model } from "@ashx-j/lunr-ai/compat";
|
|
33
|
-
import type { AgentSessionEvent, SessionStats } from "../core/agent-session.ts";
|
|
33
|
+
import type { AgentSession, AgentSessionEvent, SessionStats } from "../core/agent-session.ts";
|
|
34
34
|
import type { CompactionResult } from "../core/compaction/index.ts";
|
|
35
35
|
import type { ContextUsage, SessionShutdownEvent, ToolDefinition } from "../core/extensions/types.ts";
|
|
36
36
|
import type { ModelRuntime } from "../core/model-runtime.ts";
|
|
37
|
-
import type
|
|
37
|
+
import { type ReadonlySessionManager, SessionManager } from "../core/session-manager.ts";
|
|
38
|
+
import type { SettingsManager } from "../core/settings-manager.ts";
|
|
38
39
|
import type { MessageEvent } from "./types.ts";
|
|
39
40
|
/** runTurn resolves with this when the message was queued behind a running turn. */
|
|
40
41
|
export declare const QUEUED = "__lunr_gateway_queued__";
|
|
@@ -73,6 +74,9 @@ export interface BridgeSession {
|
|
|
73
74
|
emit(event: SessionShutdownEvent): Promise<unknown>;
|
|
74
75
|
};
|
|
75
76
|
dispose?(): void;
|
|
77
|
+
setTransferring?(value: boolean): void;
|
|
78
|
+
waitForIdle?(): Promise<void>;
|
|
79
|
+
readonly isRetrying?: boolean;
|
|
76
80
|
readonly isStreaming: boolean;
|
|
77
81
|
readonly isCompacting?: boolean;
|
|
78
82
|
readonly model?: Model<any>;
|
|
@@ -80,7 +84,9 @@ export interface BridgeSession {
|
|
|
80
84
|
readonly thinkingLevel: ThinkingLevel;
|
|
81
85
|
readonly messages: AgentMessage[];
|
|
82
86
|
readonly systemPrompt: string;
|
|
83
|
-
readonly
|
|
87
|
+
readonly settingsManager?: SettingsManager;
|
|
88
|
+
readonly resourceLoader?: AgentSession["resourceLoader"];
|
|
89
|
+
readonly sessionManager?: ReadonlySessionManager & Partial<Pick<SessionManager, "getPermissionMode" | "setPermissionMode">>;
|
|
84
90
|
getActiveToolNames(): string[];
|
|
85
91
|
getToolDefinition(name: string): ToolDefinition | undefined;
|
|
86
92
|
getAvailableThinkingLevels(): ThinkingLevel[];
|
|
@@ -113,6 +119,7 @@ export declare class AgentBridge {
|
|
|
113
119
|
private readonly cache;
|
|
114
120
|
private readonly redoStack;
|
|
115
121
|
private readonly creating;
|
|
122
|
+
private readonly changing;
|
|
116
123
|
constructor(options?: {
|
|
117
124
|
cacheCap?: number;
|
|
118
125
|
sessionFactory?: SessionFactory;
|
|
@@ -123,10 +130,13 @@ export declare class AgentBridge {
|
|
|
123
130
|
*/
|
|
124
131
|
runTurn(key: string, event: MessageEvent, callbacks?: TurnCallbacks): Promise<string>;
|
|
125
132
|
abort(key: string): Promise<void>;
|
|
133
|
+
shutdown(): Promise<void>;
|
|
134
|
+
peekSession(key: string): BridgeSession | undefined;
|
|
126
135
|
/** Return the live session for a key, or null when none exists yet. */
|
|
127
136
|
getSession(key: string): Promise<BridgeSession | null>;
|
|
128
137
|
/** Switch the chat to a different persisted session file. */
|
|
129
138
|
switchSession(key: string, sessionFile: string): Promise<void>;
|
|
139
|
+
private hasAttachedWork;
|
|
130
140
|
/** Undo the last user turn and push the previous leaf onto the redo stack. */
|
|
131
141
|
undo(key: string): Promise<{
|
|
132
142
|
userText: string;
|
|
@@ -142,6 +152,7 @@ export declare class AgentBridge {
|
|
|
142
152
|
private drainQueue;
|
|
143
153
|
private getOrCreate;
|
|
144
154
|
private createEntry;
|
|
155
|
+
private attachTransfer;
|
|
145
156
|
private _enforceCacheCap;
|
|
146
157
|
private _subscribeToSession;
|
|
147
158
|
private _handleSessionEvent;
|