fix(telegram): wire sendPollTelegram into channel action handler#18005
fix(telegram): wire sendPollTelegram into channel action handler#18005steipete merged 1 commit intoopenclaw:mainfrom
Conversation
src/agents/tools/telegram-actions.ts
Outdated
| const to = args.to as string | undefined; | ||
| if (!to) { | ||
| throw new Error("sendPoll requires 'to' (chat ID)"); | ||
| } | ||
| const question = (args.question ?? args.pollQuestion) as string | undefined; | ||
| if (!question) { | ||
| throw new Error("sendPoll requires 'question'"); | ||
| } | ||
| const options = (args.options ?? args.pollOption) as string[] | undefined; | ||
| if (!options || options.length < 2) { | ||
| throw new Error("sendPoll requires at least 2 options"); | ||
| } | ||
| const maxSelections = | ||
| typeof args.maxSelections === "number" ? args.maxSelections : undefined; | ||
| const isAnonymous = | ||
| typeof args.isAnonymous === "boolean" ? args.isAnonymous : undefined; | ||
| const silent = typeof args.silent === "boolean" ? args.silent : undefined; | ||
| const replyToMessageId = | ||
| typeof args.replyTo === "number" ? args.replyTo : undefined; | ||
| const messageThreadId = | ||
| typeof args.threadId === "number" ? args.threadId : undefined; | ||
| const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined; |
There was a problem hiding this comment.
args is not defined — should be params
The sendPoll block references args throughout (e.g., args.to, args.question, args.options), but the function parameter is named params (line 74). Every other action block in handleTelegramAction correctly uses params. This will cause a ReferenceError at runtime when the sendPoll action is invoked.
Additionally, const accountId on line 353 shadows the outer accountId (line 78), which is inconsistent with all other action blocks that reuse the outer-scope accountId.
All args.* references need to be changed to params.*, and the re-declaration of accountId should be removed in favor of the existing outer-scope variable. For example:
| const to = args.to as string | undefined; | |
| if (!to) { | |
| throw new Error("sendPoll requires 'to' (chat ID)"); | |
| } | |
| const question = (args.question ?? args.pollQuestion) as string | undefined; | |
| if (!question) { | |
| throw new Error("sendPoll requires 'question'"); | |
| } | |
| const options = (args.options ?? args.pollOption) as string[] | undefined; | |
| if (!options || options.length < 2) { | |
| throw new Error("sendPoll requires at least 2 options"); | |
| } | |
| const maxSelections = | |
| typeof args.maxSelections === "number" ? args.maxSelections : undefined; | |
| const isAnonymous = | |
| typeof args.isAnonymous === "boolean" ? args.isAnonymous : undefined; | |
| const silent = typeof args.silent === "boolean" ? args.silent : undefined; | |
| const replyToMessageId = | |
| typeof args.replyTo === "number" ? args.replyTo : undefined; | |
| const messageThreadId = | |
| typeof args.threadId === "number" ? args.threadId : undefined; | |
| const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined; | |
| const to = params.to as string | undefined; | |
| if (!to) { | |
| throw new Error("sendPoll requires 'to' (chat ID)"); | |
| } | |
| const question = (params.question ?? params.pollQuestion) as string | undefined; | |
| if (!question) { | |
| throw new Error("sendPoll requires 'question'"); | |
| } | |
| const options = (params.options ?? params.pollOption) as string[] | undefined; | |
| if (!options || options.length < 2) { | |
| throw new Error("sendPoll requires at least 2 options"); | |
| } | |
| const maxSelections = | |
| typeof params.maxSelections === "number" ? params.maxSelections : undefined; | |
| const isAnonymous = | |
| typeof params.isAnonymous === "boolean" ? params.isAnonymous : undefined; | |
| const silent = typeof params.silent === "boolean" ? params.silent : undefined; | |
| const replyToMessageId = | |
| typeof params.replyTo === "number" ? params.replyTo : undefined; | |
| const messageThreadId = | |
| typeof params.threadId === "number" ? params.threadId : undefined; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tools/telegram-actions.ts
Line: 332:353
Comment:
**`args` is not defined — should be `params`**
The `sendPoll` block references `args` throughout (e.g., `args.to`, `args.question`, `args.options`), but the function parameter is named `params` (line 74). Every other action block in `handleTelegramAction` correctly uses `params`. This will cause a `ReferenceError` at runtime when the `sendPoll` action is invoked.
Additionally, `const accountId` on line 353 shadows the outer `accountId` (line 78), which is inconsistent with all other action blocks that reuse the outer-scope `accountId`.
All `args.*` references need to be changed to `params.*`, and the re-declaration of `accountId` should be removed in favor of the existing outer-scope variable. For example:
```suggestion
const to = params.to as string | undefined;
if (!to) {
throw new Error("sendPoll requires 'to' (chat ID)");
}
const question = (params.question ?? params.pollQuestion) as string | undefined;
if (!question) {
throw new Error("sendPoll requires 'question'");
}
const options = (params.options ?? params.pollOption) as string[] | undefined;
if (!options || options.length < 2) {
throw new Error("sendPoll requires at least 2 options");
}
const maxSelections =
typeof params.maxSelections === "number" ? params.maxSelections : undefined;
const isAnonymous =
typeof params.isAnonymous === "boolean" ? params.isAnonymous : undefined;
const silent = typeof params.silent === "boolean" ? params.silent : undefined;
const replyToMessageId =
typeof params.replyTo === "number" ? params.replyTo : undefined;
const messageThreadId =
typeof params.threadId === "number" ? params.threadId : undefined;
```
How can I resolve this? If you propose a fix, please make it concise.| return jsonResult({ ok: true, ...stats }); | ||
| } | ||
|
|
||
| if (action === "sendPoll") { |
There was a problem hiding this comment.
Missing action gate check for sendPoll
Every other action in this function checks isActionEnabled(...) before proceeding (e.g., sendMessage checks isActionEnabled("sendMessage"), deleteMessage checks isActionEnabled("deleteMessage"), sendSticker checks isActionEnabled("sticker", false)). The sendPoll block is missing this check. Without it, even if actions.poll: false is set in configuration, the action handler will still process sendPoll if called directly, bypassing the adapter's listActions gate.
Consider adding a gate check consistent with the other actions:
| if (action === "sendPoll") { | |
| if (action === "sendPoll") { | |
| if (!isActionEnabled("poll")) { | |
| throw new Error("Telegram poll is disabled via actions.poll."); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tools/telegram-actions.ts
Line: 331:331
Comment:
**Missing action gate check for `sendPoll`**
Every other action in this function checks `isActionEnabled(...)` before proceeding (e.g., `sendMessage` checks `isActionEnabled("sendMessage")`, `deleteMessage` checks `isActionEnabled("deleteMessage")`, `sendSticker` checks `isActionEnabled("sticker", false)`). The `sendPoll` block is missing this check. Without it, even if `actions.poll: false` is set in configuration, the action handler will still process `sendPoll` if called directly, bypassing the adapter's `listActions` gate.
Consider adding a gate check consistent with the other actions:
```suggestion
if (action === "sendPoll") {
if (!isActionEnabled("poll")) {
throw new Error("Telegram poll is disabled via actions.poll.");
}
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Pull request overview
This PR wires the existing sendPollTelegram function into the Telegram channel action adapter to enable poll creation via the unified action interface. The implementation adds poll support to the Telegram message actions handler, fixing issue #16977 where poll functionality was fully implemented but unreachable.
Changes:
- Added "poll" action to
telegramMessageActions.listActions()with action gate support - Implemented
handleAction("poll", ...)in the Telegram action adapter to extract poll parameters and route tohandleTelegramAction("sendPoll", ...) - Added
handleTelegramAction("sendPoll", ...)handler to validate poll parameters and callsendPollTelegram - Added unit test coverage for the poll action routing
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/channels/plugins/actions/telegram.ts | Adds poll action to listActions and implements handleAction("poll") to extract and route poll parameters |
| src/agents/tools/telegram-actions.ts | Implements sendPoll action handler to validate parameters and call sendPollTelegram |
| src/channels/plugins/actions/actions.test.ts | Adds test case verifying poll action routing with question and options |
| if (gate("poll")) { | ||
| actions.add("poll"); | ||
| } |
There was a problem hiding this comment.
The TelegramActionConfig type is missing a poll field, which means the action gate check will always use the default value. For consistency with Discord (polls?: boolean; in DiscordActionConfig) and WhatsApp (polls?: boolean; in WhatsAppActionConfig), the Telegram config should include a poll field.
Update src/config/types.telegram.ts to add:
export type TelegramActionConfig = {
reactions?: boolean;
sendMessage?: boolean;
deleteMessage?: boolean;
editMessage?: boolean;
sticker?: boolean;
poll?: boolean; // Add this line
};Note: Discord and WhatsApp use polls (plural), but since Telegram already uses singular forms for other actions (sticker not stickers, reaction not reactions), using poll (singular) is consistent with Telegram's existing convention.
| if (action === "poll") { | ||
| const to = readStringParam(params, "to", { required: true }); | ||
| const question = readStringParam(params, "pollQuestion") ?? readStringParam(params, "question", { required: true }); | ||
| const options = readStringArrayParam(params, "pollOption") ?? readStringArrayParam(params, "options"); |
There was a problem hiding this comment.
The options parameter should be marked as required when used as a fallback. Currently, if pollOption is undefined, the code reads options without the required: true flag, which could result in options being undefined. This would cause validation to fail later in handleTelegramAction, but it's better to fail early with a clear error message.
Consider updating line 208 to:
const options = readStringArrayParam(params, "pollOption") ?? readStringArrayParam(params, "options", { required: true });This matches the pattern used for the question parameter on line 207 and follows the principle of failing fast with clear error messages.
| const options = readStringArrayParam(params, "pollOption") ?? readStringArrayParam(params, "options"); | |
| const options = readStringArrayParam(params, "pollOption") ?? readStringArrayParam(params, "options", { required: true }); |
| if (action === "poll") { | ||
| const to = readStringParam(params, "to", { required: true }); | ||
| const question = readStringParam(params, "pollQuestion") ?? readStringParam(params, "question", { required: true }); | ||
| const options = readStringArrayParam(params, "pollOption") ?? readStringArrayParam(params, "options"); | ||
| const threadId = readStringParam(params, "threadId"); | ||
| const replyTo = readStringParam(params, "replyTo"); | ||
| const silent = typeof params.silent === "boolean" ? params.silent : undefined; | ||
| return await handleTelegramAction( | ||
| { | ||
| action: "sendPoll", | ||
| to, | ||
| question, | ||
| options, | ||
| replyTo: replyTo != null ? Number(replyTo) : undefined, | ||
| threadId: threadId != null ? Number(threadId) : undefined, | ||
| silent, | ||
| accountId: accountId ?? undefined, | ||
| }, | ||
| cfg, | ||
| ); | ||
| } |
There was a problem hiding this comment.
The poll action handler is missing support for Telegram-specific poll parameters that are available via CLI and low-level action calls:
pollMulti/allowMultiselect- to enable multiple selectionspollDurationSeconds- Telegram supports poll duration in seconds (5-600)pollAnonymous/pollPublic- to control poll anonymity (Telegram-specific)
These parameters are supported by the underlying handleTelegramAction("sendPoll", ...) handler (see lines 344-347 in telegram-actions.ts) and are used in the message-action-runner (lines 574-593), but they're not being extracted and passed through from the action adapter.
Consider adding these parameters to ensure feature parity:
const allowMultiselect = typeof params.pollMulti === "boolean" ? params.pollMulti : undefined;
const durationSeconds = readNumberParam(params, "pollDurationSeconds", { integer: true });
const pollAnonymous = typeof params.pollAnonymous === "boolean" ? params.pollAnonymous : undefined;
const pollPublic = typeof params.pollPublic === "boolean" ? params.pollPublic : undefined;
const isAnonymous = pollAnonymous ? true : pollPublic ? false : undefined;
const maxSelections = allowMultiselect && options ? Math.max(2, options.length) : 1;Then include these in the handleTelegramAction call: maxSelections, durationSeconds, isAnonymous.
| const res = await sendPollTelegram( | ||
| to, | ||
| { question, options, maxSelections }, | ||
| { | ||
| accountId: accountId || undefined, | ||
| replyToMessageId, | ||
| messageThreadId, | ||
| isAnonymous, | ||
| silent, | ||
| }, | ||
| ); |
There was a problem hiding this comment.
The sendPoll action handler is missing support for poll duration parameters. The PollInput type accepts both durationSeconds and durationHours, but the handler only extracts maxSelections (line 357).
To support poll duration for Telegram polls (which use durationSeconds with a range of 5-600 seconds), the handler should:
- Extract
durationSecondsfrom args:
const durationSeconds = typeof args.durationSeconds === "number" ? args.durationSeconds : undefined;- Pass it to the PollInput object:
{ question, options, maxSelections, durationSeconds }This would enable CLI commands like:
openclaw message poll --channel telegram --to <chat> --poll-question "Q" --poll-option "A" --poll-option "B" --poll-duration-seconds 300| if (action === "sendPoll") { | ||
| const to = args.to as string | undefined; | ||
| if (!to) { | ||
| throw new Error("sendPoll requires 'to' (chat ID)"); | ||
| } | ||
| const question = (args.question ?? args.pollQuestion) as string | undefined; | ||
| if (!question) { | ||
| throw new Error("sendPoll requires 'question'"); | ||
| } | ||
| const options = (args.options ?? args.pollOption) as string[] | undefined; | ||
| if (!options || options.length < 2) { | ||
| throw new Error("sendPoll requires at least 2 options"); | ||
| } | ||
| const maxSelections = | ||
| typeof args.maxSelections === "number" ? args.maxSelections : undefined; | ||
| const isAnonymous = | ||
| typeof args.isAnonymous === "boolean" ? args.isAnonymous : undefined; | ||
| const silent = typeof args.silent === "boolean" ? args.silent : undefined; | ||
| const replyToMessageId = | ||
| typeof args.replyTo === "number" ? args.replyTo : undefined; | ||
| const messageThreadId = | ||
| typeof args.threadId === "number" ? args.threadId : undefined; | ||
| const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined; | ||
|
|
||
| const res = await sendPollTelegram( | ||
| to, | ||
| { question, options, maxSelections }, | ||
| { | ||
| accountId: accountId || undefined, | ||
| replyToMessageId, | ||
| messageThreadId, | ||
| isAnonymous, | ||
| silent, | ||
| }, | ||
| ); | ||
| return jsonResult({ | ||
| ok: true, | ||
| messageId: res.messageId, | ||
| chatId: res.chatId, | ||
| pollId: res.pollId, | ||
| }); | ||
| } |
There was a problem hiding this comment.
The sendPoll action handler is using an undefined variable args instead of the function parameter params. This will cause a runtime error when the action is invoked.
All references to args in this block should be changed to params:
- Line 332:
args.to→params.to - Line 336:
args.question ?? args.pollQuestion→params.question ?? params.pollQuestion - Line 340:
args.options ?? args.pollOption→params.options ?? params.pollOption - Line 344:
args.maxSelections→params.maxSelections - Line 346:
args.isAnonymous→params.isAnonymous - Line 348:
args.silent→params.silent - Line 349:
args.replyTo→params.replyTo - Line 351:
args.threadId→params.threadId - Line 353:
args.accountId→params.accountId
This is consistent with all other action handlers in the same file, which use params.
…nclaw#16977) The Telegram channel adapter listed no 'poll' action, so agents could not create polls via the unified action interface. The underlying sendPollTelegram function was already implemented but unreachable. Changes: - telegram.ts: add 'poll' to listActions (enabled by default via gate), add handleAction branch that reads pollQuestion/pollOption params and delegates to handleTelegramAction with action 'sendPoll'. - telegram-actions.ts: add 'sendPoll' handler that validates question, options (≥2), and forwards to sendPollTelegram with threading, silent, and anonymous options. - actions.test.ts: add test verifying poll action routes correctly. Fixes openclaw#16977
|
Reverted on main as an accidental merge. Revert commit:
|
Summary
Wire the existing
sendPollTelegramfunction into the Telegram channel action adapter so agents can create polls via the unified action interface.Fixes #16977
Root Cause
sendPollTelegramwas fully implemented but unreachable — the Telegram action adapter had no"poll"action inlistActionsand no correspondinghandleActionbranch.Behavior Changes
telegramMessageActions.listActions()now includes"poll"(enabled by default, disable withactions.poll: false).handleAction("poll", ...)readspollQuestion/pollOption(orquestion/options) params and delegates tohandleTelegramAction("sendPoll", ...).handleTelegramAction("sendPoll", ...)validates question + ≥2 options and callssendPollTelegramwith threading, silent, and anonymous options.Tests
routes poll action to sendPoll with question and optionsin the consolidated action test suite.Sign-Off
Greptile Summary
This PR wires the existing
sendPollTelegramfunction into the Telegram channel action adapter so agents can create polls via the unified action interface. The adapter layer intelegram.tsis correctly implemented, but the handler intelegram-actions.tshas a critical runtime bug.telegram-actions.ts: The newsendPollblock references an undefined variableargsinstead of the function parameterparams. This will cause aReferenceErrorat runtime. Every other action block in the function correctly usesparams.sendPollhandler does not checkisActionEnabled("poll"), unlike every other action inhandleTelegramAction. This means the action could be executed even when disabled via config.handleTelegramAction, so it validates the adapter routing layer but does not catch theargsbug in the actual implementation. Consider adding an integration-level test or at minimum a unit test forhandleTelegramAction("sendPoll", ...)directly.Confidence Score: 1/5
sendPollhandler intelegram-actions.tsreferencesargsinstead ofparams, which is an undefined variable in the function scope. This is a guaranteedReferenceErrorwhenever the poll action is invoked. The test suite does not catch this because it mocks the affected function.src/agents/tools/telegram-actions.tsrequires immediate fixes — theargs→paramsrename and missing action gate check must be resolved before merging.Last reviewed commit: 0933758