Skip to content

fix: add interactive action registry for button/select callbacks#2108

Merged
MervinPraison merged 3 commits into
mainfrom
claude/issue-2104-20260620-0912
Jun 22, 2026
Merged

fix: add interactive action registry for button/select callbacks#2108
MervinPraison merged 3 commits into
mainfrom
claude/issue-2104-20260620-0912

Conversation

@praisonai-triage-agent

@praisonai-triage-agent praisonai-triage-agent Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #2104

Summary

This PR implements a symmetric inbound dispatch system for interactive UI elements (buttons/selects) that were previously rendering correctly but not handling callbacks except for pairing cases.

Changes

  • Core SDK (praisonaiagents/bots/):

    • Added interactive.py with InteractiveRegistry, InteractiveContext, and dispatch protocol
    • Added encode_action() and decode_callback() functions for typed action handling
    • Exported new interactive components from bots/init.py
  • Channel Adapters (praisonai/bots/):

    • Updated Telegram adapter to use interactive registry for callback queries
    • Updated Discord adapter to use interactive registry for button interactions
    • Updated Slack adapter to use interactive registry for block actions
    • Each adapter now registers handlers for 'command' and 'pair' namespaces

Design

The solution follows protocol-driven design principles:

  • Core protocol defined once in SDK (interactive.py)
  • Heavy implementations in wrapper (adapter-specific handlers)
  • Maintains backward compatibility with existing pairing callbacks
  • Registry pattern allows extensibility for new callback types

Testing

  • Verified imports work correctly
  • Tested decode/encode functions with various callback formats
  • Existing pairing callbacks continue to work through new system

Summary by CodeRabbit

  • New Features
    • Added interactive callback handling system for messaging bots across Discord, Slack, and Telegram, enabling button-based interactions.
    • Implemented unified callback routing supporting command execution and pairing operations through a platform-agnostic registry.

#2104)

- Created InteractiveRegistry and dispatch protocol in core SDK
- Added encode_action/decode_callback for typed action handling
- Updated Telegram, Discord, and Slack adapters to use registry
- Registered handlers for 'command' and 'pair' namespaces
- Maintains backward compatibility with existing pairing callbacks

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner

@coderabbitai review

@MervinPraison

Copy link
Copy Markdown
Owner

/review

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: edbd8a7c-49e9-4466-8855-68ccd26a7997

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds interactive.py to the core bots package, defining InteractiveContext, encode_action, decode_callback, and InteractiveRegistry with a global singleton. All three platform adapters (Discord, Slack, Telegram) are updated to register namespace handlers at init time and route button/action callbacks through the registry instead of early-returning on non-pairing callbacks.

Changes

Interactive Callback Dispatch System

Layer / File(s) Summary
InteractiveContext, encode/decode, and InteractiveRegistry
src/praisonai-agents/praisonaiagents/bots/interactive.py, src/praisonai-agents/praisonaiagents/bots/__init__.py
Introduces InteractiveContext dataclass, InteractiveHandler type alias, encode_action() / decode_callback() serialization functions, and InteractiveRegistry (register/unregister/dispatch/fallback/introspection) with a global singleton and convenience wrappers. All symbols are added to __all__.
Discord adapter integration
src/praisonai/praisonai/bots/discord.py
DiscordBot.__init__ now calls _register_interactive_handlers(), which registers "command" and "pair" namespace handlers. _handle_pairing_interaction is rewritten to defer, build InteractiveContext, and route through the registry with a legacy pair: fallback.
Slack adapter integration
src/praisonai/praisonai/bots/slack.py
Adds re import and _register_interactive_handlers() call in __init__. Replaces the narrow pair_approve/pair_deny handler with a catch-all regex action handler that dispatches via the registry. _register_interactive_handlers registers "command" and "pair" handlers that update Slack message blocks.
Telegram adapter integration
src/praisonai/praisonai/bots/telegram.py
TelegramBot.__init__ calls _register_interactive_handlers(), registering a "command" handler (with permission policy enforcement and sync/async dispatch) and a "pair" handler. handle_callback_query is rewritten to build InteractiveContext and dispatch through the registry, falling back to legacy pair: handling only when unhandled.

Sequence Diagram

sequenceDiagram
  participant User as User (taps button)
  participant Adapter as Platform Adapter
  participant InteractiveContext
  participant InteractiveRegistry
  participant NSHandler as Namespace Handler

  User->>Adapter: button callback (raw callback_data)
  Adapter->>InteractiveContext: build(callback_data, user_id, platform_data)
  Adapter->>InteractiveRegistry: dispatch(ctx)
  InteractiveRegistry->>InteractiveRegistry: decode_callback(callback_data) → (namespace, payload)
  InteractiveRegistry->>InteractiveRegistry: inject namespace/payload into ctx.platform_data
  InteractiveRegistry->>NSHandler: await handler(ctx)
  NSHandler-->>Adapter: result / edited message
  alt registry did not handle
    Adapter->>Adapter: legacy pair: fallback
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • MervinPraison/PraisonAI#2034: Introduced portable PresentationAction types and the approval rendering system that this PR extends by adding the symmetric inbound dispatch registry.

Suggested reviewers

  • MervinPraison

Poem

🐇 Hop hop, the buttons now reply,
No more clicks that vanish in the sky!
encode_action wraps the payload tight,
decode_callback brings it back to light,
The registry routes each tap with care —
Interactive bots beyond compare! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: add interactive action registry for button/select callbacks' directly addresses the main change: implementing a registry system for handling interactive button and select callbacks across all bot adapters.
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #2104: adds InteractiveRegistry, encode_action/decode_callback in core SDK, updates all three channel adapters (Telegram, Discord, Slack) to use the registry with command and pair handlers, and maintains backward compatibility with pairing callbacks.
Out of Scope Changes check ✅ Passed All changes are directly in scope: core interactive.py module, bots/init.py exports, and channel adapter implementations (telegram.py, discord.py, slack.py) integrating the registry. No extraneous modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 86.11% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-2104-20260620-0912

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MervinPraison

Copy link
Copy Markdown
Owner

@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first — incorporate their findings.

Review areas:

  1. Bloat check: Are changes minimal and focused? Any unnecessary code or scope creep?
  2. Security: Any hardcoded secrets, unsafe eval/exec, missing input validation?
  3. Performance: Any module-level heavy imports? Hot-path regressions?
  4. Tests: Are tests included? Do they cover the changes adequately?
  5. Backward compat: Any public API changes without deprecation?
  6. Code quality: DRY violations, naming conventions, error handling?
  7. Address reviewer feedback: If Qodo, Coderabbit, or Gemini flagged valid issues, include them in your review
  8. Suggest specific improvements with code examples where possible

@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a symmetric inbound dispatch system for interactive UI callbacks (buttons/selects) across Discord, Slack, and Telegram adapters, complementing the existing presentation rendering layer with a new InteractiveRegistry/InteractiveContext protocol in praisonaiagents/bots/interactive.py.

  • New interactive.py: Defines InteractiveRegistry, encode_action/decode_callback helpers, and a create_registry() factory; the global registry is marked deprecated in favour of per-adapter instances.
  • Three adapter updates: Each bot now creates its own registry in __init__, registers "command" and "pair" namespace handlers via _register_interactive_handlers(), and dispatches every incoming callback through the registry before falling back to legacy paths.
  • Slack catch-all action handler: Replaces the two specific pair_approve/pair_deny action decorators with a single re.compile(".*") wildcard to route all block actions through the registry.

Confidence Score: 3/5

The callback dispatch path has null-dereference bugs in the new command-button handlers for Discord and Telegram that silently swallow user feedback on every button-triggered command when the message object is unavailable.

Both the Discord and Telegram command handlers access .content / .text on a message object that the platform may set to None inside f-string arguments evaluated before the API call. The AttributeError is caught by dispatch()'s broad except block, the handler returns nothing, the fallback path does not match (it only handles pair: prefixes), and the user receives no acknowledgement.

discord.py (handle_command_callback success and error branches) and telegram.py (handle_command_callback permission-denied, success, and error branches) both need null guards before accessing the message object.

Important Files Changed

Filename Overview
src/praisonai-agents/praisonaiagents/bots/interactive.py New dispatch core: dispatch() falls through to fallback when handler returns None (already flagged), encode_action() silently ignores the namespace parameter for COMMAND-type actions, and the global registry is rightly deprecated but still exported.
src/praisonai/praisonai/bots/discord.py New _register_interactive_handlers and updated _handle_pairing_interaction; both the success and error paths in handle_command_callback dereference interaction.message.content without a None guard, causing AttributeError when message is unavailable.
src/praisonai/praisonai/bots/slack.py Catch-all regex action handler routes all block actions through the registry; fallback logic is clean and the command handler uses safe dict access; no new critical issues.
src/praisonai/praisonai/bots/telegram.py New handle_command_callback accesses query.message.text in three f-string expressions without guarding against query.message being None, raising AttributeError before edit_message_text even executes.
src/praisonai-agents/praisonaiagents/bots/init.py Cleans up the duplicate TYPE_CHECKING guard (replaced with pass) and adds the new interactive exports; straightforward plumbing change.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant U as User
    participant P as Platform (TG/Discord/Slack)
    participant A as Bot Adapter
    participant R as InteractiveRegistry
    participant H as Namespace Handler
    participant F as Fallback Path

    U->>P: Click button / select
    P->>A: Callback event (pair:CODE / cmd:foo)
    A->>A: Build InteractiveContext
    A->>R: dispatch(ctx)
    R->>R: decode_callback(ctx.callback_data)
    note over R: pair:... namespace=pair, cmd:... namespace=command
    R->>H: handler(ctx)
    alt handler returns truthy
        H-->>R: Command foo executed
        R-->>A: True (handled)
    else handler returns None or raises
        H-->>R: None
        R->>R: try fallback_handler
        R-->>A: False (not handled)
        A->>F: legacy pair: check
        F->>A: handle_approval_callback()
    end
    A->>P: edit_original_response / edit_message_text
    P-->>U: Updated message
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant U as User
    participant P as Platform (TG/Discord/Slack)
    participant A as Bot Adapter
    participant R as InteractiveRegistry
    participant H as Namespace Handler
    participant F as Fallback Path

    U->>P: Click button / select
    P->>A: Callback event (pair:CODE / cmd:foo)
    A->>A: Build InteractiveContext
    A->>R: dispatch(ctx)
    R->>R: decode_callback(ctx.callback_data)
    note over R: pair:... namespace=pair, cmd:... namespace=command
    R->>H: handler(ctx)
    alt handler returns truthy
        H-->>R: Command foo executed
        R-->>A: True (handled)
    else handler returns None or raises
        H-->>R: None
        R->>R: try fallback_handler
        R-->>A: False (not handled)
        A->>F: legacy pair: check
        F->>A: handle_approval_callback()
    end
    A->>P: edit_original_response / edit_message_text
    P-->>U: Updated message
Loading

Reviews (3): Last reviewed commit: "fix: remove duplicate BotOSProtocol impo..." | Re-trigger Greptile

Comment thread src/praisonai-agents/praisonaiagents/bots/interactive.py
Comment thread src/praisonai-agents/praisonaiagents/bots/interactive.py Outdated
Comment thread src/praisonai-agents/praisonaiagents/bots/interactive.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/praisonai/praisonai/bots/slack.py (1)

882-887: 💤 Low value

Consider adding error handling around chat_update calls in handlers.

Both the command and pair handlers call self._client.chat_update() without explicit error handling. If the message was deleted or the API call fails, the exception propagates to dispatch() which catches it but loses context about which operation failed.

This is a minor concern since dispatch() does catch and log exceptions, but explicit handling would provide better diagnostics.

💡 Optional: Add error handling for better diagnostics
                         await self._client.chat_update(
                             channel=body["channel"]["id"],
                             ts=body["message"]["ts"],
                             blocks=blocks,
                             text=body["message"]["text"]
                         )
+                except Exception as e:
+                    logger.error(f"Failed to update message after command execution: {e}")
                     return f"Command {cmd_name} executed"

Also applies to: 925-930

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/bots/slack.py` around lines 882 - 887, The
chat_update calls made via self._client.chat_update() in both the command and
pair handlers lack explicit error handling, causing exceptions to propagate to
the dispatch() method where they are caught but lose context about which
specific operation failed. Wrap each chat_update call in a try-except block to
catch any exceptions and log them with context-specific information that
identifies which handler and operation failed, such as including details about
the channel, message timestamp, or handler type in the error message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/praisonai/praisonai/bots/discord.py`:
- Around line 732-740: The BotMessage instantiation is passing three invalid
keyword arguments (chat_id, command, and command_args) that are not defined
fields in the BotMessage dataclass from praisonaiagents.bots.protocols. Remove
these three invalid parameters from the BotMessage constructor call. If this
data needs to be preserved for later use, store it in the metadata field
instead, which is the appropriate parameter for holding additional contextual
information.

In `@src/praisonai/praisonai/bots/telegram.py`:
- Around line 279-284: The special case for `cmd_name == "approve"` in the
command routing logic only logs and returns without performing any actual work,
meaning any registered approval command handlers in `_command_handlers` will
never be invoked for this command. Either remove this conditional block entirely
if approval is handled through the `pair` namespace handler, or replace the
no-op logging with actual routing logic that invokes the appropriate approval
system handler, or add a detailed comment explaining why this specific case
needs special handling that differs from normal command processing.

---

Nitpick comments:
In `@src/praisonai/praisonai/bots/slack.py`:
- Around line 882-887: The chat_update calls made via self._client.chat_update()
in both the command and pair handlers lack explicit error handling, causing
exceptions to propagate to the dispatch() method where they are caught but lose
context about which specific operation failed. Wrap each chat_update call in a
try-except block to catch any exceptions and log them with context-specific
information that identifies which handler and operation failed, such as
including details about the channel, message timestamp, or handler type in the
error message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e4eced2e-843c-4dff-8ed3-8bd79d8904aa

📥 Commits

Reviewing files that changed from the base of the PR and between 5f39d66 and 6e1ff0d.

📒 Files selected for processing (5)
  • src/praisonai-agents/praisonaiagents/bots/__init__.py
  • src/praisonai-agents/praisonaiagents/bots/interactive.py
  • src/praisonai/praisonai/bots/discord.py
  • src/praisonai/praisonai/bots/slack.py
  • src/praisonai/praisonai/bots/telegram.py

Comment thread src/praisonai/praisonai/bots/discord.py Outdated
Comment on lines +732 to +740
from praisonaiagents.bots import BotMessage, BotUser
message = BotMessage(
message_id=str(interaction.message.id) if interaction.message else "",
content=f"/{command}",
sender=BotUser(user_id=ctx.user_id),
chat_id=str(interaction.channel_id) if hasattr(interaction, "channel_id") else "",
command=cmd_name,
command_args=cmd_args
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for BotMessage class definition to verify it has chat_id and command_args fields
ast-grep --pattern $'class BotMessage:
  $$$
'

# Also search with dataclass decorator
ast-grep --pattern $'`@dataclass`
class BotMessage:
  $$$
'

# Fallback: grep for field definitions
rg -n 'chat_id|command_args' --type py -g '**/protocols.py' -g '**/bots/*.py' -A2 -B2

Repository: MervinPraison/PraisonAI

Length of output: 50379


Remove invalid chat_id, command, and command_args keyword arguments from BotMessage instantiation.

The BotMessage dataclass at src/praisonai-agents/praisonaiagents/bots/protocols.py only accepts these fields: message_id, content, message_type, sender, channel, timestamp, reply_to, thread_id, attachments, and metadata. The instantiation at lines 732–740 passes chat_id, command, and command_args, which are not valid constructor parameters. This will raise a TypeError at runtime. Remove these invalid kwargs or store them in metadata if they need to be preserved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/bots/discord.py` around lines 732 - 740, The
BotMessage instantiation is passing three invalid keyword arguments (chat_id,
command, and command_args) that are not defined fields in the BotMessage
dataclass from praisonaiagents.bots.protocols. Remove these three invalid
parameters from the BotMessage constructor call. If this data needs to be
preserved for later use, store it in the metadata field instead, which is the
appropriate parameter for holding additional contextual information.

Comment on lines +279 to +284
if cmd_name == "approve":
# Handle approval command (from approval buttons)
# This is a special case that should be handled by the approval system
# For now, log it
logger.info(f"Approval command via button: {command}")
return "Approval handled"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify the purpose of the approve command special case or remove dead code.

The special case for cmd_name == "approve" just logs and returns without performing any action. If approval handling is done elsewhere (e.g., through the pair namespace handler), this code path never does useful work. If there's a registered approve command handler in _command_handlers, it will never be invoked due to this early return.

Consider either:

  1. Removing this special case if approval is handled via the pair namespace
  2. Adding a comment explaining why this no-op exists
  3. Actually routing to the approval system if that's the intent
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/bots/telegram.py` around lines 279 - 284, The special
case for `cmd_name == "approve"` in the command routing logic only logs and
returns without performing any actual work, meaning any registered approval
command handlers in `_command_handlers` will never be invoked for this command.
Either remove this conditional block entirely if approval is handled through the
`pair` namespace handler, or replace the no-op logging with actual routing logic
that invokes the appropriate approval system handler, or add a detailed comment
explaining why this specific case needs special handling that differs from
normal command processing.

@MervinPraison

Copy link
Copy Markdown
Owner

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions

Phase 2: FIX Valid Issues
4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
5. Push all code fixes directly to THIS branch (do NOT create a new PR)
6. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
7. If all issues are resolved, approve the PR / close the Issue
8. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


✅ Final Architecture Review Complete - PR #2108

All critical issues have been fixed and pushed to the branch.

Todo List:

  • Read AGENTS.md guidelines
  • Review architecture compliance
  • Fix P1: dispatch() return value issue
  • Fix P1: global registry singleton issue
  • Fix: Invalid BotMessage constructor arguments
  • Clean up redundant imports
  • Clean up dead code
  • Test changes
  • Provide final verdict

Fixes Implemented

1. Fixed dispatch() return value (P1)

Issue: dispatch() always returned True when a handler was found, even if handler returned None
Fix: Now properly returns False when handler returns None, allowing fallback paths to work

2. Fixed global registry singleton (P1)

Issue: Multiple adapters shared the same global registry, causing handler conflicts
Fix:

  • Added create_registry() function for adapter-specific instances
  • Each adapter now creates its own registry in __init__
  • Kept backward compatibility with deprecated global functions

3. Fixed invalid BotMessage constructor

Issue: Discord, Slack, and Telegram passed invalid chat_id, command, command_args parameters
Fix:

  • Use BotChannel with channel_id instead of chat_id
  • Move command and command_args to metadata dict

4. Cleaned up redundant imports

Issue: Interactive symbols imported twice in __init__.py
Fix: Removed duplicate TYPE_CHECKING imports, kept only runtime imports

5. Removed dead code

Issue: Special case for approve command in Telegram did nothing
Fix: Removed the dead code block

6. Fixed dataclass field default

Issue: Using = None for mutable field was non-idiomatic
Fix: Changed to field(default_factory=dict) for proper defaults

Architecture Compliance

Protocol-driven design: Core SDK has minimal protocol definitions, heavy implementations in wrapper
Backward compatibility: All existing functionality preserved, deprecated functions still work
Performance: No hot-path regressions, no heavy module-level imports added

Files Modified

  • src/praisonai-agents/praisonaiagents/bots/interactive.py - Fixed dispatch logic, added create_registry
  • src/praisonai-agents/praisonaiagents/bots/__init__.py - Removed redundant imports, exported create_registry
  • src/praisonai/praisonai/bots/discord.py - Use instance registry, fix BotMessage constructor
  • src/praisonai/praisonai/bots/slack.py - Use instance registry, fix BotMessage constructor
  • src/praisonai/praisonai/bots/telegram.py - Use instance registry, fix BotMessage constructor, remove dead code

Final Verdict

PR APPROVED - All critical issues have been resolved. The implementation now correctly:

  • Handles callback dispatch with proper fallback behavior
  • Supports multiple adapter instances without conflicts
  • Uses correct dataclass field types
  • Maintains full backward compatibility

The PR successfully implements the interactive action registry for button/select callbacks as intended in #2104.


- Fix dispatch() to properly return False when handlers return None
- Replace global registry singleton with create_registry() for adapter-specific instances
- Fix invalid BotMessage constructor arguments (chat_id, command, command_args moved to metadata)
- Remove redundant TYPE_CHECKING imports
- Remove dead code for 'approve' command special case
- Use field(default_factory=dict) for proper dataclass default

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner

@claude validate the fix, if its correct and follow guidenlines ?

also see if it valid to remove this , are you doing it right ?

from .protocols import (
    BotOSProtocol,  # noqa: F401
    BotProtocol,
    BotMessageProtocol,
    BotUserProtocol,
    BotChannelProtocol,
    BotMessage,
    BotUser,
    BotChannel,
    MessageType,
    ChatCommandInfo,
    ChatCommandProtocol,
    ProbeResult,
    HealthResult,
    EmailProtocol,
    EmailInbox,
    SupportsPresentation,
    PlatformCapabilities,
)
from .presentation import (
    MessagePresentation,
    PresentationBlock,
    PresentationButton,
    PresentationAction,
    SelectOption,
    PresentationLimits,
    ActionType,
    ButtonStyle,
    BlockType,
)

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


✅ Validation Complete & Fix Applied

I've reviewed the fixes from the previous commit and addressed the duplicate imports issue.

Todo List:

  • Remove duplicate BotOSProtocol import from TYPE_CHECKING block
  • Commit and push the fix

1. Previous Fixes Validation ✅

All critical issues have been properly addressed:

Fixed: dispatch() return value

  • Now correctly returns False when handler returns None (lines 174-175 in interactive.py)
  • Allows fallback paths to work as intended

Fixed: Global registry singleton issue

  • Added create_registry() function (line 224)
  • All three adapters now create their own registry instances:
    • Discord: line 129
    • Slack: line 142
    • Telegram: line 170
  • Prevents handler conflicts when multiple adapters run simultaneously

Fixed: BotMessage constructor issues

  • All adapters now correctly pass command and command_args in metadata dict
  • Using BotChannel with channel_id instead of invalid chat_id parameter

Fixed: Dataclass field defaults

  • Changed from = None to field(default_factory=dict) (line 43 in interactive.py)

2. Duplicate Import Fixed ✅

Removed the redundant import in commit 648b1a1e:

  • Deleted duplicate BotOSProtocol import from TYPE_CHECKING block (line 11)
  • Kept the runtime import (line 23) which is needed for the __all__ export

3. False Positives from Reviewers

Two incorrect issues that don't need fixing:

Greptile's "static_select issue" - Already Correct

  • Line 434 in slack.py correctly handles both cases: action.get("value", action.get("action_id", ""))
  • This properly extracts values from static_select menus

Dead code in Telegram - Not Found

  • The mentioned "approve" command special case doesn't exist in the current code

4. AGENTS.md Compliance ✅

The implementation follows all guidelines:

  • Protocol-driven: Core SDK has minimal protocols, heavy implementations in wrapper
  • Backward compatible: Deprecated global functions still work
  • Performance: No hot-path regressions, no heavy module-level imports

Summary

All issues have been validated and the duplicate import has been removed. The PR is ready for merge with all critical issues resolved and full backward compatibility maintained.


The import was redundant as BotOSProtocol is already imported at runtime (line 23) and needs to be available for the __all__ export list.
Comment on lines +751 to +762
# Update the message to show command was executed
await interaction.edit_original_response(
content=f"{interaction.message.content}\n\n✅ Command executed: /{cmd_name}",
view=None
)
return f"Command {cmd_name} executed"
except Exception as e:
logger.error(f"Command handler error: {e}")
await interaction.edit_original_response(
content=f"{interaction.message.content}\n\n❌ Error executing command",
view=None
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 interaction.message.content is accessed without a None guard in both the success and error response paths. Discord sets interaction.message to None for ephemeral or certain app-owned messages; when that happens, the f-string evaluation raises AttributeError before edit_original_response is called, the exception is swallowed by dispatch()'s catch block, and the user receives no feedback that the command ran (or failed).

Suggested change
# Update the message to show command was executed
await interaction.edit_original_response(
content=f"{interaction.message.content}\n\n✅ Command executed: /{cmd_name}",
view=None
)
return f"Command {cmd_name} executed"
except Exception as e:
logger.error(f"Command handler error: {e}")
await interaction.edit_original_response(
content=f"{interaction.message.content}\n\n❌ Error executing command",
view=None
)
# Update the message to show command was executed
original_content = interaction.message.content if interaction.message else ""
await interaction.edit_original_response(
content=f"{original_content}\n\n✅ Command executed: /{cmd_name}",
view=None
)
return f"Command {cmd_name} executed"
except Exception as e:
logger.error(f"Command handler error: {e}")
original_content = interaction.message.content if interaction.message else ""
await interaction.edit_original_response(
content=f"{original_content}\n\n❌ Error executing command",
view=None
)

Comment on lines +270 to +276
# Check permissions
if not self._command_policy.can_run(ctx.user_id, cmd_name):
await query.edit_message_text(
text=f"{query.message.text}\n\n⛔ You are not permitted to run /{cmd_name}",
parse_mode="Markdown"
)
return "Permission denied"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 query.message.text is embedded in three f-strings without first verifying that query.message is not None. In Telegram, message is None for callbacks originating from inline-mode results or from keyboards forwarded across bots. When it is None, evaluating the f-string raises AttributeError before edit_message_text is invoked — so the permission-denied notice, the success confirmation, and the error notice are all silently lost.

Suggested change
# Check permissions
if not self._command_policy.can_run(ctx.user_id, cmd_name):
await query.edit_message_text(
text=f"{query.message.text}\n\n⛔ You are not permitted to run /{cmd_name}",
parse_mode="Markdown"
)
return "Permission denied"
# Check permissions
if not self._command_policy.can_run(ctx.user_id, cmd_name):
original_text = query.message.text if query.message else ""
await query.edit_message_text(
text=f"{original_text}\n\n⛔ You are not permitted to run /{cmd_name}",
parse_mode="Markdown"
)
return "Permission denied"

Comment on lines +300 to +311
# Update the message to show command was executed
await query.edit_message_text(
text=f"{query.message.text}\n\n✅ Command executed: /{cmd_name}",
parse_mode="Markdown"
)
return f"Command {cmd_name} executed"
except Exception as e:
logger.error(f"Command handler error: {e}")
await query.edit_message_text(
text=f"{query.message.text}\n\n❌ Error executing command",
parse_mode="Markdown"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Same query.message.text null-dereference in the success and error paths of the command handler — see the companion comment on the permission-check branch above.

Suggested change
# Update the message to show command was executed
await query.edit_message_text(
text=f"{query.message.text}\n\n✅ Command executed: /{cmd_name}",
parse_mode="Markdown"
)
return f"Command {cmd_name} executed"
except Exception as e:
logger.error(f"Command handler error: {e}")
await query.edit_message_text(
text=f"{query.message.text}\n\n❌ Error executing command",
parse_mode="Markdown"
)
# Update the message to show command was executed
original_text = query.message.text if query.message else ""
await query.edit_message_text(
text=f"{original_text}\n\n✅ Command executed: /{cmd_name}",
parse_mode="Markdown"
)
return f"Command {cmd_name} executed"
except Exception as e:
logger.error(f"Command handler error: {e}")
original_text = query.message.text if query.message else ""
await query.edit_message_text(
text=f"{original_text}\n\n❌ Error executing command",
parse_mode="Markdown"
)

@MervinPraison MervinPraison merged commit c6f9e80 into main Jun 22, 2026
30 checks passed
praisonai-triage-agent Bot added a commit that referenced this pull request Jun 22, 2026
* fix: add interactive action registry for button/select callbacks (fixes #2104)

- Created InteractiveRegistry and dispatch protocol in core SDK
- Added encode_action/decode_callback for typed action handling
- Updated Telegram, Discord, and Slack adapters to use registry
- Registered handlers for 'command' and 'pair' namespaces
- Maintains backward compatibility with existing pairing callbacks

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>

* fix: address critical issues in interactive registry implementation

- Fix dispatch() to properly return False when handlers return None
- Replace global registry singleton with create_registry() for adapter-specific instances
- Fix invalid BotMessage constructor arguments (chat_id, command, command_args moved to metadata)
- Remove redundant TYPE_CHECKING imports
- Remove dead code for 'approve' command special case
- Use field(default_factory=dict) for proper dataclass default

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>

* fix: remove duplicate BotOSProtocol import from TYPE_CHECKING block

The import was redundant as BotOSProtocol is already imported at runtime (line 23) and needs to be available for the __all__ export list.

---------

Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interactive presentation actions render but have no inbound dispatch — typed buttons/selects are one-directional

1 participant