telegram implementation
Runtime ownership
lib/adapter.mjs owns grammY connection and polling, inbound admission, turn execution, media and outbound transport. It creates one TelegramCommands and one TelegramAskFlow for its lifetime, so replacing the bot during reconnect retains pending prompts and the short-token counter. Both owners read the adapter's current transport, control API and command catalog at call time.
lib/commands.mjs publishes the public command menu and handles slash commands, picker rendering, pagination and callbacks. It owns pending picker descriptors and uses the shared command core for controls and project/context selection. An empty catalog admits no local command. Operator checks stay before context-picker consumption and before the callback acknowledgement that would prevent showing a refusal alert.
lib/askFlow.mjs owns pending questions, option selection, custom-text capture and settlement. The adapter's postAsk and resolveAsk hooks are consumed by the shared live-message reducer and delegate to this owner. Question callbacks allow the original sender or an operator; custom text is captured only from the original sender, in the existing inbound order. Core ask_resolved events retire prompts, including cancellation while delivery is pending: the question is registered before sending, removed immediately on resolution, and its late-arriving keyboard is cleared without re-registering it; the configured askTimeoutMs bounds only interactive command pickers. Keyboard rendering remains in lib/ask.mjs, and sends/edits use the adapter's existing transport and flood-wait handling.
Command picker callbacks and pagination must match their originating message. An older chooser cannot consume or replace a newer one; completed descriptors are consumed before callback acknowledgement.
askTimeoutMs controls only interactive picker menus, such as /model. With no override it uses six minutes. An expired menu closes locally; parked core questions have no additional adapter deadline.
Parked AskUserQuestion prompts remain answerable until the core accepts an answer or emits ask_resolved for timeout, cancellation or replacement. Channel adapters do not expire these questions locally. askTimeoutMs controls only the adapter's local interactive pickers or numbered menus; it does not change the core question deadline. Core resolution removes the pending entry and retires the prompt. Telegram accepts free text only from the original sender and permits option callbacks from that sender or an operator; WhatsApp text replies retain their original sender binding.
Native question rendering passes the selected service-message table to formatQuestionOptionLabel(option, messages, maxLabelLength) and formatQuestionPrompt(question, messages) from elowen-plugin-shared/ask. Recommendation labels and numbered reply instructions use English, Czech or Slovak without changing stored option labels or values. Non-recommended options have no suffix, and custom: false omits the custom-reply instruction. Rendering keeps the channel's markup and limits; there is no message-table fallback.