feat(telegram): surface awooop agent evidence chain
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user