diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index feeaadd7..a882419d 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -2275,6 +2275,9 @@ "callbackOwnerReviewBlocker": "Blocker: {reason}", "callbackOwnerReviewEmpty": "Recent callback evidence is matched or no data is available yet", "callbackTraceRecoveryBacklog": "Callback trace backlog: missing trace {missing}; 1h {recent1h}; 24h {recent24h}; traced after gap {recovered}; recovery {status}", + "callbackTraceRecoveryAction": "Next handling: {action}; human required={human}", + "callbackTraceRecoveryOwner": "Owner: AwoooP Callback Evidence; support: TelegramGateway / Run Timeline", + "callbackTraceRecoveryClosure": "Close when 1h=0 and 24h=0; current 1h {recent1h} / 24h {recent24h}", "callbackTraceRecoveryDecision": "Decision: {gap}; next: {next}", "callbackTraceRecoveryLatest": "Last gap: {missing}; recovery first: {first}; recovery latest: {latest}", "callbackTraceRecoveryUnavailable": "Callback trace recovery summary has not returned yet", @@ -2310,6 +2313,18 @@ "ownerResponseValidationChecks": "Cross-packet checks={crossPacket}; evidence routing={routing}; display sections={sections}", "ownerResponseValidationBoundary": "No approval record, no primary switch, and no 執行期閘門" }, + "humanRequired": { + "yes": "yes", + "no": "no" + }, + "callbackTraceRecoveryActions": { + "unavailable": "summary did not return; check the callback-replies API first", + "closed": "close criteria are met; retain the historical evidence", + "investigateActiveGap": "new gaps still exist; check new Telegram reply_markup trace writes", + "verifyInstrumentation": "no recovery signal; check TelegramGateway / Timeline instrumentation", + "waitDecay": "wait for the legacy backlog to decay over 24h; no human action needed", + "observeRecovery": "observe the recovery signal before opening a human task" + }, "claim": { "ready": "Full auto-repair claim: ready", "notReady": "Full auto-repair claim: not ready", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 8dd871fc..8a8ca2ef 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -2276,6 +2276,9 @@ "callbackOwnerReviewBlocker": "卡點:{reason}", "callbackOwnerReviewEmpty": "近期 callback evidence 均已匹配或尚無資料", "callbackTraceRecoveryBacklog": "Callback trace backlog:缺 trace {missing};1h {recent1h};24h {recent24h};gap 後 traced {recovered};復原 {status}", + "callbackTraceRecoveryAction": "接續處理:{action};需要人工={human}", + "callbackTraceRecoveryOwner": "主責:AwoooP Callback Evidence;協作:TelegramGateway / Run Timeline", + "callbackTraceRecoveryClosure": "關閉條件:1h=0 且 24h=0;目前 1h {recent1h} / 24h {recent24h}", "callbackTraceRecoveryDecision": "判讀:{gap};下一步:{next}", "callbackTraceRecoveryLatest": "最後缺口:{missing};復原首筆:{first};復原最新:{latest}", "callbackTraceRecoveryUnavailable": "Callback trace recovery summary 尚未回傳", @@ -2311,6 +2314,18 @@ "ownerResponseValidationChecks": "跨包驗收={crossPacket};證據路由={routing};顯示區塊={sections}", "ownerResponseValidationBoundary": "不建立審批紀錄、不切主要來源、不開執行期閘門" }, + "humanRequired": { + "yes": "是", + "no": "否" + }, + "callbackTraceRecoveryActions": { + "unavailable": "summary 未回傳,先確認 callback-replies API", + "closed": "已符合關閉條件,保留歷史證據即可", + "investigateActiveGap": "仍有新缺口,檢查新 Telegram reply_markup trace 寫入", + "verifyInstrumentation": "沒有復原訊號,檢查 TelegramGateway / Timeline instrumentation", + "waitDecay": "等待舊 backlog 24h decay,不需人工處理", + "observeRecovery": "觀察復原訊號,先不開人工任務" + }, "claim": { "ready": "完整自動修復聲明:可宣稱", "notReady": "完整自動修復聲明:不可宣稱", diff --git a/apps/web/src/app/[locale]/awooop/work-items/page.tsx b/apps/web/src/app/[locale]/awooop/work-items/page.tsx index 36bb29ec..39349ea9 100644 --- a/apps/web/src/app/[locale]/awooop/work-items/page.tsx +++ b/apps/web/src/app/[locale]/awooop/work-items/page.tsx @@ -848,6 +848,19 @@ type CallbackRepliesWorkItemResponse = { summary?: CallbackReplyAuditSummary | null; }; +type CallbackTraceRecoveryActionKey = + | "unavailable" + | "closed" + | "investigateActiveGap" + | "verifyInstrumentation" + | "waitDecay" + | "observeRecovery"; + +type CallbackTraceRecoveryAction = { + actionKey: CallbackTraceRecoveryActionKey; + humanRequired: boolean; +}; + type AiRouteRepairEvidence = { target_resource?: string | null; access_blockers?: string[]; @@ -1627,24 +1640,54 @@ function callbackOwnerReviewOpenEvents( function callbackTraceRecoveryStatus( summary: CallbackReplyAuditSummary | null | undefined ): WorkStatus { - if (!summary) { - return "blocked"; - } - const missingTrace = summary.outbound_reply_markup_missing_trace_ref_total ?? 0; - if (missingTrace <= 0) { + const action = callbackTraceRecoveryAction(summary); + if (action.actionKey === "closed") { return "live"; } - const gapStatus = summary.outbound_reply_markup_trace_ref_gap_status; - const recoveryStatus = summary.outbound_reply_markup_trace_ref_gap_recovery_status; - if (gapStatus === "active_gap" || recoveryStatus === "no_recovery_signal") { + if ( + action.actionKey === "investigateActiveGap" || + action.actionKey === "verifyInstrumentation" || + action.actionKey === "unavailable" + ) { return "blocked"; } - if (recoveryStatus === "recovered_after_gap") { + if (action.actionKey === "waitDecay") { return "in_progress"; } return "watching"; } +function callbackTraceRecoveryAction( + summary: CallbackReplyAuditSummary | null | undefined +): CallbackTraceRecoveryAction { + if (!summary) { + return { actionKey: "unavailable", humanRequired: true }; + } + const missingTrace = summary.outbound_reply_markup_missing_trace_ref_total ?? 0; + if (missingTrace <= 0) { + return { actionKey: "closed", humanRequired: false }; + } + const recent1h = + summary.outbound_reply_markup_missing_trace_ref_recent_1h_total ?? 0; + const recent24h = + summary.outbound_reply_markup_missing_trace_ref_recent_24h_total ?? 0; + const gapStatus = summary.outbound_reply_markup_trace_ref_gap_status; + const recoveryStatus = summary.outbound_reply_markup_trace_ref_gap_recovery_status; + if (gapStatus === "active_gap" || recent1h > 0) { + return { actionKey: "investigateActiveGap", humanRequired: true }; + } + if (recoveryStatus === "no_recovery_signal") { + return { actionKey: "verifyInstrumentation", humanRequired: true }; + } + if (recoveryStatus === "recovered_after_gap") { + if (recent24h > 0) { + return { actionKey: "waitDecay", humanRequired: false }; + } + return { actionKey: "closed", humanRequired: false }; + } + return { actionKey: "observeRecovery", humanRequired: false }; +} + function buildWorkItems( telemetry: Telemetry, t: ReturnType @@ -1679,6 +1722,7 @@ function buildWorkItems( const latestCallbackWorkItem = latestCallbackSummary?.work_item ?? null; const latestCallbackTriage = latestCallbackWorkItem?.triage ?? null; const callbackTraceSummary = telemetry.callbackReplies?.summary ?? null; + const callbackTraceAction = callbackTraceRecoveryAction(callbackTraceSummary); const aiRoute = telemetry.aiRouteStatus; const aiRouteRepairEvidence = aiRoute?.repair_evidence ?? null; const aiRouteWorkItem = aiRouteRepairEvidence?.work_item ?? null; @@ -2055,6 +2099,25 @@ function buildWorkItems( }), evidenceDetails: callbackTraceSummary ? [ + t("evidence.callbackTraceRecoveryAction", { + action: t( + `callbackTraceRecoveryActions.${callbackTraceAction.actionKey}` as never + ), + human: t( + `humanRequired.${callbackTraceAction.humanRequired ? "yes" : "no"}` as never + ), + }), + t("evidence.callbackTraceRecoveryOwner"), + t("evidence.callbackTraceRecoveryClosure", { + recent1h: + callbackTraceSummary + .outbound_reply_markup_missing_trace_ref_recent_1h_total ?? + 0, + recent24h: + callbackTraceSummary + .outbound_reply_markup_missing_trace_ref_recent_24h_total ?? + 0, + }), t("evidence.callbackTraceRecoveryDecision", { gap: callbackTraceSummary.outbound_reply_markup_trace_ref_gap_status ??