feat(telegram): surface awooop agent evidence chain
All checks were successful
CD Pipeline / tests (push) Successful in 1m15s
Code Review / ai-code-review (push) Successful in 12s
CD Pipeline / build-and-deploy (push) Successful in 3m28s
CD Pipeline / post-deploy-checks (push) Successful in 1m38s

This commit is contained in:
Your Name
2026-05-25 16:35:27 +08:00
parent 2e0d7f65c1
commit f84482299b
4 changed files with 342 additions and 1 deletions

View File

@@ -433,6 +433,164 @@ def _format_awooop_status_chain_lines(
return lines
def _first_mapping_item(values: object) -> dict[str, object]:
if not isinstance(values, list):
return {}
for item in values:
if isinstance(item, dict):
return item
return {}
def _provider_correlation_summary(source_correlation: dict[str, object] | None) -> str:
providers = (
source_correlation.get("providers")
if isinstance(source_correlation, dict)
and isinstance(source_correlation.get("providers"), dict)
else {}
)
parts: list[str] = []
for provider in ("sentry", "signoz"):
item = providers.get(provider) if isinstance(providers, dict) else {}
if not isinstance(item, dict):
item = {}
parts.append(
f"{provider} "
f"{_safe_int(item.get('direct_ref_total'))}/"
f"{_safe_int(item.get('candidate_total'))}/"
f"{_safe_int(item.get('applied_link_total'))}"
)
return " · ".join(parts)
def _format_awooop_agent_evidence_lines(
*,
truth_chain: dict[str, object] | None = None,
remediation_history: dict[str, object] | None = None,
source_correlation: dict[str, object] | None = None,
) -> list[str]:
"""Render the same agent evidence matrix used by Operator UI for Telegram."""
if not truth_chain and not remediation_history and not source_correlation:
return []
quality = (
truth_chain.get("automation_quality")
if isinstance(truth_chain, dict)
and isinstance(truth_chain.get("automation_quality"), dict)
else {}
)
facts = quality.get("facts") if isinstance(quality.get("facts"), dict) else {}
latest = _latest_remediation_history_item(remediation_history)
mcp = _callback_reply_awooop_mcp_snapshot(truth_chain)
execution = _callback_reply_awooop_execution_snapshot(truth_chain)
gateway = mcp.get("gateway") if isinstance(mcp.get("gateway"), dict) else {}
legacy = mcp.get("legacy") if isinstance(mcp.get("legacy"), dict) else {}
top_tool = _first_mapping_item(mcp.get("top_tools"))
ansible = (
execution.get("ansible")
if isinstance(execution.get("ansible"), dict)
else {}
)
candidate_playbook = _first_mapping_item(ansible.get("candidate_playbooks"))
playbook_paths = execution.get("playbook_paths")
playbook_ids = execution.get("playbook_ids")
selected_playbook = ""
if isinstance(playbook_paths, list) and playbook_paths:
selected_playbook = str(playbook_paths[0] or "")
elif isinstance(playbook_ids, list) and playbook_ids:
selected_playbook = str(playbook_ids[0] or "")
else:
selected_playbook = str(
ansible.get("latest_playbook_path")
or candidate_playbook.get("playbook_path")
or candidate_playbook.get("catalog_id")
or "--"
)
source_status = "missing"
direct_total = 0
candidate_total = 0
applied_total = 0
if isinstance(source_correlation, dict):
source_status = str(
source_correlation.get("verification_status")
or source_correlation.get("status")
or "missing"
)
direct_total = _safe_int(source_correlation.get("direct_ref_total"))
candidate_total = _safe_int(source_correlation.get("candidate_total"))
applied_total = _safe_int(source_correlation.get("applied_link_total"))
verification = (
facts.get("verification_result")
or latest.get("verification_result_preview")
or "missing"
)
auto_repair_records = _safe_int(facts.get("auto_repair_execution_records"))
operation_records = _safe_int(facts.get("automation_operation_records"))
km_entries = _safe_int(facts.get("knowledge_entries"))
latest_executor = str(execution.get("latest_executor") or "--")
latest_status = str(execution.get("latest_status") or "--")
latest_operation = str(execution.get("latest_operation_type") or "--")
latest_action = str(execution.get("latest_action") or "--")
top_tool_name = str(top_tool.get("tool_name") or "--")
return [
"",
"🧩 <b>AI Agent 證據鏈</b>",
(
"MCP / 自建 MCP: "
f"Gateway <code>{_safe_int(gateway.get('success'))}/"
f"{_safe_int(gateway.get('total'))}</code>"
f"失敗 <code>{_safe_int(gateway.get('failed'))}</code>"
f"阻擋 <code>{_safe_int(gateway.get('blocked'))}</code>"
),
(
"MCP top: "
f"<code>{html.escape(top_tool_name)}</code> | "
f"first-class <code>{_safe_int(gateway.get('first_class_total'))}</code> / "
f"legacy <code>{_safe_int(legacy.get('total'))}</code> / "
f"policy <code>{_safe_int(gateway.get('policy_enforced_total'))}</code>"
),
(
"Sentry/SigNoz: "
f"<code>{html.escape(source_status)}</code> | "
f"direct <code>{direct_total}</code> / "
f"candidate <code>{candidate_total}</code> / "
f"applied <code>{applied_total}</code>"
),
(
"Provider: "
f"<code>{html.escape(_provider_correlation_summary(source_correlation))}</code>"
),
(
"Executor: "
f"<code>{html.escape(latest_executor)}/{html.escape(latest_status)}</code> | "
f"op <code>{html.escape(latest_operation)}</code> / "
f"action <code>{html.escape(latest_action)}</code> / "
f"ops <code>{_safe_int(execution.get('operation_total'))}</code>"
),
(
"PlayBook / Ansible: "
f"<code>{html.escape(selected_playbook)}</code> | "
f"ansible <code>{html.escape(_bool_code(ansible.get('considered'), unknown_when_none=True))}</code> / "
f"candidates <code>{_safe_int(ansible.get('candidate_count'))}</code> / "
f"check-mode <code>{html.escape(_bool_code(ansible.get('latest_check_mode'), unknown_when_none=True))}</code> / "
f"status <code>{html.escape(str(ansible.get('latest_status') or '--'))}</code>"
),
(
"KM / Learning: "
f"KM <code>{km_entries}</code> / "
f"AutoRepair <code>{auto_repair_records}</code> / "
f"Ops <code>{operation_records}</code> | "
f"verify <code>{html.escape(str(verification))}</code>"
),
]
def _format_km_stale_completion_lines(summary: dict[str, object] | None) -> list[str]:
"""Render KM owner-review completion state for Telegram detail/history replies."""
if not summary:
@@ -6545,6 +6703,12 @@ class TelegramGateway:
error=str(source_exc),
)
lines += _format_awooop_agent_evidence_lines(
truth_chain=truth_chain,
remediation_history=remediation_history,
source_correlation=source_correlation,
)
awooop_status_chain_snapshot = _callback_reply_awooop_status_chain_snapshot(
incident_id=incident_id,
truth_chain=truth_chain,
@@ -6724,6 +6888,12 @@ class TelegramGateway:
error=str(source_exc),
)
lines += _format_awooop_agent_evidence_lines(
truth_chain=truth_chain,
remediation_history=remediation_history,
source_correlation=source_correlation,
)
awooop_status_chain_snapshot = _callback_reply_awooop_status_chain_snapshot(
incident_id=incident_id,
truth_chain=truth_chain,