Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion plugins/platforms/telegram/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5149,7 +5149,10 @@ async def send_exec_approval(
InlineKeyboardButton("✅ Always", callback_data=f"ea:always:{approval_id}")
)
buttons.append(InlineKeyboardButton("❌ Deny", callback_data=f"ea:deny:{approval_id}"))
keyboard = InlineKeyboardMarkup([buttons])
# Pair into rows (2x2 for the full set) so labels stay readable on
# mobile — a single 4-button row truncates to "Allo… / Ses… / …".
rows = [buttons[i:i + 2] for i in range(0, len(buttons), 2)]
keyboard = InlineKeyboardMarkup(rows)

kwargs: Dict[str, Any] = {
"chat_id": normalize_telegram_chat_id(chat_id),
Expand Down
72 changes: 72 additions & 0 deletions tests/gateway/test_telegram_approval_buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,78 @@ async def test_non_smart_allow_permanent_false_keeps_session(self, monkeypatch):

assert buttons == ["✅ Allow Once", "✅ Session", "❌ Deny"]

@pytest.mark.asyncio
async def test_full_approval_keyboard_is_two_by_two(self, monkeypatch):
"""Regression: d48bf743f flattened all buttons into one row (4x1)."""
adapter = _make_adapter()
adapter._bot.send_message = AsyncMock(return_value=SimpleNamespace(message_id=42))
captured_rows = []
monkeypatch.setattr(
"plugins.platforms.telegram.adapter.InlineKeyboardButton",
lambda text, callback_data: text,
)
monkeypatch.setattr(
"plugins.platforms.telegram.adapter.InlineKeyboardMarkup",
lambda rows: captured_rows.extend(rows) or rows,
)

await adapter.send_exec_approval(
chat_id="12345", command="curl example.test", session_key="s",
)

assert captured_rows == [
["✅ Allow Once", "✅ Session"],
["✅ Always", "❌ Deny"],
]

@pytest.mark.asyncio
async def test_three_button_keyboard_pairs_then_singleton(self, monkeypatch):
adapter = _make_adapter()
adapter._bot.send_message = AsyncMock(return_value=SimpleNamespace(message_id=42))
captured_rows = []
monkeypatch.setattr(
"plugins.platforms.telegram.adapter.InlineKeyboardButton",
lambda text, callback_data: text,
)
monkeypatch.setattr(
"plugins.platforms.telegram.adapter.InlineKeyboardMarkup",
lambda rows: captured_rows.extend(rows) or rows,
)

await adapter.send_exec_approval(
chat_id="12345", command="curl example.test", session_key="s",
allow_permanent=False,
)

assert captured_rows == [
["✅ Allow Once", "✅ Session"],
["❌ Deny"],
]

@pytest.mark.asyncio
async def test_smart_deny_two_buttons_share_one_row(self, monkeypatch):
"""smart_deny yields 2 buttons — they pair into a single readable row."""
adapter = _make_adapter()
adapter._bot.send_message = AsyncMock(return_value=SimpleNamespace(message_id=42))
captured_rows = []
monkeypatch.setattr(
"plugins.platforms.telegram.adapter.InlineKeyboardButton",
lambda text, callback_data: text,
)
monkeypatch.setattr(
"plugins.platforms.telegram.adapter.InlineKeyboardMarkup",
lambda rows: captured_rows.extend(rows) or rows,
)

await adapter.send_exec_approval(
chat_id="12345", command="curl example.test", session_key="s",
allow_permanent=False, smart_denied=True,
)

assert captured_rows == [
["✅ Allow Once", "❌ Deny"],
]

@pytest.mark.asyncio
async def test_stores_approval_state(self):
adapter = _make_adapter()
Expand Down
Loading