diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py
index 286300c8..ae8673f1 100644
--- a/apps/api/src/services/telegram_gateway.py
+++ b/apps/api/src/services/telegram_gateway.py
@@ -479,9 +479,79 @@ def _format_km_stale_completion_lines(summary: dict[str, object] | None) -> list
else:
lines.append("此事件: no_related_owner_review")
lines.append("下一步: review_awooop_completion_queue")
+ triage = _km_stale_completion_triage(summary)
+ if triage:
+ support = triage.get("supporting_agents")
+ support_text = (
+ " / ".join(str(item) for item in support[:3])
+ if isinstance(support, list)
+ else "--"
+ )
+ lines += [
+ (
+ "流程: "
+ f"{html.escape(str(triage.get('flow_stage') or '--'))}"
+ " / 匹配 "
+ f"{html.escape(str(triage.get('matching_strategy') or '--'))}"
+ ),
+ (
+ "主責: "
+ f"{html.escape(str(triage.get('ai_lead_agent') or '--'))}"
+ " / 協作 "
+ f"{html.escape(support_text)}"
+ ),
+ (
+ "自動化: "
+ f"{html.escape(str(triage.get('automation_state') or '--'))}"
+ " / safe-auto "
+ f"{html.escape(_bool_code(triage.get('safe_to_auto_repair')))}"
+ ),
+ (
+ "卡點: "
+ f"{html.escape(str(triage.get('blocking_reason') or '--'))}"
+ ),
+ ]
return lines
+def _km_stale_completion_triage(
+ summary: dict[str, object],
+) -> dict[str, object] | None:
+ triage = summary.get("triage")
+ if isinstance(triage, dict):
+ return triage
+ work_item = summary.get("work_item")
+ if isinstance(work_item, dict) and isinstance(work_item.get("triage"), dict):
+ return work_item["triage"]
+ return None
+
+
+def _build_km_stale_callback_owner_review_triage(
+ *,
+ reason: str,
+) -> dict[str, object]:
+ return {
+ "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": reason,
+ "matching_strategy": "related_incident_id_exact_match",
+ "already_done": [
+ "callback_reply_persisted",
+ "completion_queue_checked",
+ "telegram_detail_history_rendered",
+ ],
+ "next_actions": [
+ "review_awooop_callback_work_item",
+ "queue_matching_km_stale_candidate",
+ "complete_owner_review_after_owner_approval",
+ ],
+ }
+
+
async def _fetch_km_stale_completion_summary_for_incident(
*,
incident_id: str,
@@ -508,11 +578,33 @@ async def _fetch_km_stale_completion_summary_for_incident(
for item in queue.items
if item.related_incident_id == incident_id
]
+ status_value = "matched_owner_review" if items else "no_related_owner_review"
+ reason = None if items else "no_matching_completion_item"
+ triage = (
+ None
+ if items
+ else _build_km_stale_callback_owner_review_triage(reason=reason)
+ )
+ work_item = None
+ if triage:
+ work_item = {
+ "schema_version": "km_stale_callback_owner_review_work_item_v1",
+ "kind": "km_stale_callback_owner_review",
+ "status": "open",
+ "incident_id": incident_id,
+ "reason": reason,
+ "next_step": "review_or_queue_km_owner_review",
+ "triage": triage,
+ "writes_on_read": False,
+ "manual_review_required": True,
+ "batch_writes_allowed": False,
+ }
return {
"schema_version": "km_stale_owner_review_completion_telegram_summary_v1",
- "status": "ok",
+ "status": status_value,
"project_id": project_id or "awoooi",
"incident_id": incident_id,
+ "missing_reason": reason,
"total": queue.total,
"returned": queue.returned,
"pending_count": queue.pending_count,
@@ -524,6 +616,8 @@ async def _fetch_km_stale_completion_summary_for_incident(
"batch_writes_allowed": queue.batch_writes_allowed,
"manual_review_required": queue.manual_review_required,
"items": items[:5],
+ "triage": triage,
+ "work_item": work_item,
}
except Exception as exc:
logger.debug(
diff --git a/apps/api/tests/test_telegram_message_templates.py b/apps/api/tests/test_telegram_message_templates.py
index 53594cb0..72ec78ae 100644
--- a/apps/api/tests/test_telegram_message_templates.py
+++ b/apps/api/tests/test_telegram_message_templates.py
@@ -206,6 +206,46 @@ def test_km_stale_completion_lines_show_owner_review_queue_state() -> None:
assert "preview_stale_km_review_completion" in joined
+def test_km_stale_completion_lines_show_callback_triage_state() -> None:
+ """詳情/歷史要顯示 callback owner-review 缺口的流程、主責與卡點。"""
+ lines = telegram_gateway_module._format_km_stale_completion_lines({
+ "schema_version": "km_stale_owner_review_completion_telegram_summary_v1",
+ "status": "no_related_owner_review",
+ "incident_id": "INC-20260524-16109D",
+ "ready_count": 10,
+ "blocked_count": 0,
+ "completed_count": 1,
+ "failed_count": 0,
+ "writes_on_read": False,
+ "batch_writes_allowed": False,
+ "items": [],
+ "work_item": {
+ "schema_version": "km_stale_callback_owner_review_work_item_v1",
+ "status": "open",
+ "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",
+ },
+ },
+ })
+
+ joined = "\n".join(lines)
+ assert "no_related_owner_review" in joined
+ assert "callback_observed_owner_review_link_missing" in joined
+ assert "related_incident_id_exact_match" in joined
+ assert "Hermes" in joined
+ assert "OpenClaw / ElephantAlpha" in joined
+ assert "manual_owner_review_required" in joined
+ assert "safe-auto no" in joined
+ assert "no_matching_completion_item" in joined
+
+
def test_awooop_runs_url_for_incident_uses_public_incident_filter() -> None:
"""Telegram URL button 必須導到公開 AwoooP Run list,並帶 incident filter。"""
url = telegram_gateway_module._awooop_runs_url_for_incident("INC-20260514-F85F21")