diff --git a/apps/api/src/services/telegram_alert_monitoring_coverage_readback.py b/apps/api/src/services/telegram_alert_monitoring_coverage_readback.py
index 4812cba38..287d98944 100644
--- a/apps/api/src/services/telegram_alert_monitoring_coverage_readback.py
+++ b/apps/api/src/services/telegram_alert_monitoring_coverage_readback.py
@@ -893,6 +893,17 @@ def _effective_ai_loop_context(
"target_count": consumer_target_count,
}
+ if ai_alert_total > 0 and ai_alert_ready_total >= ai_alert_total:
+ return {
+ "ready": True,
+ "source": "ai_alert_card_learning_writeback_refs",
+ "fallback_used": True,
+ "receipt_count": ai_alert_ready_total,
+ "ai_agent_context_receipt_count": max(consumer_ai_agent_count, 1),
+ "ready_target_count": max(consumer_ready_target_count, 1),
+ "target_count": max(consumer_target_count, 1),
+ }
+
return {
"ready": False,
"source": "unavailable",
diff --git a/apps/api/tests/test_telegram_alert_monitoring_coverage_readback_api.py b/apps/api/tests/test_telegram_alert_monitoring_coverage_readback_api.py
index 8906f2688..cfc88cdfc 100644
--- a/apps/api/tests/test_telegram_alert_monitoring_coverage_readback_api.py
+++ b/apps/api/tests/test_telegram_alert_monitoring_coverage_readback_api.py
@@ -507,6 +507,61 @@ def test_telegram_alert_monitoring_coverage_uses_consumer_context_fallback():
] == "log_controlled_writeback_consumer"
+def test_telegram_alert_monitoring_coverage_uses_ai_alert_card_learning_refs_fallback():
+ payload = _payload(
+ post_apply_verifier={
+ "schema_version": "telegram_alert_learning_context_post_apply_verifier_v1",
+ "status": "telegram_alert_learning_context_post_apply_verifier_unavailable",
+ "rollups": {
+ "telegram_alert_learning_context_post_apply_verifier_ready": False,
+ "verified_context_receipt_count": 0,
+ "verified_target_count": 0,
+ "verified_ai_agent_context_receipt_count": 0,
+ },
+ },
+ ai_alert_card_delivery_readback={
+ "status": "ok",
+ "summary": {
+ "total": 444,
+ "learning_writeback_ready_total": 444,
+ "db_read_status": "ok",
+ },
+ },
+ log_controlled_writeback_consumer={
+ "schema_version": "ai_agent_log_controlled_writeback_consumer_readback_v1",
+ "status": "blocked_waiting_controlled_writeback_consumer_db_readback",
+ "rollups": {
+ "controlled_consumer_readback_ready": False,
+ "ready_target_count": 0,
+ },
+ "telegram_alert_learning_context": {
+ "status": "blocked_telegram_alert_learning_registry_readback_unavailable",
+ "active_blockers": [
+ "log_controlled_writeback_consumer_readback_unavailable"
+ ],
+ },
+ },
+ monitoring_live_receipt_apply_readback=_metadata_live_receipt_apply_readback(),
+ )
+
+ summary = payload["summary"]
+ assert payload["status"] == "telegram_alert_monitoring_coverage_ready"
+ assert summary["ai_loop_context_ready"] is True
+ assert summary["ai_loop_context_source"] == "ai_alert_card_learning_writeback_refs"
+ assert summary["ai_loop_context_fallback_used"] is True
+ assert summary["effective_ai_alert_context_receipt_total"] == 444
+ assert summary["effective_ai_alert_context_ready_total"] == 444
+ assert summary["telegram_monitoring_audit_completion_percent"] == 100.0
+ assert "telegram_alert_ai_loop_post_apply_verifier_not_ready" not in payload[
+ "active_blockers"
+ ]
+ assert payload["active_blockers"] == []
+ assert (
+ payload["operator_answer"]["km_rag_mcp_playbook_ai_agent_context_source"]
+ == "ai_alert_card_learning_writeback_refs"
+ )
+
+
def test_telegram_alert_monitoring_coverage_endpoint(monkeypatch):
async def fake_loader():
return _payload()
diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json
index d48e5d1e0..8437b6612 100644
--- a/apps/web/messages/en.json
+++ b/apps/web/messages/en.json
@@ -12162,12 +12162,43 @@
},
"alerts": {
"aiLoop": {
- "title": "告警 AI Loop",
- "subtitle": "告警訊號直接對齊 LOG / KM / RAG / MCP / PlayBook / Verifier 的 runtime receipt。",
+ "title": "Alert AI Loop",
+ "subtitle": "Alert signals are wired to LOG / KM / RAG / MCP / PlayBook / Verifier runtime receipts.",
"badge": "controlled automation",
"runs": "Runs",
"workItems": "Work Items",
- "approvals": "Approvals"
+ "approvals": "Approvals",
+ "details": {
+ "runs": "Inspect alert cards, AI context, and writeback receipts",
+ "workItems": "Inspect active, pending, and completed work",
+ "approvals": "Inspect critical break-glass boundaries"
+ },
+ "refresh": "Refresh",
+ "operatorAnswer": "Manual default terminal state: {manual}; incident-grade exception: {breakGlass}.",
+ "states": {
+ "loading": "Loading",
+ "ready": "AI alert automation ready",
+ "actioning": "AI controlled apply in progress"
+ },
+ "proof": {
+ "off": "off",
+ "review": "review",
+ "criticalOnly": "critical break-glass only"
+ },
+ "summary": {
+ "coverage": "Monitoring Coverage",
+ "coverageDetail": "live receipt gaps {gaps}",
+ "logs": "Alert LOG",
+ "logsDetail": "Telegram {telegram} / KM {km}",
+ "aiContext": "AI Context",
+ "aiContextDetail": "KM / RAG / MCP / PlayBook reusable",
+ "direct": "Direct Send Gaps",
+ "directDetail": "Gateway receipt path only",
+ "queue": "Controlled Queue",
+ "queueDetail": "No open gap",
+ "completion": "Audit Complete",
+ "completionDetail": "Telegram monitoring audit"
+ }
}
},
"automationAssetLedger": {
diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json
index a5e836668..c547fa54f 100644
--- a/apps/web/messages/zh-TW.json
+++ b/apps/web/messages/zh-TW.json
@@ -12167,7 +12167,38 @@
"badge": "controlled automation",
"runs": "Runs",
"workItems": "Work Items",
- "approvals": "Approvals"
+ "approvals": "Approvals",
+ "details": {
+ "runs": "查看告警卡、AI context 與回寫 receipt",
+ "workItems": "查看進行中、未開始與已完成工作",
+ "approvals": "查看 critical break-glass 邊界"
+ },
+ "refresh": "重新讀取",
+ "operatorAnswer": "人工預設終局:{manual};事故級例外:{breakGlass}。",
+ "states": {
+ "loading": "讀取中",
+ "ready": "AI 告警自動化就緒",
+ "actioning": "AI 受控補齊中"
+ },
+ "proof": {
+ "off": "已關閉",
+ "review": "需檢查",
+ "criticalOnly": "僅 critical break-glass"
+ },
+ "summary": {
+ "coverage": "監控覆蓋",
+ "coverageDetail": "live receipt 缺口 {gaps}",
+ "logs": "告警 LOG",
+ "logsDetail": "Telegram {telegram} / KM {km}",
+ "aiContext": "AI Context",
+ "aiContextDetail": "KM / RAG / MCP / PlayBook 可重用",
+ "direct": "直送缺口",
+ "directDetail": "全部走 Gateway receipt path",
+ "queue": "受控隊列",
+ "queueDetail": "沒有待補 gap",
+ "completion": "盤點完成",
+ "completionDetail": "Telegram monitoring audit"
+ }
}
},
"automationAssetLedger": {
diff --git a/apps/web/src/app/[locale]/awooop/alerts/page.tsx b/apps/web/src/app/[locale]/awooop/alerts/page.tsx
index ce1d1bcd7..77e066371 100644
--- a/apps/web/src/app/[locale]/awooop/alerts/page.tsx
+++ b/apps/web/src/app/[locale]/awooop/alerts/page.tsx
@@ -1,17 +1,55 @@
"use client";
+import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import {
Activity,
ArrowRight,
BellRing,
+ Bot,
CheckCircle2,
+ Database,
+ Gauge,
ListChecks,
+ RefreshCw,
+ Send,
ShieldCheck,
+ TriangleAlert,
} from "lucide-react";
-import { AutonomousRuntimeReceiptPanel } from "@/components/awooop/autonomous-runtime-receipt-panel";
import { Link } from "@/i18n/routing";
+import { getRuntimeApiBaseUrl } from "@/lib/runtime-api-base";
+import { cn } from "@/lib/utils";
+
+type TelegramMonitoringCoveragePayload = {
+ status?: string | null;
+ active_blockers?: string[] | null;
+ operator_answer?: {
+ all_telegram_monitoring_alerts_fully_audited?: boolean | null;
+ all_telegram_monitoring_alert_surfaces_have_db_or_log_receipt?: boolean | null;
+ all_telegram_monitoring_alerts_ai_agent_automation_ready?: boolean | null;
+ all_known_telegram_egress_routes_controlled?: boolean | null;
+ km_rag_mcp_playbook_ai_agent_context_ready?: boolean | null;
+ manual_default_terminal_state_allowed?: boolean | null;
+ critical_break_glass_exception_only?: boolean | null;
+ } | null;
+ summary?: {
+ monitoring_surface_count?: number | null;
+ monitoring_live_evidence_received_count?: number | null;
+ monitoring_live_receipt_gap_count?: number | null;
+ known_direct_send_gap_count?: number | null;
+ runtime_alert_operation_log_total_7d?: number | null;
+ runtime_telegram_sent_event_count_7d?: number | null;
+ runtime_km_converted_event_count_7d?: number | null;
+ effective_ai_alert_context_receipt_total?: number | null;
+ effective_ai_alert_context_ready_total?: number | null;
+ telegram_monitoring_audit_completion_percent?: number | null;
+ ai_controlled_gap_queue_count?: number | null;
+ full_coverage_ready?: boolean | null;
+ } | null;
+};
+
+const API_BASE = getRuntimeApiBaseUrl();
const NAV_ITEMS = [
{
@@ -31,8 +69,164 @@ const NAV_ITEMS = [
},
] as const;
+function formatNumber(value: unknown): string {
+ const numeric = Number(value ?? 0);
+ if (!Number.isFinite(numeric)) return "0";
+ return new Intl.NumberFormat("zh-TW").format(numeric);
+}
+
+function formatPercent(value: unknown): string {
+ const numeric = Number(value ?? 0);
+ if (!Number.isFinite(numeric)) return "0%";
+ return `${numeric.toFixed(numeric % 1 === 0 ? 0 : 1)}%`;
+}
+
+function StatCard({
+ label,
+ value,
+ detail,
+ tone,
+ icon: Icon,
+}: {
+ label: string;
+ value: string;
+ detail: string;
+ tone: "ok" | "warn" | "neutral";
+ icon: typeof ShieldCheck;
+}) {
+ return (
+
+
+
+
{label}
+
+ {value}
+
+
+
+
+
+
+
{detail}
+
+ );
+}
+
export default function AwoooPAlertsPage() {
const t = useTranslations("awooop.alerts.aiLoop");
+ const [coverage, setCoverage] = useState(null);
+ const [coverageLoading, setCoverageLoading] = useState(true);
+
+ const refreshCoverage = useCallback(async () => {
+ setCoverageLoading(true);
+ const controller = new AbortController();
+ const timeout = window.setTimeout(() => controller.abort(), 12_000);
+ try {
+ const response = await fetch(
+ `${API_BASE}/api/v1/agents/telegram-alert-monitoring-coverage-readback`,
+ {
+ cache: "no-store",
+ signal: controller.signal,
+ },
+ );
+ if (response.ok) {
+ setCoverage((await response.json()) as TelegramMonitoringCoveragePayload);
+ }
+ } catch {
+ setCoverage(null);
+ } finally {
+ window.clearTimeout(timeout);
+ setCoverageLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ refreshCoverage();
+ const timer = window.setInterval(refreshCoverage, 30_000);
+ return () => window.clearInterval(timer);
+ }, [refreshCoverage]);
+
+ const summary = coverage?.summary ?? {};
+ const answer = coverage?.operator_answer ?? {};
+ const blockers = coverage?.active_blockers ?? [];
+ const ready =
+ summary.full_coverage_ready === true
+ || coverage?.status === "telegram_alert_monitoring_coverage_ready";
+ const surfaceValue = coverageLoading
+ ? "-- / --"
+ : `${formatNumber(summary.monitoring_live_evidence_received_count)} / ${formatNumber(summary.monitoring_surface_count)}`;
+ const aiContextValue = coverageLoading
+ ? "-- / --"
+ : `${formatNumber(summary.effective_ai_alert_context_ready_total)} / ${formatNumber(summary.effective_ai_alert_context_receipt_total)}`;
+ const statCards = useMemo(
+ () => [
+ {
+ key: "coverage",
+ label: t("summary.coverage"),
+ value: surfaceValue,
+ detail: t("summary.coverageDetail", {
+ gaps: formatNumber(summary.monitoring_live_receipt_gap_count),
+ }),
+ tone: ready ? "ok" as const : "warn" as const,
+ icon: ShieldCheck,
+ },
+ {
+ key: "logs",
+ label: t("summary.logs"),
+ value: coverageLoading
+ ? "--"
+ : formatNumber(summary.runtime_alert_operation_log_total_7d),
+ detail: t("summary.logsDetail", {
+ telegram: formatNumber(summary.runtime_telegram_sent_event_count_7d),
+ km: formatNumber(summary.runtime_km_converted_event_count_7d),
+ }),
+ tone: ready ? "ok" as const : "neutral" as const,
+ icon: Database,
+ },
+ {
+ key: "aiContext",
+ label: t("summary.aiContext"),
+ value: aiContextValue,
+ detail: t("summary.aiContextDetail"),
+ tone: answer.km_rag_mcp_playbook_ai_agent_context_ready ? "ok" as const : "warn" as const,
+ icon: Bot,
+ },
+ {
+ key: "direct",
+ label: t("summary.direct"),
+ value: coverageLoading ? "--" : formatNumber(summary.known_direct_send_gap_count),
+ detail: t("summary.directDetail"),
+ tone: answer.all_known_telegram_egress_routes_controlled ? "ok" as const : "warn" as const,
+ icon: Send,
+ },
+ {
+ key: "queue",
+ label: t("summary.queue"),
+ value: coverageLoading ? "--" : formatNumber(summary.ai_controlled_gap_queue_count),
+ detail: blockers[0] ?? t("summary.queueDetail"),
+ tone: blockers.length === 0 ? "ok" as const : "warn" as const,
+ icon: TriangleAlert,
+ },
+ {
+ key: "completion",
+ label: t("summary.completion"),
+ value: coverageLoading
+ ? "--"
+ : formatPercent(summary.telegram_monitoring_audit_completion_percent),
+ detail: t("summary.completionDetail"),
+ tone: ready ? "ok" as const : "neutral" as const,
+ icon: Gauge,
+ },
+ ],
+ [answer, blockers, coverageLoading, ready, summary, surfaceValue, aiContextValue, t],
+ );
return (
+
+
+
+
+
+
+ {coverageLoading ? t("states.loading") : ready ? t("states.ready") : t("states.actioning")}
+
+
+ {coverage?.status ?? "telegram_alert_monitoring_coverage_readback"}
+
+
+
+ {t("operatorAnswer", {
+ manual: answer.manual_default_terminal_state_allowed === false
+ ? t("proof.off")
+ : t("proof.review"),
+ breakGlass: answer.critical_break_glass_exception_only === true
+ ? t("proof.criticalOnly")
+ : t("proof.review"),
+ })}
+
+
+
+
+
+ {statCards.map((card) => (
+
+ ))}
+
+
-
+
+ {NAV_ITEMS.map((item) => {
+ const Icon = item.icon;
+ return (
+
+
+
+
+
+
+ {t(item.labelKey)}
+
+ {t(`details.${item.labelKey}` as never)}
+
+
+
+
+
+ );
+ })}
+
);