diff --git a/apps/api/src/services/decision_manager.py b/apps/api/src/services/decision_manager.py
index 8f397e3d..76e4e121 100644
--- a/apps/api/src/services/decision_manager.py
+++ b/apps/api/src/services/decision_manager.py
@@ -227,6 +227,11 @@ async def _push_decision_to_telegram(
decision_state=_decision_state,
)
+ # 2026-04-12 ogt: ADR-075 — 從 Incident 提取 alert_category/notification_type(各分支共用)
+ _alert_category = getattr(incident, "alert_category", "") or ""
+ _notification_type = getattr(incident, "notification_type", "") or (_notif_type.value if _notif_type else "")
+ _alertname = incident.signals[0].labels.get("alertname", "MetaSystemAlert") if incident.signals else "MetaSystemAlert"
+
if _notif_type == NotificationType.TYPE_1:
# 純資訊通知 — 無按鈕
tg_result = await gateway.send_info_notification(
@@ -244,11 +249,20 @@ async def _push_decision_to_telegram(
resource_name=target[:50],
diff_summary=description[:500],
)
+ elif _notif_type.value == "TYPE-8M" or _alert_category in ("alertchain_health", "flywheel_health"):
+ # TYPE-8M:飛輪/告警鏈路健康異常,發到個人 DM(不發群組)
+ tg_result = await gateway.send_meta_alert(
+ incident_id=incident.incident_id,
+ approval_id=approval_id,
+ alertname=_alertname,
+ alert_category=_alert_category,
+ diagnosis=reasoning[:100] if reasoning else description[:100],
+ severity_level=risk_level,
+ system_impact=description[:150] if description else "",
+ probable_cause=reasoning[:100] if reasoning else "",
+ )
else:
# TYPE-2 / TYPE-3 / TYPE-4 都走 send_approval_card(按鈕組合由 alert_category 決定)
- # 2026-04-12 ogt: ADR-075 斷點 A 修復 — 從 Incident 提取 alert_category/notification_type
- _alert_category = getattr(incident, "alert_category", "") or ""
- _notification_type = getattr(incident, "notification_type", "") or _notif_type.value if _notif_type else ""
tg_result = await gateway.send_approval_card(
approval_id=approval_id,
risk_level=risk_level,
diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py
index 489b73a4..b9aaa6f7 100644
--- a/apps/api/src/services/telegram_gateway.py
+++ b/apps/api/src/services/telegram_gateway.py
@@ -1867,6 +1867,67 @@ class TelegramGateway:
},
)
+ # =========================================================================
+ # ADR-075: TYPE-8M Meta-System 告警(飛輪/告警鏈路健康)
+ # 2026-04-12 ogt
+ # =========================================================================
+
+ async def send_meta_alert(
+ self,
+ incident_id: str,
+ approval_id: str,
+ alertname: str,
+ alert_category: str, # "flywheel_health" or "alertchain_health"
+ diagnosis: str,
+ severity_level: str = "critical",
+ system_impact: str = "",
+ probable_cause: str = "",
+ ) -> dict:
+ """
+ TYPE-8M Meta-System 告警 — 飛輪或告警鏈路自身健康異常。
+
+ 適用: FlywheelPlaybookZero / AlertChainBroken_* 等
+ 按鈕: 固定 3 個([觸發診斷] [查看面板] [靜默])
+ 只發個人 DM,不發群組(雙頻道路由規則)。
+ """
+ severity_emoji = "🔴" if severity_level == "critical" else "🟠"
+ category_label = "飛輪核心異常" if alert_category == "flywheel_health" else "告警鏈路異常"
+
+ text = (
+ f"⚙️ META SYSTEM | {severity_emoji} {category_label}\n"
+ f"━━━━━━━━━━━━━━━━━━━\n"
+ f"📋 {html.escape(incident_id)}\n"
+ f"🚨 異常元件:{html.escape(alertname)}\n"
+ f"🎯 診斷結果:{html.escape(diagnosis[:100])}\n"
+ )
+ if system_impact:
+ text += f"\n🧠 系統影響\n{html.escape(system_impact[:150])}\n"
+ if probable_cause:
+ text += f"└─ 可能根因:{html.escape(probable_cause[:100])}\n"
+
+ silence_nonce = self._security.generate_callback_nonce(approval_id, "silence")
+ keyboard = {
+ "inline_keyboard": [
+ [
+ {"text": "🔄 觸發診斷", "callback_data": f"action:flywheel_diag:{incident_id}"},
+ {"text": "📊 查看面板", "callback_data": f"action:flywheel_dashboard:{incident_id}"},
+ ],
+ [
+ {"text": "🔕 靜默 1h", "callback_data": silence_nonce},
+ ],
+ ]
+ }
+
+ return await self._make_request(
+ "sendMessage",
+ {
+ "chat_id": settings.OPENCLAW_TG_CHAT_ID,
+ "text": text,
+ "parse_mode": "HTML",
+ "reply_markup": keyboard,
+ },
+ )
+
# =========================================================================
# 新訊息發送方法 (2026-03-29 ogt: ADR-038)
# =========================================================================