Skip to content

fix(telegram): wire sendPollTelegram into channel action handler#18005

Merged
steipete merged 1 commit intoopenclaw:mainfrom
arosstale:fix/16977-telegram-poll-action-v2
Feb 16, 2026
Merged

fix(telegram): wire sendPollTelegram into channel action handler#18005
steipete merged 1 commit intoopenclaw:mainfrom
arosstale:fix/16977-telegram-poll-action-v2

Conversation

@arosstale
Copy link
Contributor

@arosstale arosstale commented Feb 16, 2026

Summary

Wire the existing sendPollTelegram function into the Telegram channel action adapter so agents can create polls via the unified action interface.

Fixes #16977

Root Cause

sendPollTelegram was fully implemented but unreachable — the Telegram action adapter had no "poll" action in listActions and no corresponding handleAction branch.

Behavior Changes

  • telegramMessageActions.listActions() now includes "poll" (enabled by default, disable with actions.poll: false).
  • handleAction("poll", ...) reads pollQuestion/pollOption (or question/options) params and delegates to handleTelegramAction("sendPoll", ...).
  • handleTelegramAction("sendPoll", ...) validates question + ≥2 options and calls sendPollTelegram with threading, silent, and anonymous options.

Tests

  • Added: routes poll action to sendPoll with question and options in the consolidated action test suite.

Sign-Off

  • Models used: Claude (Anthropic)
  • Submitter effort: AI-generated, human-reviewed
  • Keyword: lobster-biscuit

Greptile Summary

This PR wires the existing sendPollTelegram function into the Telegram channel action adapter so agents can create polls via the unified action interface. The adapter layer in telegram.ts is correctly implemented, but the handler in telegram-actions.ts has a critical runtime bug.

  • Critical bug in telegram-actions.ts: The new sendPoll block references an undefined variable args instead of the function parameter params. This will cause a ReferenceError at runtime. Every other action block in the function correctly uses params.
  • Missing action gate check: The sendPoll handler does not check isActionEnabled("poll"), unlike every other action in handleTelegramAction. This means the action could be executed even when disabled via config.
  • Test coverage gap: The test mocks handleTelegramAction, so it validates the adapter routing layer but does not catch the args bug in the actual implementation. Consider adding an integration-level test or at minimum a unit test for handleTelegramAction("sendPoll", ...) directly.

Confidence Score: 1/5

  • This PR will crash at runtime due to an undefined variable reference and should not be merged as-is.
  • The sendPoll handler in telegram-actions.ts references args instead of params, which is an undefined variable in the function scope. This is a guaranteed ReferenceError whenever the poll action is invoked. The test suite does not catch this because it mocks the affected function.
  • src/agents/tools/telegram-actions.ts requires immediate fixes — the argsparams rename and missing action gate check must be resolved before merging.

Last reviewed commit: 0933758

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +332 to +353
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
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") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to handleTelegramAction("sendPoll", ...)
  • Added handleTelegramAction("sendPoll", ...) handler to validate poll parameters and call sendPollTelegram
  • 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

Comment on lines +50 to +52
if (gate("poll")) {
actions.add("poll");
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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");
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
const options = readStringArrayParam(params, "pollOption") ?? readStringArrayParam(params, "options");
const options = readStringArrayParam(params, "pollOption") ?? readStringArrayParam(params, "options", { required: true });

Copilot uses AI. Check for mistakes.
Comment on lines +205 to +225
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,
);
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The poll action handler is missing support for Telegram-specific poll parameters that are available via CLI and low-level action calls:

  1. pollMulti / allowMultiselect - to enable multiple selections
  2. pollDurationSeconds - Telegram supports poll duration in seconds (5-600)
  3. 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.

Copilot uses AI. Check for mistakes.
Comment on lines +355 to +365
const res = await sendPollTelegram(
to,
{ question, options, maxSelections },
{
accountId: accountId || undefined,
replyToMessageId,
messageThreadId,
isAnonymous,
silent,
},
);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Extract durationSeconds from args:
const durationSeconds = typeof args.durationSeconds === "number" ? args.durationSeconds : undefined;
  1. 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

Copilot uses AI. Check for mistakes.
Comment on lines +331 to +372
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,
});
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.toparams.to
  • Line 336: args.question ?? args.pollQuestionparams.question ?? params.pollQuestion
  • Line 340: args.options ?? args.pollOptionparams.options ?? params.pollOption
  • Line 344: args.maxSelectionsparams.maxSelections
  • Line 346: args.isAnonymousparams.isAnonymous
  • Line 348: args.silentparams.silent
  • Line 349: args.replyToparams.replyTo
  • Line 351: args.threadIdparams.threadId
  • Line 353: args.accountIdparams.accountId

This is consistent with all other action handlers in the same file, which use params.

Copilot uses AI. Check for mistakes.
…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
@steipete steipete merged commit 7bb9a7d into openclaw:main Feb 16, 2026
23 checks passed
@ngutman
Copy link
Contributor

ngutman commented Feb 17, 2026

Reverted on main as an accidental merge.

Revert commit:

  • 92de4031ab84cccaec0f95f75046d25ad57dcf78

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram polls: sendPollTelegram exists but is not wired into action handler or outbound

4 participants