feat(telegram): persist callback owner review snapshots
This commit is contained in:
@@ -96,6 +96,7 @@ class CallbackReplyItem(BaseModel):
|
|||||||
callback_reply: dict[str, Any]
|
callback_reply: dict[str, Any]
|
||||||
awooop_status_chain: dict[str, Any] | None = None
|
awooop_status_chain: dict[str, Any] | None = None
|
||||||
km_stale_completion_summary: dict[str, Any] | None = None
|
km_stale_completion_summary: dict[str, Any] | None = None
|
||||||
|
persisted_km_stale_completion_summary: dict[str, Any] | None = None
|
||||||
run_detail_href: str | None = None
|
run_detail_href: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -374,6 +374,8 @@ async def list_callback_replies(
|
|||||||
m.sent_at,
|
m.sent_at,
|
||||||
m.triggered_by_state,
|
m.triggered_by_state,
|
||||||
m.source_envelope -> 'callback_reply' AS callback_reply,
|
m.source_envelope -> 'callback_reply' AS callback_reply,
|
||||||
|
m.source_envelope -> 'km_stale_completion_summary'
|
||||||
|
AS persisted_km_stale_completion_summary,
|
||||||
r.agent_id,
|
r.agent_id,
|
||||||
r.state AS run_state,
|
r.state AS run_state,
|
||||||
r.created_at AS run_created_at
|
r.created_at AS run_created_at
|
||||||
@@ -1053,6 +1055,9 @@ def _callback_reply_event_item(row: Mapping[str, Any]) -> dict[str, Any]:
|
|||||||
"agent_id": row.get("agent_id"),
|
"agent_id": row.get("agent_id"),
|
||||||
"run_created_at": row.get("run_created_at"),
|
"run_created_at": row.get("run_created_at"),
|
||||||
"callback_reply": callback_reply,
|
"callback_reply": callback_reply,
|
||||||
|
"persisted_km_stale_completion_summary": _as_dict(
|
||||||
|
row.get("persisted_km_stale_completion_summary"),
|
||||||
|
) or None,
|
||||||
"run_detail_href": (
|
"run_detail_href": (
|
||||||
f"/awooop/runs/{run_id}?project_id={project_id}"
|
f"/awooop/runs/{run_id}?project_id={project_id}"
|
||||||
if run_id and project_id
|
if run_id and project_id
|
||||||
|
|||||||
@@ -824,6 +824,7 @@ def _callback_reply_source_envelope_extra(
|
|||||||
callback_action: str | None = None,
|
callback_action: str | None = None,
|
||||||
parse_mode: str | None = None,
|
parse_mode: str | None = None,
|
||||||
error: str | None = None,
|
error: str | None = None,
|
||||||
|
km_stale_completion_summary: dict[str, object] | None = None,
|
||||||
) -> dict[str, object] | None:
|
) -> dict[str, object] | None:
|
||||||
"""Build AwoooP metadata for Telegram detail/history callback replies."""
|
"""Build AwoooP metadata for Telegram detail/history callback replies."""
|
||||||
if not incident_id:
|
if not incident_id:
|
||||||
@@ -844,12 +845,68 @@ def _callback_reply_source_envelope_extra(
|
|||||||
if error:
|
if error:
|
||||||
callback_reply["error"] = _sanitize_telegram_error(error)[:300]
|
callback_reply["error"] = _sanitize_telegram_error(error)[:300]
|
||||||
|
|
||||||
return {
|
extra: dict[str, object] = {
|
||||||
"callback_reply": callback_reply,
|
"callback_reply": callback_reply,
|
||||||
"source_refs": {
|
"source_refs": {
|
||||||
"incident_ids": [incident_id],
|
"incident_ids": [incident_id],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
km_snapshot = _callback_reply_km_stale_completion_snapshot(
|
||||||
|
km_stale_completion_summary,
|
||||||
|
)
|
||||||
|
if km_snapshot:
|
||||||
|
extra["km_stale_completion_summary"] = km_snapshot
|
||||||
|
|
||||||
|
return extra
|
||||||
|
|
||||||
|
|
||||||
|
def _callback_reply_km_stale_completion_snapshot(
|
||||||
|
summary: dict[str, object] | None,
|
||||||
|
) -> dict[str, object] | None:
|
||||||
|
"""Persist a compact KM owner-review state snapshot with callback evidence."""
|
||||||
|
if not isinstance(summary, dict):
|
||||||
|
return None
|
||||||
|
|
||||||
|
snapshot: dict[str, object] = {
|
||||||
|
"schema_version": "km_stale_owner_review_callback_reply_snapshot_v1",
|
||||||
|
"source_schema_version": str(summary.get("schema_version") or ""),
|
||||||
|
"status": str(summary.get("status") or "observed"),
|
||||||
|
"incident_id": str(summary.get("incident_id") or ""),
|
||||||
|
"project_id": str(summary.get("project_id") or ""),
|
||||||
|
"missing_reason": str(summary.get("missing_reason") or ""),
|
||||||
|
"ready_count": _safe_int(summary.get("ready_count")),
|
||||||
|
"blocked_count": _safe_int(summary.get("blocked_count")),
|
||||||
|
"completed_count": _safe_int(summary.get("completed_count")),
|
||||||
|
"failed_count": _safe_int(summary.get("failed_count")),
|
||||||
|
"related_total": _safe_int(summary.get("related_total")),
|
||||||
|
"writes_on_read": bool(summary.get("writes_on_read")),
|
||||||
|
"batch_writes_allowed": bool(summary.get("batch_writes_allowed")),
|
||||||
|
"manual_review_required": bool(summary.get("manual_review_required", True)),
|
||||||
|
}
|
||||||
|
triage = _km_stale_completion_triage(summary)
|
||||||
|
if triage:
|
||||||
|
raw_supporting_agents = triage.get("supporting_agents")
|
||||||
|
supporting_agents = (
|
||||||
|
raw_supporting_agents
|
||||||
|
if isinstance(raw_supporting_agents, list)
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
snapshot["triage"] = {
|
||||||
|
"schema_version": str(triage.get("schema_version") or ""),
|
||||||
|
"flow_stage": str(triage.get("flow_stage") or ""),
|
||||||
|
"ai_lead_agent": str(triage.get("ai_lead_agent") or ""),
|
||||||
|
"supporting_agents": [
|
||||||
|
str(agent)
|
||||||
|
for agent in supporting_agents
|
||||||
|
if str(agent).strip()
|
||||||
|
][:5],
|
||||||
|
"automation_state": str(triage.get("automation_state") or ""),
|
||||||
|
"safe_to_auto_repair": bool(triage.get("safe_to_auto_repair")),
|
||||||
|
"blocking_reason": str(triage.get("blocking_reason") or ""),
|
||||||
|
"matching_strategy": str(triage.get("matching_strategy") or ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot
|
||||||
|
|
||||||
|
|
||||||
def _merge_outbound_source_envelope_extra(
|
def _merge_outbound_source_envelope_extra(
|
||||||
@@ -864,6 +921,10 @@ def _merge_outbound_source_envelope_extra(
|
|||||||
if isinstance(callback_reply, dict):
|
if isinstance(callback_reply, dict):
|
||||||
envelope["callback_reply"] = callback_reply
|
envelope["callback_reply"] = callback_reply
|
||||||
|
|
||||||
|
km_stale_completion_summary = extra.get("km_stale_completion_summary")
|
||||||
|
if isinstance(km_stale_completion_summary, dict):
|
||||||
|
envelope["km_stale_completion_summary"] = km_stale_completion_summary
|
||||||
|
|
||||||
extra_refs = extra.get("source_refs")
|
extra_refs = extra.get("source_refs")
|
||||||
if isinstance(extra_refs, dict):
|
if isinstance(extra_refs, dict):
|
||||||
source_refs = envelope.setdefault("source_refs", {})
|
source_refs = envelope.setdefault("source_refs", {})
|
||||||
@@ -6101,6 +6162,7 @@ class TelegramGateway:
|
|||||||
reply_markup=_awooop_runs_reply_markup(incident_id),
|
reply_markup=_awooop_runs_reply_markup(incident_id),
|
||||||
incident_id=incident_id,
|
incident_id=incident_id,
|
||||||
callback_action="detail",
|
callback_action="detail",
|
||||||
|
km_stale_completion_summary=km_completion_summary,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -6253,6 +6315,7 @@ class TelegramGateway:
|
|||||||
reply_markup=_awooop_runs_reply_markup(incident_id),
|
reply_markup=_awooop_runs_reply_markup(incident_id),
|
||||||
incident_id=incident_id,
|
incident_id=incident_id,
|
||||||
callback_action="history",
|
callback_action="history",
|
||||||
|
km_stale_completion_summary=km_completion_summary,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -6491,6 +6554,7 @@ class TelegramGateway:
|
|||||||
reply_markup: dict | None = None,
|
reply_markup: dict | None = None,
|
||||||
incident_id: str | None = None,
|
incident_id: str | None = None,
|
||||||
callback_action: str | None = None,
|
callback_action: str | None = None,
|
||||||
|
km_stale_completion_summary: dict[str, object] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Send a multi-line HTML message without cutting Telegram tags in half."""
|
"""Send a multi-line HTML message without cutting Telegram tags in half."""
|
||||||
chunks = _telegram_html_chunks(lines)
|
chunks = _telegram_html_chunks(lines)
|
||||||
@@ -6510,6 +6574,7 @@ class TelegramGateway:
|
|||||||
chunk_count=len(chunks),
|
chunk_count=len(chunks),
|
||||||
callback_action=callback_action,
|
callback_action=callback_action,
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
|
km_stale_completion_summary=km_stale_completion_summary,
|
||||||
)
|
)
|
||||||
if source_extra:
|
if source_extra:
|
||||||
payload[_AWOOOP_SOURCE_ENVELOPE_EXTRA_KEY] = source_extra
|
payload[_AWOOOP_SOURCE_ENVELOPE_EXTRA_KEY] = source_extra
|
||||||
@@ -6540,6 +6605,7 @@ class TelegramGateway:
|
|||||||
callback_action=callback_action,
|
callback_action=callback_action,
|
||||||
parse_mode="plain_text",
|
parse_mode="plain_text",
|
||||||
error=str(exc),
|
error=str(exc),
|
||||||
|
km_stale_completion_summary=km_stale_completion_summary,
|
||||||
)
|
)
|
||||||
if fallback_source_extra:
|
if fallback_source_extra:
|
||||||
fallback_payload[_AWOOOP_SOURCE_ENVELOPE_EXTRA_KEY] = fallback_source_extra
|
fallback_payload[_AWOOOP_SOURCE_ENVELOPE_EXTRA_KEY] = fallback_source_extra
|
||||||
@@ -6572,6 +6638,7 @@ class TelegramGateway:
|
|||||||
callback_action=callback_action,
|
callback_action=callback_action,
|
||||||
parse_mode="plain_text",
|
parse_mode="plain_text",
|
||||||
error=str(fallback_exc),
|
error=str(fallback_exc),
|
||||||
|
km_stale_completion_summary=km_stale_completion_summary,
|
||||||
)
|
)
|
||||||
if rescue_source_extra:
|
if rescue_source_extra:
|
||||||
rescue_payload[_AWOOOP_SOURCE_ENVELOPE_EXTRA_KEY] = rescue_source_extra
|
rescue_payload[_AWOOOP_SOURCE_ENVELOPE_EXTRA_KEY] = rescue_source_extra
|
||||||
|
|||||||
@@ -375,6 +375,15 @@ def test_callback_reply_event_item_surfaces_run_link_and_human_flag() -> None:
|
|||||||
"incident_id": "INC-20260513-79ED5E",
|
"incident_id": "INC-20260513-79ED5E",
|
||||||
"error": "HTTP error: 400",
|
"error": "HTTP error: 400",
|
||||||
},
|
},
|
||||||
|
"persisted_km_stale_completion_summary": {
|
||||||
|
"schema_version": "km_stale_owner_review_callback_reply_snapshot_v1",
|
||||||
|
"status": "no_related_owner_review",
|
||||||
|
"ready_count": 4,
|
||||||
|
"triage": {
|
||||||
|
"flow_stage": "callback_observed_owner_review_link_missing",
|
||||||
|
"ai_lead_agent": "Hermes",
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
assert item["status"] == "failed"
|
assert item["status"] == "failed"
|
||||||
@@ -385,6 +394,10 @@ def test_callback_reply_event_item_surfaces_run_link_and_human_flag() -> None:
|
|||||||
assert item["run_detail_href"] == (
|
assert item["run_detail_href"] == (
|
||||||
"/awooop/runs/5c0306e0-591a-5445-9a33-80f499426b38?project_id=awoooi"
|
"/awooop/runs/5c0306e0-591a-5445-9a33-80f499426b38?project_id=awoooi"
|
||||||
)
|
)
|
||||||
|
assert item["persisted_km_stale_completion_summary"]["ready_count"] == 4
|
||||||
|
assert item["persisted_km_stale_completion_summary"]["triage"]["ai_lead_agent"] == (
|
||||||
|
"Hermes"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_list_callback_replies_response_preserves_callback_evidence() -> None:
|
def test_list_callback_replies_response_preserves_callback_evidence() -> None:
|
||||||
@@ -444,6 +457,29 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None:
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"persisted_km_stale_completion_summary": {
|
||||||
|
"schema_version": (
|
||||||
|
"km_stale_owner_review_callback_reply_snapshot_v1"
|
||||||
|
),
|
||||||
|
"source_schema_version": (
|
||||||
|
"km_stale_owner_review_completion_callback_summary_v1"
|
||||||
|
),
|
||||||
|
"project_id": "awoooi",
|
||||||
|
"incident_id": "INC-20260513-79ED5E",
|
||||||
|
"status": "matched_owner_review",
|
||||||
|
"ready_count": 3,
|
||||||
|
"blocked_count": 1,
|
||||||
|
"completed_count": 2,
|
||||||
|
"failed_count": 0,
|
||||||
|
"batch_writes_allowed": False,
|
||||||
|
"manual_review_required": True,
|
||||||
|
"related_total": 1,
|
||||||
|
"triage": {
|
||||||
|
"flow_stage": "callback_observed_owner_review_link_missing",
|
||||||
|
"ai_lead_agent": "Hermes",
|
||||||
|
"automation_state": "manual_owner_review_required",
|
||||||
|
},
|
||||||
|
},
|
||||||
"run_detail_href": (
|
"run_detail_href": (
|
||||||
"/awooop/runs/5c0306e0-591a-5445-9a33-80f499426b38"
|
"/awooop/runs/5c0306e0-591a-5445-9a33-80f499426b38"
|
||||||
"?project_id=awoooi"
|
"?project_id=awoooi"
|
||||||
@@ -463,6 +499,9 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None:
|
|||||||
)
|
)
|
||||||
assert dumped["items"][0]["km_stale_completion_summary"]["ready_count"] == 3
|
assert dumped["items"][0]["km_stale_completion_summary"]["ready_count"] == 3
|
||||||
assert dumped["items"][0]["km_stale_completion_summary"]["related_total"] == 1
|
assert dumped["items"][0]["km_stale_completion_summary"]["related_total"] == 1
|
||||||
|
assert dumped["items"][0]["persisted_km_stale_completion_summary"]["triage"][
|
||||||
|
"ai_lead_agent"
|
||||||
|
] == "Hermes"
|
||||||
assert dumped["items"][0]["run_detail_href"].endswith("project_id=awoooi")
|
assert dumped["items"][0]["run_detail_href"].endswith("project_id=awoooi")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -329,6 +329,16 @@ async def test_send_request_strips_awooop_callback_metadata_before_telegram_api(
|
|||||||
"status": "callback_reply_sent",
|
"status": "callback_reply_sent",
|
||||||
"incident_id": "INC-20260513-79ED5E",
|
"incident_id": "INC-20260513-79ED5E",
|
||||||
},
|
},
|
||||||
|
"km_stale_completion_summary": {
|
||||||
|
"schema_version": (
|
||||||
|
"km_stale_owner_review_callback_reply_snapshot_v1"
|
||||||
|
),
|
||||||
|
"status": "no_related_owner_review",
|
||||||
|
"ready_count": 2,
|
||||||
|
"triage": {
|
||||||
|
"flow_stage": "callback_observed_owner_review_link_missing",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -339,6 +349,9 @@ async def test_send_request_strips_awooop_callback_metadata_before_telegram_api(
|
|||||||
assert captured["mirror"]["source_envelope_extra"]["callback_reply"]["status"] == (
|
assert captured["mirror"]["source_envelope_extra"]["callback_reply"]["status"] == (
|
||||||
"callback_reply_sent"
|
"callback_reply_sent"
|
||||||
)
|
)
|
||||||
|
assert captured["mirror"]["source_envelope_extra"][
|
||||||
|
"km_stale_completion_summary"
|
||||||
|
]["ready_count"] == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -394,17 +407,52 @@ async def test_send_html_line_message_marks_callback_reply_evidence(monkeypatch)
|
|||||||
reply_markup=reply_markup,
|
reply_markup=reply_markup,
|
||||||
incident_id="INC-20260514-F85F21",
|
incident_id="INC-20260514-F85F21",
|
||||||
callback_action="history",
|
callback_action="history",
|
||||||
|
km_stale_completion_summary={
|
||||||
|
"schema_version": "km_stale_owner_review_completion_callback_summary_v1",
|
||||||
|
"project_id": "awoooi",
|
||||||
|
"incident_id": "INC-20260514-F85F21",
|
||||||
|
"status": "no_related_owner_review",
|
||||||
|
"ready_count": 3,
|
||||||
|
"blocked_count": 1,
|
||||||
|
"completed_count": 2,
|
||||||
|
"failed_count": 0,
|
||||||
|
"writes_on_read": False,
|
||||||
|
"batch_writes_allowed": False,
|
||||||
|
"manual_review_required": True,
|
||||||
|
"related_total": 0,
|
||||||
|
"work_item": {
|
||||||
|
"triage": {
|
||||||
|
"schema_version": "km_stale_callback_owner_review_triage_v1",
|
||||||
|
"flow_stage": "callback_observed_owner_review_link_missing",
|
||||||
|
"ai_lead_agent": "Hermes",
|
||||||
|
"supporting_agents": ["OpenClaw", "ElephantAlpha"],
|
||||||
|
"automation_state": "manual_owner_review_required",
|
||||||
|
"safe_to_auto_repair": False,
|
||||||
|
"blocking_reason": "no_matching_completion_item",
|
||||||
|
"matching_strategy": "related_incident_id_exact_match",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
first_extra = sent_requests[0][1]["_awooop_source_envelope_extra"]["callback_reply"]
|
first_source_extra = sent_requests[0][1]["_awooop_source_envelope_extra"]
|
||||||
fallback_extra = sent_requests[1][1]["_awooop_source_envelope_extra"]["callback_reply"]
|
fallback_source_extra = sent_requests[1][1]["_awooop_source_envelope_extra"]
|
||||||
|
first_extra = first_source_extra["callback_reply"]
|
||||||
|
fallback_extra = fallback_source_extra["callback_reply"]
|
||||||
|
|
||||||
assert first_extra["status"] == "callback_reply_sent"
|
assert first_extra["status"] == "callback_reply_sent"
|
||||||
assert first_extra["action"] == "history"
|
assert first_extra["action"] == "history"
|
||||||
assert first_extra["parse_mode"] == "HTML"
|
assert first_extra["parse_mode"] == "HTML"
|
||||||
|
assert first_source_extra["km_stale_completion_summary"]["ready_count"] == 3
|
||||||
|
assert first_source_extra["km_stale_completion_summary"]["triage"]["flow_stage"] == (
|
||||||
|
"callback_observed_owner_review_link_missing"
|
||||||
|
)
|
||||||
assert fallback_extra["status"] == "callback_reply_fallback_sent"
|
assert fallback_extra["status"] == "callback_reply_fallback_sent"
|
||||||
assert fallback_extra["incident_id"] == "INC-20260514-F85F21"
|
assert fallback_extra["incident_id"] == "INC-20260514-F85F21"
|
||||||
assert fallback_extra["parse_mode"] == "plain_text"
|
assert fallback_extra["parse_mode"] == "plain_text"
|
||||||
|
assert fallback_source_extra["km_stale_completion_summary"]["triage"][
|
||||||
|
"ai_lead_agent"
|
||||||
|
] == "Hermes"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -2805,6 +2805,10 @@
|
|||||||
"noRelated": "This incident has no matching owner-review completion item yet.",
|
"noRelated": "This incident has no matching owner-review completion item yet.",
|
||||||
"fetchFailed": "KM owner-review summary failed to load: {reason}",
|
"fetchFailed": "KM owner-review summary failed to load: {reason}",
|
||||||
"openWorkItem": "Open work item",
|
"openWorkItem": "Open work item",
|
||||||
|
"snapshotTitle": "Callback-time Evidence Snapshot",
|
||||||
|
"snapshotStatus": "Snapshot status: {status}; ready {ready} / blocked {blocked} / completed {completed} / failed {failed}",
|
||||||
|
"snapshotFlow": "Snapshot flow: {stage}; match: {strategy}",
|
||||||
|
"snapshotAutomation": "Snapshot automation: lead {lead}; state {state}; safe auto-repair={safe}; blocker {blocker}",
|
||||||
"triageFlow": "Flow: {stage}; match: {strategy}",
|
"triageFlow": "Flow: {stage}; match: {strategy}",
|
||||||
"triageAgents": "Lead: {lead}; support: {support}",
|
"triageAgents": "Lead: {lead}; support: {support}",
|
||||||
"triageAutomation": "Automation: {state}; safe auto-repair={safe}",
|
"triageAutomation": "Automation: {state}; safe auto-repair={safe}",
|
||||||
|
|||||||
@@ -2806,6 +2806,10 @@
|
|||||||
"noRelated": "本 Incident 尚未對到 owner-review completion item。",
|
"noRelated": "本 Incident 尚未對到 owner-review completion item。",
|
||||||
"fetchFailed": "KM owner-review 摘要讀取失敗:{reason}",
|
"fetchFailed": "KM owner-review 摘要讀取失敗:{reason}",
|
||||||
"openWorkItem": "開啟工作項",
|
"openWorkItem": "開啟工作項",
|
||||||
|
"snapshotTitle": "Callback 當下 Evidence Snapshot",
|
||||||
|
"snapshotStatus": "當下狀態:{status};ready {ready} / blocked {blocked} / completed {completed} / failed {failed}",
|
||||||
|
"snapshotFlow": "當下流程:{stage};匹配:{strategy}",
|
||||||
|
"snapshotAutomation": "當下自動化:主責 {lead};狀態 {state};可安全自動修復={safe};卡點 {blocker}",
|
||||||
"triageFlow": "流程:{stage};匹配:{strategy}",
|
"triageFlow": "流程:{stage};匹配:{strategy}",
|
||||||
"triageAgents": "主責:{lead};協作:{support}",
|
"triageAgents": "主責:{lead};協作:{support}",
|
||||||
"triageAutomation": "自動化:{state};可安全自動修復={safe}",
|
"triageAutomation": "自動化:{state};可安全自動修復={safe}",
|
||||||
|
|||||||
@@ -328,6 +328,7 @@ interface KmStaleCallbackOwnerReviewTriage {
|
|||||||
|
|
||||||
interface KmStaleCompletionSummary {
|
interface KmStaleCompletionSummary {
|
||||||
schema_version?: string;
|
schema_version?: string;
|
||||||
|
source_schema_version?: string | null;
|
||||||
project_id?: string | null;
|
project_id?: string | null;
|
||||||
incident_id?: string | null;
|
incident_id?: string | null;
|
||||||
status?: string | null;
|
status?: string | null;
|
||||||
@@ -346,6 +347,7 @@ interface KmStaleCompletionSummary {
|
|||||||
related_total?: number;
|
related_total?: number;
|
||||||
related_items?: KmStaleCompletionSummaryItem[];
|
related_items?: KmStaleCompletionSummaryItem[];
|
||||||
work_item?: KmStaleCallbackOwnerReviewWorkItem | null;
|
work_item?: KmStaleCallbackOwnerReviewWorkItem | null;
|
||||||
|
triage?: KmStaleCallbackOwnerReviewTriage | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CallbackReplyEvent {
|
interface CallbackReplyEvent {
|
||||||
@@ -369,6 +371,7 @@ interface CallbackReplyEvent {
|
|||||||
run_detail_href?: string | null;
|
run_detail_href?: string | null;
|
||||||
awooop_status_chain?: AwoooPStatusChain | null;
|
awooop_status_chain?: AwoooPStatusChain | null;
|
||||||
km_stale_completion_summary?: KmStaleCompletionSummary | null;
|
km_stale_completion_summary?: KmStaleCompletionSummary | null;
|
||||||
|
persisted_km_stale_completion_summary?: KmStaleCompletionSummary | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CallbackRepliesResponse {
|
interface CallbackRepliesResponse {
|
||||||
@@ -1587,6 +1590,58 @@ function CallbackKmCompletionSummary({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CallbackKmCompletionSnapshot({
|
||||||
|
snapshot,
|
||||||
|
}: {
|
||||||
|
snapshot?: KmStaleCompletionSummary | null;
|
||||||
|
}) {
|
||||||
|
const t = useTranslations("awooop.callbackReply.events.kmCompletion");
|
||||||
|
if (!snapshot) return null;
|
||||||
|
|
||||||
|
const statusKey = normalizeKmCompletionStatus(snapshot.status);
|
||||||
|
const triage = snapshot.triage ?? null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-3 border-t border-[#e0ddd4] pt-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ShieldCheck className="h-3.5 w-3.5 text-[#1f5b9b]" aria-hidden="true" />
|
||||||
|
<p className="text-xs font-semibold text-[#141413]">
|
||||||
|
{t("snapshotTitle")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 grid gap-1 text-xs leading-5 text-[#5f5b52]">
|
||||||
|
<p>
|
||||||
|
{t("snapshotStatus", {
|
||||||
|
status: t(`statuses.${statusKey}` as never),
|
||||||
|
ready: snapshot.ready_count ?? 0,
|
||||||
|
blocked: snapshot.blocked_count ?? 0,
|
||||||
|
completed: snapshot.completed_count ?? 0,
|
||||||
|
failed: snapshot.failed_count ?? 0,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{triage ? (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
{t("snapshotFlow", {
|
||||||
|
stage: triage.flow_stage ?? "--",
|
||||||
|
strategy: triage.matching_strategy ?? "--",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{t("snapshotAutomation", {
|
||||||
|
lead: triage.ai_lead_agent ?? "--",
|
||||||
|
state: triage.automation_state ?? "--",
|
||||||
|
safe: String(triage.safe_to_auto_repair ?? false),
|
||||||
|
blocker: triage.blocking_reason ?? "--",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function CallbackReplyEvidencePanel({
|
function CallbackReplyEvidencePanel({
|
||||||
events,
|
events,
|
||||||
total,
|
total,
|
||||||
@@ -1680,6 +1735,9 @@ function CallbackReplyEvidencePanel({
|
|||||||
<CallbackKmCompletionSummary
|
<CallbackKmCompletionSummary
|
||||||
summary={event.km_stale_completion_summary}
|
summary={event.km_stale_completion_summary}
|
||||||
/>
|
/>
|
||||||
|
<CallbackKmCompletionSnapshot
|
||||||
|
snapshot={event.persisted_km_stale_completion_summary}
|
||||||
|
/>
|
||||||
<Link
|
<Link
|
||||||
href={runHref as never}
|
href={runHref as never}
|
||||||
className="mt-3 inline-flex items-center gap-1.5 border border-[#d8d3c7] bg-[#faf9f3] px-2 py-1 text-xs font-semibold text-[#2e2b26] hover:border-[#1f6feb] hover:bg-[#edf4ff] hover:text-[#0f4fa8]"
|
className="mt-3 inline-flex items-center gap-1.5 border border-[#d8d3c7] bg-[#faf9f3] px-2 py-1 text-xs font-semibold text-[#2e2b26] hover:border-[#1f6feb] hover:bg-[#edf4ff] hover:text-[#0f4fa8]"
|
||||||
|
|||||||
Reference in New Issue
Block a user