feat(awooop): surface gateway summary in details
This commit is contained in:
@@ -28,6 +28,7 @@ from src.db.awooop_models import (
|
||||
)
|
||||
from src.db.base import get_db_context
|
||||
from src.services.audit_sink import write_audit
|
||||
from src.services.awooop_truth_chain_service import _summarize_gateway_mcp
|
||||
from src.services.awooop_approval_token import issue_approval_token, record_approval
|
||||
from src.services.run_state_machine import transition
|
||||
|
||||
@@ -240,6 +241,17 @@ def _outbound_timeline_title(
|
||||
return f"{channel}:{fallback}"
|
||||
|
||||
|
||||
def _mcp_gateway_summary_row(row: AwoooPMcpGatewayAudit) -> dict[str, Any]:
|
||||
"""Convert SQLAlchemy audit rows into the truth-chain summary shape."""
|
||||
return {
|
||||
"agent_id": row.agent_id,
|
||||
"tool_name": row.tool_name,
|
||||
"result_status": row.result_status,
|
||||
"block_gate": row.block_gate,
|
||||
"gate_result": row.gate_result or {},
|
||||
}
|
||||
|
||||
|
||||
async def get_run_detail(
|
||||
run_id: str,
|
||||
project_id: str | None = None,
|
||||
@@ -373,18 +385,27 @@ async def get_run_detail(
|
||||
for row in outbound_messages
|
||||
]
|
||||
|
||||
mcp_items = [
|
||||
{
|
||||
def _mcp_item(row: AwoooPMcpGatewayAudit) -> dict[str, Any]:
|
||||
gate_result = row.gate_result if isinstance(row.gate_result, dict) else {}
|
||||
return {
|
||||
"call_id": row.call_id,
|
||||
"agent_id": row.agent_id,
|
||||
"tool_name": row.tool_name,
|
||||
"result_status": row.result_status,
|
||||
"block_gate": row.block_gate,
|
||||
"block_reason": row.block_reason,
|
||||
"latency_ms": row.latency_ms,
|
||||
"created_at": row.created_at,
|
||||
"required_scope": gate_result.get("required_scope"),
|
||||
"policy_enforced": gate_result.get("policy_enforced"),
|
||||
"is_shadow": gate_result.get("is_shadow"),
|
||||
"gate_result": gate_result,
|
||||
}
|
||||
for row in mcp_calls
|
||||
]
|
||||
|
||||
mcp_items = [_mcp_item(row) for row in mcp_calls]
|
||||
mcp_gateway_summary = _summarize_gateway_mcp([
|
||||
_mcp_gateway_summary_row(row) for row in mcp_calls
|
||||
])
|
||||
|
||||
timeline: list[dict[str, Any]] = [
|
||||
_timeline_item(
|
||||
@@ -433,15 +454,28 @@ async def get_run_detail(
|
||||
)
|
||||
)
|
||||
for row in mcp_calls:
|
||||
gate_result = row.gate_result if isinstance(row.gate_result, dict) else {}
|
||||
scope = gate_result.get("required_scope")
|
||||
policy_enforced = gate_result.get("policy_enforced")
|
||||
summary = row.block_reason
|
||||
if summary is None:
|
||||
summary = (
|
||||
f"agent={row.agent_id or 'unknown'}"
|
||||
f" scope={scope or 'unknown'}"
|
||||
f" policy_enforced={policy_enforced}"
|
||||
)
|
||||
timeline.append(
|
||||
_timeline_item(
|
||||
ts=row.created_at,
|
||||
kind="mcp",
|
||||
title=f"MCP: {row.tool_name}",
|
||||
status=row.result_status,
|
||||
summary=row.block_reason,
|
||||
summary=summary,
|
||||
metadata={
|
||||
"agent_id": row.agent_id,
|
||||
"block_gate": row.block_gate,
|
||||
"required_scope": scope,
|
||||
"policy_enforced": policy_enforced,
|
||||
"latency_ms": row.latency_ms,
|
||||
},
|
||||
)
|
||||
@@ -487,6 +521,7 @@ async def get_run_detail(
|
||||
"inbound_events": inbound_items,
|
||||
"outbound_messages": outbound_items,
|
||||
"mcp_calls": mcp_items,
|
||||
"mcp_gateway": mcp_gateway_summary,
|
||||
"timeline": timeline,
|
||||
"counts": {
|
||||
"steps": len(step_items),
|
||||
|
||||
@@ -71,6 +71,62 @@ _TELEGRAM_BOT_URL_RE = re.compile(r"(api\.telegram\.org/bot)[^/\s]+")
|
||||
_INCIDENT_ID_RE = re.compile(r"\bINC-\d{8}-[A-Z0-9]{4,}\b")
|
||||
|
||||
|
||||
def _top_gateway_bucket(
|
||||
buckets: list[dict[str, object]],
|
||||
field: str,
|
||||
) -> str | None:
|
||||
if not buckets:
|
||||
return None
|
||||
top = max(buckets, key=lambda row: int(row.get("total") or 0))
|
||||
value = top.get(field)
|
||||
if value is None:
|
||||
return None
|
||||
return f"{value} ({top.get('total', 0)})"
|
||||
|
||||
|
||||
def _format_gateway_summary_lines(summary: dict[str, object] | None) -> list[str]:
|
||||
if not summary or int(summary.get("total") or 0) <= 0:
|
||||
return []
|
||||
|
||||
by_agent = summary.get("by_agent") if isinstance(summary.get("by_agent"), list) else []
|
||||
by_tool = summary.get("by_tool") if isinstance(summary.get("by_tool"), list) else []
|
||||
by_scope = summary.get("by_scope") if isinstance(summary.get("by_scope"), list) else []
|
||||
blockers = summary.get("blockers") if isinstance(summary.get("blockers"), list) else []
|
||||
|
||||
lines = [
|
||||
"",
|
||||
"🛡️ <b>MCP Gateway</b>",
|
||||
(
|
||||
"階段: "
|
||||
f"<code>{html.escape(str(summary.get('stage') or 'unknown'))}</code>"
|
||||
" / "
|
||||
f"<code>{html.escape(str(summary.get('stage_status') or 'unknown'))}</code>"
|
||||
),
|
||||
(
|
||||
"治理: "
|
||||
f"first-class <code>{int(summary.get('first_class_total') or 0)}</code> / "
|
||||
f"policy <code>{int(summary.get('policy_enforced_total') or 0)}</code> / "
|
||||
f"legacy <code>{int(summary.get('legacy_bridge_total') or 0)}</code>"
|
||||
),
|
||||
]
|
||||
|
||||
agent = _top_gateway_bucket(by_agent, "agent_id")
|
||||
tool = _top_gateway_bucket(by_tool, "tool_name")
|
||||
scope = _top_gateway_bucket(by_scope, "required_scope")
|
||||
if agent:
|
||||
lines.append(f"Agent: <code>{html.escape(agent)}</code>")
|
||||
if tool:
|
||||
lines.append(f"Tool: <code>{html.escape(tool)}</code>")
|
||||
if scope:
|
||||
lines.append(f"Scope: <code>{html.escape(scope)}</code>")
|
||||
if blockers:
|
||||
lines.append(
|
||||
"卡點: "
|
||||
+ html.escape(", ".join(str(item) for item in blockers[:3]))
|
||||
)
|
||||
return lines
|
||||
|
||||
|
||||
def _sanitize_telegram_error(text: str) -> str:
|
||||
"""遮蔽 Telegram Bot URL 中的 token,避免例外字串污染 log / trace。"""
|
||||
return _TELEGRAM_BOT_URL_RE.sub(r"\1<redacted>", text)
|
||||
@@ -5064,6 +5120,25 @@ class TelegramGateway:
|
||||
+ html.escape(", ".join(mismatch_codes[:4]))
|
||||
)
|
||||
|
||||
try:
|
||||
from src.services.awooop_truth_chain_service import fetch_truth_chain
|
||||
|
||||
truth_chain = await fetch_truth_chain(
|
||||
source_id=incident_id,
|
||||
project_id=getattr(incident, "project_id", None) or "awoooi",
|
||||
)
|
||||
gateway_summary = (
|
||||
(truth_chain.get("mcp") or {})
|
||||
.get("awooop_gateway")
|
||||
)
|
||||
lines += _format_gateway_summary_lines(gateway_summary)
|
||||
except Exception as truth_exc:
|
||||
logger.warning(
|
||||
"incident_detail_truth_chain_summary_failed",
|
||||
incident_id=incident_id,
|
||||
error=str(truth_exc),
|
||||
)
|
||||
|
||||
await self.send_notification("\n".join(lines))
|
||||
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user