feat(awooop): show callback evidence capture status
All checks were successful
CD Pipeline / tests (push) Successful in 1m5s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / build-and-deploy (push) Successful in 4m30s
CD Pipeline / post-deploy-checks (push) Successful in 1m55s

This commit is contained in:
Your Name
2026-05-25 10:54:39 +08:00
parent 1c8ebdf283
commit 04684eef5f
6 changed files with 299 additions and 6 deletions

View File

@@ -53,6 +53,11 @@ type CallbackReplyStatus =
| "rescue_sent"
| "failed"
| "observed";
type CallbackEvidenceCaptureState =
| "captured"
| "partial"
| "not_captured"
| "observed";
type RemediationStatus =
| "no_evidence"
| "mcp_observed"
@@ -114,6 +119,18 @@ interface CallbackReplySummary {
latest_provider_message_id?: string | null;
}
interface CallbackEvidenceCaptureStatus {
schema_version?: string;
status?: CallbackEvidenceCaptureState | string | null;
reason?: string | null;
action?: string | null;
captured?: string[];
missing?: string[];
snapshot_rollout?: string | null;
next_action?: string | null;
event_at?: string | null;
}
interface Run {
run_id: string;
project_id: string;
@@ -373,6 +390,7 @@ interface CallbackReplyEvent {
persisted_awooop_status_chain?: AwoooPStatusChain | null;
km_stale_completion_summary?: KmStaleCompletionSummary | null;
persisted_km_stale_completion_summary?: KmStaleCompletionSummary | null;
evidence_capture_status?: CallbackEvidenceCaptureStatus | null;
}
interface CallbackRepliesResponse {
@@ -732,6 +750,19 @@ function normalizeCallbackReplyEventStatus(statusValue?: string | null): Callbac
return "observed";
}
function normalizeCallbackEvidenceCaptureStatus(
statusValue?: string | null
): CallbackEvidenceCaptureState {
if (
statusValue === "captured" ||
statusValue === "partial" ||
statusValue === "not_captured"
) {
return statusValue;
}
return "observed";
}
function normalizeKmCompletionStatus(statusValue?: string | null) {
if (
statusValue === "matched_owner_review" ||
@@ -744,6 +775,90 @@ function normalizeKmCompletionStatus(statusValue?: string | null) {
return "observed";
}
function CallbackEvidenceCaptureStatusPanel({
captureStatus,
}: {
captureStatus?: CallbackEvidenceCaptureStatus | null;
}) {
const t = useTranslations("awooop.callbackReply.events.capture");
if (!captureStatus) return null;
const state = normalizeCallbackEvidenceCaptureStatus(captureStatus.status);
const config = {
captured: {
className: "border-[#8ec58e] bg-[#edf8ed] text-[#17602a]",
icon: ShieldCheck,
},
partial: {
className: "border-[#e1c36a] bg-[#fff7df] text-[#8a5a08]",
icon: AlertCircle,
},
not_captured: {
className: "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]",
icon: BellOff,
},
observed: {
className: "border-[#9bb6d9] bg-[#eef5ff] text-[#1f5b9b]",
icon: SearchCheck,
},
}[state];
const Icon = config.icon;
const itemLabels: Record<string, string> = {
awooop_status_chain: t("items.awooopStatusChain"),
km_stale_completion_summary: t("items.kmCompletionSummary"),
};
const formatItems = (items?: string[]) => (
items && items.length > 0
? items.map((item) => itemLabels[item] ?? item).join(" / ")
: t("none")
);
const nextActionRaw = captureStatus.next_action ?? "observed";
const nextActionKey = nextActionRaw === "none"
|| nextActionRaw === "press_telegram_detail_or_history_after_rollout"
? nextActionRaw
: "observed";
return (
<div className="mt-3 border-t border-[#e0ddd4] pt-3">
<div className="flex flex-wrap items-center gap-2">
<Icon className="h-3.5 w-3.5 text-[#1f5b9b]" aria-hidden="true" />
<p className="text-xs font-semibold text-[#141413]">{t("title")}</p>
<span
className={cn(
"inline-flex items-center border px-2 py-0.5 text-xs font-semibold",
config.className
)}
>
{t(`statuses.${state}` as never)}
</span>
</div>
<div className="mt-2 grid gap-1 text-xs leading-5 text-[#5f5b52]">
<p>
{t("captured", {
items: formatItems(captureStatus.captured),
})}
</p>
<p>
{t("missing", {
items: formatItems(captureStatus.missing),
})}
</p>
<p>
{t("nextAction", {
action: t(`nextActions.${nextActionKey}` as never),
})}
</p>
<p className="truncate font-mono text-[#77736a]">
{t("reason", {
reason: captureStatus.reason ?? "--",
rollout: captureStatus.snapshot_rollout ?? "--",
})}
</p>
</div>
</div>
);
}
function RemediationEvidenceCell({ summary }: { summary?: RemediationSummary | null }) {
const t = useTranslations("awooop.listEvidence");
const status = normalizeRemediationStatus(summary);
@@ -1796,6 +1911,9 @@ function CallbackReplyEvidencePanel({
compact
className="mt-3"
/>
<CallbackEvidenceCaptureStatusPanel
captureStatus={event.evidence_capture_status}
/>
<CallbackAwoooPStatusChainSnapshot
chain={event.persisted_awooop_status_chain}
/>