feat(awooop): surface legacy mcp evidence in run detail
All checks were successful
Code Review / ai-code-review (push) Successful in 10s
CD Pipeline / tests (push) Successful in 1m10s
CD Pipeline / build-and-deploy (push) Successful in 4m7s
CD Pipeline / post-deploy-checks (push) Successful in 1m57s

This commit is contained in:
Your Name
2026-05-18 09:16:59 +08:00
parent bc701b8fd3
commit 902593f775
6 changed files with 245 additions and 6 deletions

View File

@@ -29,9 +29,13 @@ from src.db.awooop_models import (
AwoooPRunStepJournal,
)
from src.db.base import get_db_context
from src.db.models import MCPAuditLog
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.awooop_truth_chain_service import (
_summarize_gateway_mcp,
_summarize_mcp,
)
from src.services.run_state_machine import transition
logger = structlog.get_logger(__name__)
@@ -458,6 +462,24 @@ def _remediation_timeline_summary(item: dict[str, Any]) -> str:
)[:500]
def _legacy_mcp_timeline_status(record: dict[str, Any]) -> str:
if record.get("success") is True:
return "success"
if record.get("success") is False:
return "failed"
return "warning"
def _legacy_mcp_timeline_summary(record: dict[str, Any]) -> str:
return (
f"incident={record.get('incident_id') or '--'} "
f"agent={record.get('agent_role') or '--'} "
f"node={record.get('flywheel_node') or '--'} "
f"duration_ms={record.get('duration_ms') if record.get('duration_ms') is not None else '--'} "
f"error={record.get('error_message') or '--'}"
)[:500]
def _run_remediation_list_summary(
*,
run: AwoooPRunState,
@@ -706,6 +728,60 @@ async def _fetch_run_remediation_history(
}
def _legacy_mcp_record(row: MCPAuditLog) -> dict[str, Any]:
return {
"id": row.id,
"session_id": row.session_id,
"flywheel_node": row.flywheel_node,
"mcp_server": row.mcp_server,
"tool_name": row.tool_name,
"duration_ms": row.duration_ms,
"success": row.success,
"error_message": row.error_message,
"incident_id": row.incident_id,
"agent_role": row.agent_role,
"created_at": row.created_at,
}
async def _fetch_run_legacy_mcp_history(
incident_ids: list[str],
*,
limit: int = _MAX_TIMELINE_ITEMS,
) -> dict[str, Any]:
"""Fetch legacy/self-built MCP audit rows linked through incident ids."""
if not incident_ids:
return {
"schema_version": "awooop_run_legacy_mcp_evidence_v1",
"source": "mcp_audit_log",
"incident_ids": [],
"total": 0,
"limit": limit,
"records": [],
"summary": _summarize_mcp([]),
}
async with get_db_context("awoooi") as db:
result = await db.execute(
select(MCPAuditLog)
.where(MCPAuditLog.incident_id.in_(incident_ids))
.order_by(MCPAuditLog.created_at.desc())
.limit(limit)
)
rows = list(result.scalars().all())
records = [_legacy_mcp_record(row) for row in rows]
return {
"schema_version": "awooop_run_legacy_mcp_evidence_v1",
"source": "mcp_audit_log",
"incident_ids": incident_ids,
"total": len(records),
"limit": limit,
"records": records,
"summary": _summarize_mcp(records),
}
async def get_run_detail(
run_id: str,
project_id: str | None = None,
@@ -865,6 +941,7 @@ async def get_run_detail(
inbound_events=inbound_events,
outbound_messages=outbound_messages,
)
legacy_mcp_history = await _fetch_run_legacy_mcp_history(incident_ids)
remediation_history = await _fetch_run_remediation_history(incident_ids)
timeline: list[dict[str, Any]] = [
@@ -940,6 +1017,32 @@ async def get_run_detail(
},
)
)
for record in legacy_mcp_history.get("records", []):
if not isinstance(record, dict):
continue
tool_route = "/".join(
part
for part in (
str(record.get("mcp_server") or ""),
str(record.get("tool_name") or ""),
)
if part
) or "unknown"
timeline.append(
_timeline_item(
ts=record.get("created_at"),
kind="mcp",
title=f"Legacy MCP: {tool_route}",
status=_legacy_mcp_timeline_status(record),
summary=_legacy_mcp_timeline_summary(record),
metadata={
"incident_id": record.get("incident_id"),
"agent_role": record.get("agent_role"),
"flywheel_node": record.get("flywheel_node"),
"history_source": "mcp_audit_log",
},
)
)
for item in remediation_history.get("items", []):
if not isinstance(item, dict):
continue
@@ -1002,6 +1105,7 @@ async def get_run_detail(
"outbound_messages": outbound_items,
"mcp_calls": mcp_items,
"mcp_gateway": mcp_gateway_summary,
"mcp_legacy": legacy_mcp_history,
"remediation_history": remediation_history,
"timeline": timeline,
"counts": {
@@ -1009,6 +1113,7 @@ async def get_run_detail(
"inbound_events": len(inbound_items),
"outbound_messages": len(outbound_items),
"mcp_calls": len(mcp_items),
"legacy_mcp_calls": legacy_mcp_history.get("total", 0),
"remediation_history": remediation_history.get("total", 0),
"timeline": len(timeline),
},