diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py index ad3910b6..f2a2c776 100644 --- a/apps/api/src/services/telegram_gateway.py +++ b/apps/api/src/services/telegram_gateway.py @@ -2229,6 +2229,7 @@ class TelegramGateway: user_id: int, username: str, user: dict, + message_id: int | None = None, ) -> dict: """ 2026-04-19 P0 修 (ADR-092): 處理 4 LLM scanner 的互動按鈕. @@ -2269,6 +2270,18 @@ class TelegramGateway: feedback_text = result.get("feedback_text", "已收到") await self._answer_callback(callback_query_id, action, text=feedback_text) + # 2026-04-22 Claude Sonnet 4.6: 發群組 reply(toast 2-3 秒消失,群組才是永久可見) + if message_id and feedback_text: + try: + await self._send_request("sendMessage", { + "chat_id": settings.OPENCLAW_TG_CHAT_ID, + "text": feedback_text, + "reply_to_message_id": message_id, + }) + logger.info("ai_advisory_group_reply_sent", action=pure_action, message_id=message_id) + except Exception as _ge: + logger.warning("ai_advisory_group_reply_failed", action=pure_action, error=str(_ge)) + return { "action": action, "advisory_type": advisory_type, "advisory_id": advisory_id, "user": user, "success": result.get("success", False), @@ -3141,6 +3154,7 @@ class TelegramGateway: user_id=user_id, username=username, user=user, + message_id=message_id, ) # =================================================================== @@ -5544,26 +5558,43 @@ class TelegramGateway: ) if approval: + from src.models.approval import ApprovalStatus + status_val = approval.status.value if hasattr(approval.status, "value") else str(approval.status) logger.info( "telegram_approval_signed_via_polling", approval_id=approval_id, user_id=user_id, - status=approval.status.value, - execution_triggered=execution_triggered, - ) - # 2026-04-09 Claude Sonnet 4.6: 回應 Telegram — 更新訊息狀態 + answer callback - await self._notify_approval_result( - message_id=message_id, - incident_id=approval_id, - action="approve", - username=username, + status=status_val, execution_triggered=execution_triggered, ) + # 2026-04-22 Claude Sonnet 4.6: 只有真正轉為 APPROVED 才發「執行中...」 + # 非 PENDING 狀態下 sign_approval early-return → approval 是舊 record + # 此時不應發「執行中...」,應告知用戶告警已處理過 + if approval.status == ApprovalStatus.APPROVED: + # 2026-04-09 Claude Sonnet 4.6: 回應 Telegram — 更新訊息狀態 + answer callback + await self._notify_approval_result( + message_id=message_id, + incident_id=approval_id, + action="approve", + username=username, + execution_triggered=execution_triggered, + ) + else: + # 告警已是 execution_failed / execution_success / rejected 等終態 + try: + await self._send_request("sendMessage", { + "chat_id": settings.OPENCLAW_TG_CHAT_ID, + "text": f"ℹ️ 此告警已處理(狀態:{status_val}),無法重複批准 by @{username}", + "reply_to_message_id": message_id, + }) + except Exception as _ne: + logger.warning("telegram_approval_already_resolved_notify_failed", error=str(_ne)) + return + # ADR-073 修補 + 2026-04-14 Claude Sonnet 4.6 修復: # 原本 gate 用 execution_triggered,race condition 時失效(樂觀鎖失敗) # 改用 approval.status == APPROVED(與 REST API 路徑 approvals.py:360 對齊) # 用 Redis lock exec:{approval_id} 防重入(REST + Telegram 同時簽核) - from src.models.approval import ApprovalStatus if approval.status == ApprovalStatus.APPROVED: import asyncio