feat(telegram): expose alert learning registry readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m11s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m11s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -554,11 +554,35 @@ interface AiAlertCardDeliverySummary {
|
||||
delivery_receipt_required_total: number;
|
||||
runtime_write_gate_open_count: number;
|
||||
runtime_write_allowed: boolean;
|
||||
learning_writeback_ready_total?: number;
|
||||
learning_writeback_missing_total?: number;
|
||||
km_writeback_ref_ready_total?: number;
|
||||
playbook_writeback_ref_ready_total?: number;
|
||||
rag_chunk_ref_ready_total?: number;
|
||||
mcp_evidence_ref_ready_total?: number;
|
||||
post_verifier_ref_ready_total?: number;
|
||||
learning_registry_target_count?: number;
|
||||
learning_registry_ready_target_count?: number;
|
||||
learning_registry_missing_target_count?: number;
|
||||
learning_registry_next_action?: string;
|
||||
latest_sent_at?: string | null;
|
||||
latest_queued_at?: string | null;
|
||||
production_write_count: number;
|
||||
}
|
||||
|
||||
interface AiAlertCardLearningRegistryBinding {
|
||||
target: string;
|
||||
status: string;
|
||||
consumer_surface: string;
|
||||
source_field: string;
|
||||
ref?: string | null;
|
||||
latest_ref?: string | null;
|
||||
ready_receipt_count?: number;
|
||||
target_write_performed?: boolean;
|
||||
metadata_only?: boolean;
|
||||
next_action?: string;
|
||||
}
|
||||
|
||||
interface AiAlertCardDeliveryItem {
|
||||
message_id: string;
|
||||
run_id: string;
|
||||
@@ -578,6 +602,10 @@ interface AiAlertCardDeliveryItem {
|
||||
runtime_write_allowed: boolean;
|
||||
candidate_only: boolean;
|
||||
delivery_receipt_readback_required: boolean;
|
||||
learning_writeback_ready?: boolean;
|
||||
learning_writeback_status?: string;
|
||||
learning_writeback_refs?: Record<string, unknown>;
|
||||
learning_registry_bindings?: AiAlertCardLearningRegistryBinding[];
|
||||
source_refs: Record<string, unknown>;
|
||||
run_state?: string | null;
|
||||
agent_id?: string | null;
|
||||
@@ -585,12 +613,29 @@ interface AiAlertCardDeliveryItem {
|
||||
run_detail_href?: string | null;
|
||||
}
|
||||
|
||||
interface AiAlertCardLearningRegistry {
|
||||
schema_version: string;
|
||||
status: string;
|
||||
work_item_id: string;
|
||||
project_id: string;
|
||||
source_endpoint: string;
|
||||
consumer_context_route: string;
|
||||
target_count: number;
|
||||
ready_target_count: number;
|
||||
missing_target_count: number;
|
||||
ready_receipt_ref_count: number;
|
||||
next_action: string;
|
||||
targets: AiAlertCardLearningRegistryBinding[];
|
||||
operation_boundaries?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
interface AiAlertCardsResponse {
|
||||
items?: AiAlertCardDeliveryItem[];
|
||||
total?: number;
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
summary?: AiAlertCardDeliverySummary | null;
|
||||
learning_registry?: AiAlertCardLearningRegistry | null;
|
||||
}
|
||||
|
||||
interface AiRoutePolicyItem {
|
||||
@@ -3620,11 +3665,13 @@ function CallbackReplyEvidencePanel({
|
||||
function AiAlertCardDeliveryPanel({
|
||||
items,
|
||||
summary,
|
||||
learningRegistry,
|
||||
loading,
|
||||
error,
|
||||
}: {
|
||||
items: AiAlertCardDeliveryItem[];
|
||||
summary?: AiAlertCardDeliverySummary | null;
|
||||
learningRegistry?: AiAlertCardLearningRegistry | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}) {
|
||||
@@ -3640,6 +3687,17 @@ function AiAlertCardDeliveryPanel({
|
||||
const failed = summary?.failed_total ?? 0;
|
||||
const pending = summary?.pending_total ?? 0;
|
||||
const runtimeGateOpen = summary?.runtime_write_gate_open_count ?? 0;
|
||||
const registryReady = learningRegistry?.ready_target_count
|
||||
?? summary?.learning_registry_ready_target_count
|
||||
?? 0;
|
||||
const registryTotal = learningRegistry?.target_count
|
||||
?? summary?.learning_registry_target_count
|
||||
?? 0;
|
||||
const registryStatus = registryTotal > 0 && registryReady === registryTotal
|
||||
? "Registry ready"
|
||||
: registryTotal > 0
|
||||
? "Registry waiting"
|
||||
: "Registry empty";
|
||||
const statusLabel = total === 0
|
||||
? "尚無送達讀回"
|
||||
: failed > 0
|
||||
@@ -3664,6 +3722,14 @@ function AiAlertCardDeliveryPanel({
|
||||
const targetLabel = (target: string) => ({
|
||||
wazuh_dashboard_api: "Wazuh 儀表板/API 讀回",
|
||||
}[target] ?? "未分類目標");
|
||||
const registryTargetLabel = (target: string) => ({
|
||||
km: "KM",
|
||||
playbook: "PlayBook",
|
||||
rag: "RAG",
|
||||
mcp: "MCP",
|
||||
verifier: "Verifier",
|
||||
ai_agent: "AI Agent",
|
||||
}[target] ?? target);
|
||||
|
||||
return (
|
||||
<section
|
||||
@@ -3688,6 +3754,9 @@ function AiAlertCardDeliveryPanel({
|
||||
<span className="border border-[#d8d3c7] bg-white px-2 py-0.5 text-xs font-semibold text-[#5f5b52]">
|
||||
執行閘門 {runtimeGateOpen}
|
||||
</span>
|
||||
<span className="border border-[#9bb6d9] bg-[#eef5ff] px-2 py-0.5 text-xs font-semibold text-[#1f5b9b]">
|
||||
{registryReady}/{registryTotal} registry
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3696,7 +3765,11 @@ function AiAlertCardDeliveryPanel({
|
||||
{ label: "總數", value: total, detail: `最新 ${latest}` },
|
||||
{ label: "已送達", value: sent, detail: "Telegram 鏡像已送達" },
|
||||
{ label: "失敗", value: failed, detail: "需查送達錯誤" },
|
||||
{ label: "等待", value: pending, detail: `影子 ${summary?.shadow_total ?? 0}` },
|
||||
{
|
||||
label: "Registry",
|
||||
value: registryReady,
|
||||
detail: `${registryStatus} · refs ${learningRegistry?.ready_receipt_ref_count ?? 0}`,
|
||||
},
|
||||
].map((metric) => (
|
||||
<div key={metric.label} className="bg-white px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{metric.label}</p>
|
||||
@@ -3706,6 +3779,46 @@ function AiAlertCardDeliveryPanel({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{learningRegistry && (
|
||||
<div className="border-b border-[#e0ddd4] bg-[#fbfcfb] px-4 py-4">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-[#141413]">AI Loop learning registry</p>
|
||||
<p className="mt-1 text-xs text-[#77736a]">
|
||||
{learningRegistry.consumer_context_route} · {learningRegistry.next_action}
|
||||
</p>
|
||||
</div>
|
||||
<span className="border border-[#cbdccf] bg-white px-2 py-0.5 text-xs font-semibold text-[#17602a]">
|
||||
{learningRegistry.ready_target_count}/{learningRegistry.target_count}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-3 grid gap-px bg-[#e0ddd4] sm:grid-cols-2 xl:grid-cols-6">
|
||||
{learningRegistry.targets.map((target) => {
|
||||
const ready = target.status === "ready_for_consumer_context";
|
||||
return (
|
||||
<div key={target.target} className="bg-white px-3 py-2">
|
||||
<p className="text-xs font-semibold text-[#141413]">
|
||||
{registryTargetLabel(target.target)}
|
||||
</p>
|
||||
<p className={cn(
|
||||
"mt-1 text-xs font-semibold",
|
||||
ready ? "text-[#17602a]" : "text-[#8a5a08]"
|
||||
)}>
|
||||
{ready ? "ready" : "waiting"} · {target.ready_receipt_count ?? 0}
|
||||
</p>
|
||||
<p
|
||||
className="mt-1 truncate font-mono text-[11px] leading-4 text-[#77736a]"
|
||||
title={target.latest_ref ?? target.ref ?? target.consumer_surface}
|
||||
>
|
||||
{target.latest_ref ?? target.ref ?? target.consumer_surface}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error ? (
|
||||
<div className="px-4 py-4 text-sm text-[#9f2f25]">讀回失敗:{error}</div>
|
||||
) : items.length > 0 ? (
|
||||
@@ -3750,6 +3863,14 @@ function AiAlertCardDeliveryPanel({
|
||||
<p>目標:<span className="font-semibold">{targetLabel(item.target)}</span></p>
|
||||
<p>證據索引:<span className="font-mono">{sourceRefCount}</span></p>
|
||||
<p>執行閘門:<span className="font-mono">{item.runtime_write_gate_count}</span></p>
|
||||
<p>
|
||||
Registry:
|
||||
<span className="font-mono">
|
||||
{item.learning_registry_bindings?.filter((binding) => (
|
||||
binding.status === "ready_for_consumer_context"
|
||||
)).length ?? 0}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={runHref as never}
|
||||
@@ -4240,6 +4361,8 @@ export default function RunsPage() {
|
||||
const [callbackEventsError, setCallbackEventsError] = useState<string | null>(null);
|
||||
const [aiAlertCardItems, setAiAlertCardItems] = useState<AiAlertCardDeliveryItem[]>([]);
|
||||
const [aiAlertCardSummary, setAiAlertCardSummary] = useState<AiAlertCardDeliverySummary | null>(null);
|
||||
const [aiAlertLearningRegistry, setAiAlertLearningRegistry] =
|
||||
useState<AiAlertCardLearningRegistry | null>(null);
|
||||
const [aiAlertCardLoading, setAiAlertCardLoading] = useState(true);
|
||||
const [aiAlertCardError, setAiAlertCardError] = useState<string | null>(null);
|
||||
const [aiRouteStatus, setAiRouteStatus] = useState<AiRouteStatusResponse | null>(null);
|
||||
@@ -4437,15 +4560,18 @@ export default function RunsPage() {
|
||||
const aiAlertData: AiAlertCardsResponse = await aiAlertRes.json();
|
||||
setAiAlertCardItems(Array.isArray(aiAlertData.items) ? aiAlertData.items : []);
|
||||
setAiAlertCardSummary(aiAlertData.summary ?? null);
|
||||
setAiAlertLearningRegistry(aiAlertData.learning_registry ?? null);
|
||||
setAiAlertCardError(null);
|
||||
} else {
|
||||
setAiAlertCardItems([]);
|
||||
setAiAlertCardSummary(null);
|
||||
setAiAlertLearningRegistry(null);
|
||||
setAiAlertCardError(`端點尚未部署或暫時不可用(HTTP ${aiAlertRes.status})`);
|
||||
}
|
||||
} catch (aiAlertError) {
|
||||
setAiAlertCardItems([]);
|
||||
setAiAlertCardSummary(null);
|
||||
setAiAlertLearningRegistry(null);
|
||||
setAiAlertCardError("無法讀回 AI 事件卡送達狀態");
|
||||
} finally {
|
||||
setAiAlertCardLoading(false);
|
||||
@@ -4794,6 +4920,7 @@ export default function RunsPage() {
|
||||
<AiAlertCardDeliveryPanel
|
||||
items={aiAlertCardItems}
|
||||
summary={aiAlertCardSummary}
|
||||
learningRegistry={aiAlertLearningRegistry}
|
||||
loading={aiAlertCardLoading}
|
||||
error={aiAlertCardError}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user