Skip to content

fix(telegram): suppress fallback reply when plugin command returns suppressReply: true - #80928

Merged
obviyus merged 5 commits into
openclaw:mainfrom
alexuser:fix/80756-suppress-plugin-command-reply
Jul 1, 2026
Merged

obviyus merged 5 commits into
openclaw:mainfrom
alexuser:fix/80756-suppress-plugin-command-reply

Conversation

@alexuser

@alexuser alexuser commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Plugin commands registered via api.registerCommand that send responses directly via Telegram Bot API (with custom retry logic, IPv4 forcing, etc.) currently trigger a spurious "No response generated. Please try again." fallback message from the Telegram channel adapter. This happens because the adapter's hasRenderableTelegramNativeReplyPayload check only looks for text/mediaUrl content, and when the handler returns undefined or {} after doing its own delivery, no content is found so the fallback fires.

This PR adds suppressReply?: boolean to PluginCommandResult so handlers can explicitly signal "I already took care of delivery — don't send a fallback."

Changes

  • src/plugins/types.ts: Add suppressReply?: boolean to PluginCommandResult with detailed JSDoc explaining when and why plugin authors should use it (for commands that deliver via channel-native APIs with custom transport guarantees)
  • extensions/telegram/src/bot-native-commands.ts: Add isSuppressedTelegramNativeReplyPayload check before the fallback path; clean up progress placeholder and return early when suppressed
  • extensions/telegram/src/bot-native-commands.test.ts: Regression test ensuring { suppressReply: true } produces no deliverReplies or editMessageTelegram calls

Fixes #80756

Real Behavior Proof

Behavior or issue addressed: Plugin commands that own their own transport delivery via Telegram Bot API produce a spurious "No response generated. Please try again." fallback because the handler returns no content and the adapter cannot distinguish "handled externally" from "no response".

Real environment tested: macOS 15.4, Node v26.0.0, local OpenClaw checkout on branch fix/80756-suppress-plugin-command-reply.

Exact steps or command run after this patch:

  1. Cloned openclaw/openclaw main, created branch fix/80756-suppress-plugin-command-reply
  2. Added suppressReply?: boolean to PluginCommandResult type in src/plugins/types.ts with JSDoc documenting the flag
  3. Added isSuppressedTelegramNativeReplyPayload helper and early-return guard in extensions/telegram/src/bot-native-commands.ts
  4. Added regression test in extensions/telegram/src/bot-native-commands.test.ts
  5. Ran the full test suite for the affected file

Evidence after fix: Terminal output from running the Telegram bot-native-commands test suite locally:

$ pnpm vitest run extensions/telegram/src/bot-native-commands.test.ts

 RUN  v4.1.6 /private/tmp/openclaw-main

 Test Files  1 passed (1)
      Tests  19 passed (19)
   Start at  23:37:51
   Duration  3.49s (transform 1.29s, setup 90ms, import 3.09s, tests 218ms, environment 0ms)

All 19 tests passed, including the new regression test for suppressReply: true.

Observed result after fix:

  • Plugin command handlers returning { suppressReply: true } no longer trigger the EMPTY_RESPONSE_FALLBACK path in Telegram native commands.
  • The progress placeholder is cleaned up correctly before the early return.
  • Existing behavior for handlers returning { text: "..." }, {}, or undefined is unchanged.
  • Commit eb3bf1a0 pushed to fix/80756-suppress-plugin-command-reply with the JSDoc update addressing the ClawSweeper P3 finding.

What was not tested: No live Telegram Bot API integration or actual message delivery was exercised; only the unit test harness behavior was validated. The fix is a pure TypeScript type + control-flow change with no runtime dependency on external APIs.

Maintainer notes

ClawSweeper review flagged the need for live Telegram proof. This change is a pure SDK API addition + Telegram adapter control-flow fix. The regression test covers the exact code path. If a maintainer believes the test coverage is sufficient, applying @clawsweeper re-review or the proof: override label would allow this to proceed.

@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: XS proof: supplied External PR includes structured after-fix real behavior proof. labels May 12, 2026
@clawsweeper

clawsweeper Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Codex review: found issues before merge. Reviewed June 30, 2026, 8:18 PM ET / 00:18 UTC.

Summary
Adds a public suppressReply?: boolean command result flag, Telegram native-command fallback suppression, SDK docs wording, and a Telegram regression test.

PR surface: Source +28, Tests +11, Docs 0. Total +39 across 4 files.

Reproducibility: yes. at source level. Current main normalizes undefined command results to {} and Telegram replaces non-renderable results with EMPTY_RESPONSE_FALLBACK; Mantis screenshots also show the baseline duplicate fallback and candidate suppression.

Review metrics: 1 noteworthy metric.

  • Public Plugin SDK result fields: 1 added. The PR adds suppressReply to PluginCommandResult, which external plugin authors may rely on after release.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #80756
Summary: This PR is the open candidate fix for the canonical Telegram plugin-command fallback report; a closed duplicate PR attempted the same behavior, and an older merged fallback PR is adjacent history.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Get maintainer acceptance for suppressReply as a public command-result field, or narrow the signal to a Telegram-owned contract.
  • Resolve the current main conflict and rerun checks against the final merge result.

Risk before merge

  • [P1] Merging the current API shape would publish a PluginCommandResult.suppressReply field whose documented behavior is Telegram-only and not honored by the sibling Discord native command path.
  • [P1] GitHub currently reports the PR branch as dirty/conflicting against main, so maintainers need a resolved merge result and fresh checks before any landing decision.

Maintainer options:

  1. Choose the SDK contract before merge (recommended)
    A maintainer should explicitly accept suppressReply as a public command-result field or request a narrower Telegram-owned signal before the conflicted branch is refreshed.
  2. Keep suppression Telegram-owned
    Rework the suppression signal into Telegram-owned channel data or another Telegram-specific command-delivery contract if maintainers do not want a global Plugin SDK field.
  3. Pause if the API direction is unwanted
    If plugin-owned direct Telegram delivery should not get a first-class command result, pause or close this PR and resolve the linked issue through the preferred transport seam.

Next step before merge

  • [P2] Manual review is needed because automation should not choose the public Plugin SDK contract shape or resolve the current conflict without maintainer API direction.

Security
Cleared: The diff only changes a typed result field, Telegram control flow, docs, and tests; no dependency, workflow, permission, secret-handling, or supply-chain risk was found.

Review findings

  • [P1] Get maintainer sign-off on the SDK return field — src/plugins/types.ts:2023
Review details

Best possible solution:

Choose the permanent suppression contract, refresh the branch against current main, and land the Telegram fallback fix with matching SDK/channel semantics, docs, and regression coverage.

Do we have a high-confidence way to reproduce the issue?

Yes at source level. Current main normalizes undefined command results to {} and Telegram replaces non-renderable results with EMPTY_RESPONSE_FALLBACK; Mantis screenshots also show the baseline duplicate fallback and candidate suppression.

Is this the best way to solve the issue?

Unclear as submitted. The Telegram early return fixes the symptom, but adding Telegram-only semantics to the public PluginCommandResult type needs maintainer acceptance or a narrower channel-owned contract before this is clearly the best fix.

Full review comments:

  • [P1] Get maintainer sign-off on the SDK return field — src/plugins/types.ts:2023
    This adds suppressReply to the public PluginCommandResult type even though the docs describe the behavior as Telegram-only and Discord native commands still warn on empty replies. Please get explicit maintainer acceptance for a cross-channel SDK contract, or keep the signal in a Telegram-owned result/channel-data shape so external plugins do not learn a global field only one adapter honors.
    Confidence: 0.88

Overall correctness: patch is incorrect
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 5e572dcf781a.

Label changes

Label justifications:

  • P2: This is a focused Telegram plugin-command fallback fix with limited blast radius, but it touches a public Plugin SDK contract.
  • merge-risk: 🚨 compatibility: The PR would publish a new PluginCommandResult field whose Telegram-only semantics need maintainer acceptance or narrowing before release.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (screenshot): Mantis supplied native Telegram Desktop before/after screenshots, and the inspected images show the baseline fallback and the candidate command without a following fallback.
  • proof: sufficient: Contributor real behavior proof is sufficient. Mantis supplied native Telegram Desktop before/after screenshots, and the inspected images show the baseline fallback and the candidate command without a following fallback.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. Mantis supplied native Telegram Desktop before/after screenshots, and the inspected images show the baseline fallback and the candidate command without a following fallback.
  • mantis: telegram-visible-proof: Mantis should capture Telegram visible proof. The PR changes visible Telegram chat behavior by removing an extra fallback reply after plugin-owned command delivery.
Evidence reviewed

PR surface:

Source +28, Tests +11, Docs 0. Total +39 across 4 files.

View PR surface stats
Area Files Added Removed Net
Source 2 29 1 +28
Tests 1 11 0 +11
Docs 1 15 15 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 4 55 16 +39

What I checked:

  • Current main lacks a suppression result: Current main still defines PluginCommandResult as ReplyPayload & { continueAgent?: boolean }, so plugin command handlers have no first-class handled-delivery or suppress-fallback signal. (src/plugins/types.ts:2027, 5e572dcf781a)
  • Plugin command results are public SDK surface: PluginCommandResult is re-exported through the plugin SDK entrypoint, so adding a field becomes an external plugin author contract. (src/plugin-sdk/plugin-entry.ts:143, 5e572dcf781a)
  • Current main normalizes undefined results to empty objects: executePluginCommand returns {} when a handler returns undefined or another non-object result, which then reaches channel renderability checks without any handled-delivery signal. (src/plugins/commands.ts:418, 5e572dcf781a)
  • Current main Telegram fallback branch remains: Telegram native plugin command results without renderable content still fall back to EMPTY_RESPONSE_FALLBACK. (extensions/telegram/src/bot-native-commands.ts:1670, 5e572dcf781a)
  • Latest release has not shipped this fix: v2026.6.11 still has the same PluginCommandResult shape and Telegram fallback branch, so neither current main nor the latest release already solves this PR's central behavior. (src/plugins/types.ts:2027, e085fa1a3ffd)
  • PR adds the public SDK field: The PR adds suppressReply?: boolean directly to PluginCommandResult. (src/plugins/types.ts:2023, d0fb6b27d7cf)

Likely related people:

  • Glucksberg: Commit 4ee808dbcb74 introduced api.registerCommand, PluginCommandResult, and the command execution path this PR extends. (role: introduced plugin command API; confidence: high; commits: 4ee808dbcb74; files: src/plugins/types.ts, src/plugins/commands.ts)
  • obviyus: The merged Telegram empty-response fallback work in fix(telegram): empty-response fallback #3796 is adjacent history for the fallback branch this PR changes. (role: adjacent fallback author; confidence: medium; commits: 718bc3f9c818; files: extensions/telegram/src/bot-native-commands.ts)
  • jalehman: Recent merged work touched Telegram native command session metadata and progress-placeholder behavior that this PR must preserve. (role: recent adjacent contributor; confidence: high; commits: 7e2b2d2987b2, ed8d5b379791; files: extensions/telegram/src/bot-native-commands.ts, src/plugins/commands.ts, src/plugins/types.ts)
  • steipete: Recent plugin command auth binding and command registration refactors are near the SDK and native command surfaces affected by this PR. (role: recent SDK and command-surface contributor; confidence: medium; commits: b9f6abf5e845, 3ca7922dfe07; files: src/plugins/commands.ts, src/plugins/types.ts, extensions/telegram/src/bot-native-commands.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added the mantis: telegram-visible-proof Mantis should capture Telegram visible proof. label May 12, 2026
@alexuser
alexuser force-pushed the fix/80756-suppress-plugin-command-reply branch from c0782ef to eb3bf1a Compare May 12, 2026 06:54
@alexuser

alexuser commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

Updates addressing ClawSweeper review

The initial ClawSweeper review flagged two P3 items:

  1. Document the new command result flag — ✅ Fixed in commit eb3bf1a0. Added detailed JSDoc to suppressReply?: boolean on PluginCommandResult explaining when plugin authors should use it (for commands that deliver via channel-native APIs with custom transport guarantees like retry logic, IPv4 forcing, chunked messages, etc.).

  2. Keep the direct-message binding regression test — ✅ Already present at bot-native-commands.test.ts:637 (not modified by this PR).

Regarding the live Telegram proof request: this is a pure SDK API addition (suppressReply?: boolean) plus a Telegram adapter control-flow change. The regression test exercises the exact code path where a plugin command returns { suppressReply: true } and verifies no fallback is sent. No external API calls are involved in the fix itself.

If a maintainer is satisfied with the test coverage and the documented API contract, could you please comment @clawsweeper re-review so the gate can proceed? Alternatively, applying the proof: override label would also allow merge.

Re-review progress:

@obviyus

obviyus commented May 14, 2026

Copy link
Copy Markdown
Contributor

@openclaw-mantis verify this PR with native Telegram Desktop proof. Please show the plugin-command path where the command sends its own Telegram response directly: on main the bad fallback No response generated. Please try again. should appear after the intended response, and on this PR only the intended response should appear.

@openclaw-mantis

openclaw-mantis Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Mantis Telegram Desktop Proof

Summary: Mantis captured native Telegram Desktop before/after GIF evidence for the Telegram command fallback behavior.

Main screenshot This PR screenshot
Baseline native Telegram Desktop screenshot Candidate native Telegram Desktop screenshot
Main This PR
Baseline native Telegram Desktop proof GIF Candidate native Telegram Desktop proof GIF

Motion-trimmed clips:

Raw QA files: https://artifacts.openclaw.ai/mantis/telegram-desktop/pr-80928/run-27109011676-1/index.json

@RomneyDa

Copy link
Copy Markdown
Member

Heads up: this PR needs to be updated against current main before the new required Dependency Guard check can pass.

@openclaw-barnacle openclaw-barnacle Bot added the docs Improvements or additions to documentation label May 30, 2026
@alexuser
alexuser force-pushed the fix/80756-suppress-plugin-command-reply branch from 1dd7d53 to 7a94e48 Compare May 30, 2026 04:03
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 30, 2026
@alexuser

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

This PR has been rebased against current to satisfy the Dependency Guard check, and the ClawSweeper P3 SDK docs feedback has been addressed:

  • Added a docs note in clarifying that is Telegram-only (skips the fallback when the command delivers its own reply).
  • Ran the docs formatter so passes cleanly.

CI is green with no failing checks. Ready for another pass.

@clawsweeper

clawsweeper Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@alexuser

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

This PR has been rebased against current main to satisfy the Dependency Guard check, and the ClawSweeper P3 SDK docs feedback has been addressed:

  • Added a docs note in docs/plugins/sdk-overview.md clarifying that suppressReply is Telegram-only (skips the fallback when the command delivers its own reply).
  • Ran the docs formatter so check-docs passes cleanly.

CI is green with no failing checks. Ready for another pass.

@clawsweeper

clawsweeper Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: 🎥 video Contributor real behavior proof includes video or recording evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels May 30, 2026
@clawsweeper clawsweeper Bot added proof: 🎥 video Contributor real behavior proof includes video or recording evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. and removed proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. proof: 🎥 video Contributor real behavior proof includes video or recording evidence. labels Jun 23, 2026
@obviyus obviyus self-assigned this Jul 1, 2026
@obviyus
obviyus force-pushed the fix/80756-suppress-plugin-command-reply branch from d0fb6b2 to e6adb82 Compare July 1, 2026 16:02
@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord size: S and removed size: XS labels Jul 1, 2026
alexuser and others added 5 commits July 1, 2026 09:08
…ppressReply: true

Adds  to the  type so plugin
commands that handle their own transport delivery (e.g. via Telegram Bot API
directly with retry logic, IPv4 forcing, etc.) can signal the channel adapter
to skip the fallback reply.

When a plugin command handler returns , the Telegram
native command dispatcher now:
1. Cleans up any progress placeholder
2. Returns early without sending the "No response generated. Please try again." fallback

Includes detailed JSDoc for the new  flag explaining its use
for plugin commands that deliver their own responses via channel-native APIs.

Fixes openclaw#80756
…ion test

ClawSweeper review flagged that the new suppressReply test replaced the
existing direct-message binding regression test instead of being added
beside it. Restore the DM binding test to maintain coverage for the
plugin command dispatch surface.
@obviyus
obviyus force-pushed the fix/80756-suppress-plugin-command-reply branch from e6adb82 to 1ec0b29 Compare July 1, 2026 16:09
@obviyus
obviyus merged commit a5e11c0 into openclaw:main Jul 1, 2026
41 checks passed
@obviyus

obviyus commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Landed via rebase onto main.

  • Scoped tests: OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs extensions/telegram/src/bot-native-commands.test.ts extensions/discord/src/monitor/native-command.plugin-dispatch.test.ts
  • Review: .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
  • Changelog: not edited; release generation owns CHANGELOG.md.
  • Land commit: 1ec0b29e462070ac559c08ddb08c3960e0703ffe
  • Merge commit: a5e11c0083c5eed2c14b999a5736047c8d4abbf6

Thanks @alexuser!

@alexuser

alexuser commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and narrowed the scope to the genuinely additive improvements:

  1. Expanded JSDoc for suppressReply — the upstream one-liner didn't explain when or why a plugin author should use it. The new docs describe the self-delivery use case and explicitly note that both Telegram and Discord native command dispatchers honor the flag.

  2. Added PluginCommandSuppressReply — a narrow result shape that plugin handlers can return when they intentionally deliver their own response via channel-native APIs. This gives authors a type-safe helper instead of repeating the ad-hoc { suppressReply: true } literal.

The Telegram and Discord behavior changes, plus the existing tests, were already merged upstream in #99647 and related commits, so those have been removed from this branch to avoid redundancy.

ClawSweeper's concern about the SDK contract shape is now addressed: the flag is documented as a cross-channel signal (not Telegram-only), and the new helper type makes the contract explicit for plugin authors.

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

Labels

channel: discord Channel integration: discord channel: telegram Channel integration: telegram docs Improvements or additions to documentation mantis: telegram-visible-proof Mantis should capture Telegram visible proof. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: S status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: registerCommand handler: no way to suppress "No response generated" when plugin owns its own transport send

3 participants