feat(Phase 5 Sprint 5.1): Telegram callback_handler 接上 dispatcher
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled

整合點: _handle_callback_query 未知 action fallback 路徑

變更:
1. Line 2601 原「⚠️ 未知操作」改呼叫 _dispatch_category_action()
2. 新增 _dispatch_category_action() method:
   - 查 callback_action_spec registry
   - 若 action 不存在 → 回「未知操作」(行為不變)
   - 若存在 → acknowledge + 從 incident 取 labels + dispatch + reply 原卡片

效果:
- check_process / check_port / check_log_* / check_health / open_signoz /
  open_flywheel 等 10 個查類按鈕現在有完整 flow(雖 Sprint 5.2 還沒接 MCP,但 stub 會 reply)
- 當 CD 部署 + Sprint 5.2 實裝 MCP 接線後,查類按鈕自動上線

Sprint 5.1 DOD:
-  callback_handler 接線 _dispatch_category_action
-  Dispatcher 讀 incident labels 替換模板變數
-  Reply to 原告警卡片(Redis tg_msg lookup)
-  MCP 實際執行(Sprint 5.2)

回歸測試: 109/109

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-14 20:41:22 +08:00
parent 36754a8a84
commit 581b244ad1

View File

@@ -2598,7 +2598,14 @@ class TelegramGateway:
await self._answer_callback(callback_query_id, action, text="🔄 重診排程中...")
await self._send_reanalyze_result(incident_id)
else:
await self._answer_callback(callback_query_id, action, text="⚠️ 未知操作")
# 2026-04-14 Claude Sonnet 4.6 (Phase 5 Sprint 5.1):
# 未知 action → fallback dispatcher (查看 callback_action_spec.yaml 是否有註冊)
await self._dispatch_category_action(
callback_query_id=callback_query_id,
action=action,
incident_id=incident_id,
user_id=user_id,
)
return {
"action": action,
@@ -2608,7 +2615,11 @@ class TelegramGateway:
"info_action": True,
}
nonce = parsed["nonce"]
nonce = parsed["nonce"] # 4-part nonce action
# 2026-04-14 Claude Sonnet 4.6 (Phase 5 Sprint 5.1):
# 寫類 nonce action 先驗 nonce 再 fallback dispatcher若 action 在 registry
# 這段邏輯在 Step 2 之後再處理,這裡只是佔位註解
# 驗證使用者 + Nonce
user = await self._security.verify_callback(
@@ -3445,6 +3456,86 @@ class TelegramGateway:
)
return True
async def _dispatch_category_action(
self,
callback_query_id: str,
action: str,
incident_id: str,
user_id: int,
) -> None:
"""
Phase 5 Sprint 5.1 (2026-04-14 Claude Sonnet 4.6):
Fallback dispatcher — 未知 info action 查 callback_action_spec.yaml
流程:
1. 查 action registry
2. 若不存在 → 原「⚠️ 未知操作」回覆
3. 若存在 → 從 incident 取 labels → dispatch_action → reply_to 原卡片
注意: 此方法只處理 info action (查類)。nonce action (寫類) 走另一路徑。
"""
from src.services.callback_dispatcher import dispatch_action, get_action_spec
spec = get_action_spec(action)
if not spec:
await self._answer_callback(callback_query_id, action, text="⚠️ 未知操作")
return
# Acknowledge callback immediately避免 Telegram 端 timeout
await self._answer_callback(
callback_query_id, action, text=f"{spec.emoji} 執行中..."
)
# 從 incident 取 labels (供模板替換)
labels: dict = {}
try:
from src.repositories.incident_repository import get_incident_repository
repo = get_incident_repository()
incident = await repo.get_by_id(incident_id)
if incident and incident.signals:
labels = incident.signals[0].labels or {}
except Exception as _e:
logger.debug("dispatch_labels_lookup_failed", incident_id=incident_id, error=str(_e))
# Dispatch
result = await dispatch_action(
action_name=action,
incident_id=incident_id,
user_id=user_id,
labels=labels,
)
# Reply to 原卡片 — 從 Redis tg_msg 查 message_id
try:
from src.core.redis_client import get_redis
redis = get_redis()
msg_id_raw = await redis.get(f"tg_msg:{incident_id}")
orig_msg_id = int(msg_id_raw) if msg_id_raw else None
except Exception:
orig_msg_id = None
try:
payload: dict = {
"chat_id": settings.OPENCLAW_TG_CHAT_ID,
"text": result.result_text,
"parse_mode": "HTML",
}
if orig_msg_id:
payload["reply_to_message_id"] = orig_msg_id
await self._http_client.post(
f"https://api.telegram.org/bot{settings.OPENCLAW_TG_BOT_TOKEN}/sendMessage",
json=payload,
)
logger.info(
"category_action_reply_sent",
action=action,
incident_id=incident_id,
success=result.success,
duration_ms=round(result.duration_ms, 1),
)
except Exception as _e:
logger.warning("category_action_reply_failed", action=action, error=str(_e))
async def _send_incident_detail(self, incident_id: str) -> None:
"""
ADR-050 P2: 傳送事件詳情訊息 (不修改原始簽核卡片)