diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 15990b63c..d3cd7701c 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -11765,6 +11765,64 @@ "expired": "已逾時", "expiredDetail": "改排 AI retry / rollback / verifier,不預設人工結案" }, + "logAutomation": { + "eyebrow": "LOG / KM / RAG / MCP / PlayBook Mainline", + "title": "AI Automation Writeback And Executor Queue", + "subtitle": "Reads the production executor readback directly and connects service log labels, feedback receipts, post-write verifier, controlled plan, and AI Agent next action queue into one trackable mainline.", + "refresh": "Refresh mainline", + "loadFailed": "AI automation mainline readback failed to load", + "emptyBatches": "No executor batches returned yet", + "boundary": "This panel shows the low / medium / high metadata writeback queue available to AI controlled apply. Runtime dispatch truth still comes from production readback; critical remains break-glass.", + "errorDetail": "AI automation mainline read failed: {error}", + "status": { + "loading": "Loading mainline", + "ready": "executor ready", + "waiting": "waiting readback", + "error": "readback failed" + }, + "metrics": { + "plans": "Writeback plans", + "plansDetail": "Verified KM / RAG / MCP / PlayBook / Agent plans.", + "batches": "Executor batches", + "batchesDetail": "ready / total batches consumable by AI Agent controlled apply.", + "selectors": "Target selectors", + "selectorsDetail": "Every writeback binds project, service, package, and tool.", + "verifiers": "Post-apply verifiers", + "verifiersDetail": "Every target batch must carry a verifier ref." + }, + "stages": { + "logs": "LOG labels", + "receipts": "Feedback receipts", + "verifier": "Post-write verifier", + "plan": "Controlled plan", + "executor": "Executor queue" + }, + "targets": { + "km": "KM", + "rag": "RAG", + "playbook": "PlayBook", + "mcp": "MCP", + "verifier": "Verifier", + "ai_agent": "AI Agent", + "unknown": "Unknown" + }, + "policy": { + "controlledApply": "L/M/H controlled apply", + "ownerReview": "L/M/H owner review", + "critical": "Critical", + "runtime": "Runtime dispatch", + "queue": "Next action queue", + "evidence": "Evidence chain" + }, + "values": { + "on": "on", + "off": "off", + "required": "required", + "notRequired": "not required", + "performed": "performed", + "notPerformed": "not performed" + } + }, "decisionRail": { "eyebrow": "審批決策 Rail", "title": "卡住的批准與 AI 受控處置判讀", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index b5ab70c6c..b16e7d9fe 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -11765,6 +11765,64 @@ "expired": "已逾時", "expiredDetail": "改排 AI retry / rollback / verifier,不預設人工結案" }, + "logAutomation": { + "eyebrow": "LOG / KM / RAG / MCP / PlayBook 主線", + "title": "AI 自動化回寫與執行器隊列", + "subtitle": "直接讀取正式 API 的 executor readback,把服務日誌貼標、feedback receipt、post-write verifier、controlled plan 與 AI Agent next action queue 串成同一條可追蹤主線。", + "refresh": "刷新主線", + "loadFailed": "AI 自動化主線 readback 載入失敗", + "emptyBatches": "尚未取得 executor batch", + "boundary": "此面板顯示 AI controlled apply 已具備的低 / 中 / 高風險 metadata writeback 隊列;runtime dispatch 是否已執行仍以正式 readback 為準,critical 維持 break-glass。", + "errorDetail": "AI 自動化主線讀取失敗:{error}", + "status": { + "loading": "讀取主線", + "ready": "executor ready", + "waiting": "等待 readback", + "error": "readback failed" + }, + "metrics": { + "plans": "回寫計畫", + "plansDetail": "已驗證的 KM / RAG / MCP / PlayBook / Agent plan。", + "batches": "Executor 批次", + "batchesDetail": "ready / total,供 AI Agent controlled apply 消費。", + "selectors": "目標 selector", + "selectorsDetail": "每筆回寫都要綁 project、service、package、tool。", + "verifiers": "Post-apply verifier", + "verifiersDetail": "每個 target batch 都必須有 verifier ref。" + }, + "stages": { + "logs": "LOG 貼標", + "receipts": "Feedback receipts", + "verifier": "Post-write verifier", + "plan": "Controlled plan", + "executor": "Executor queue" + }, + "targets": { + "km": "KM", + "rag": "RAG", + "playbook": "PlayBook", + "mcp": "MCP", + "verifier": "Verifier", + "ai_agent": "AI Agent", + "unknown": "Unknown" + }, + "policy": { + "controlledApply": "低中高風險 controlled apply", + "ownerReview": "低中高 owner review", + "critical": "Critical", + "runtime": "Runtime dispatch", + "queue": "Next action queue", + "evidence": "Evidence chain" + }, + "values": { + "on": "開啟", + "off": "關閉", + "required": "必要", + "notRequired": "不必要", + "performed": "已執行", + "notPerformed": "未執行" + } + }, "decisionRail": { "eyebrow": "審批決策 Rail", "title": "卡住的批准與 AI 受控處置判讀", diff --git a/apps/web/src/app/[locale]/awooop/approvals/page.tsx b/apps/web/src/app/[locale]/awooop/approvals/page.tsx index 009a6f1cd..706108c7e 100644 --- a/apps/web/src/app/[locale]/awooop/approvals/page.tsx +++ b/apps/web/src/app/[locale]/awooop/approvals/page.tsx @@ -108,6 +108,76 @@ interface LegacyApproval { required_signatures?: number | null; } +interface LogWritebackExecutionBatch { + batch_id?: string | null; + target?: string | null; + target_surface?: string | null; + risk_tier?: string | null; + executor_route?: string | null; + status?: string | null; + apply_mode?: string | null; + dispatch_enabled_by_policy?: boolean | null; + plan_count?: number | null; + target_selector_count?: number | null; + source_of_truth_diff_count?: number | null; + runtime_dispatch_performed?: boolean | null; + check_mode?: { + enabled?: boolean | null; + required?: boolean | null; + } | null; + rollback?: { + required?: boolean | null; + rollback_refs?: string[] | null; + } | null; + post_apply_verifier?: { + required?: boolean | null; + verifier_refs?: string[] | null; + } | null; +} + +interface LogWritebackExecutorReadback { + schema_version?: string | null; + status?: string | null; + active_blockers?: string[] | null; + readback?: { + source_status?: string | null; + safe_next_step?: string | null; + } | null; + executor_policy?: { + executor_route?: string | null; + low_medium_high_controlled_apply_enabled?: boolean | null; + owner_review_required_for_low_medium_high?: boolean | null; + critical_break_glass_required?: boolean | null; + } | null; + execution_batches?: LogWritebackExecutionBatch[] | null; + agent_consumption_context?: { + context_id?: string | null; + consumable_by?: string[] | null; + evidence_chain?: string[] | null; + next_action_queue?: Array<{ + batch_id?: string | null; + target?: string | null; + plan_count?: number | null; + }> | null; + learning_feedback_targets?: string[] | null; + raw_payload_required?: boolean | null; + } | null; + rollups?: { + source_writeback_plan_count?: number | null; + execution_batch_count?: number | null; + ready_execution_batch_count?: number | null; + target_count?: number | null; + target_selector_count?: number | null; + source_of_truth_diff_count?: number | null; + check_mode_ready_count?: number | null; + rollback_ready_count?: number | null; + post_apply_verifier_ready_count?: number | null; + controlled_executor_dispatch_ready?: boolean | null; + controlled_apply_enabled_by_policy?: boolean | null; + runtime_dispatch_performed?: boolean | null; + } | null; +} + // ============================================================================= // 常數 // ============================================================================= @@ -388,6 +458,181 @@ function RemediationEvidenceCell({ summary }: { summary?: RemediationSummary | n ); } +function LogAutomationMainlinePanel({ + readback, + loading, + error, + onRefresh, +}: { + readback: LogWritebackExecutorReadback | null; + loading: boolean; + error: string | null; + onRefresh: () => void; +}) { + const t = useTranslations("awooop.approvals.logAutomation"); + const rollups = readback?.rollups ?? {}; + const policy = readback?.executor_policy ?? {}; + const context = readback?.agent_consumption_context ?? {}; + const batches = readback?.execution_batches ?? []; + const blockers = readback?.active_blockers ?? []; + const ready = readback?.status === "controlled_writeback_executor_ready" && blockers.length === 0; + const statusText = loading + ? t("status.loading") + : error + ? t("status.error") + : ready + ? t("status.ready") + : t("status.waiting"); + const statusClass = ready + ? "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]" + : error + ? "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]" + : "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]"; + const metrics = [ + { + label: t("metrics.plans"), + value: rollups.source_writeback_plan_count ?? "--", + detail: t("metrics.plansDetail"), + icon: GitBranch, + }, + { + label: t("metrics.batches"), + value: `${rollups.ready_execution_batch_count ?? 0}/${rollups.execution_batch_count ?? 0}`, + detail: t("metrics.batchesDetail"), + icon: ListChecks, + }, + { + label: t("metrics.selectors"), + value: rollups.target_selector_count ?? "--", + detail: t("metrics.selectorsDetail"), + icon: SearchCheck, + }, + { + label: t("metrics.verifiers"), + value: rollups.post_apply_verifier_ready_count ?? "--", + detail: t("metrics.verifiersDetail"), + icon: ShieldCheck, + }, + ]; + const stages = [ + { key: "logs", value: t("stages.logs") }, + { key: "receipts", value: t("stages.receipts") }, + { key: "verifier", value: t("stages.verifier") }, + { key: "plan", value: t("stages.plan") }, + { key: "executor", value: t("stages.executor") }, + ]; + + return ( +
+
+
+ + +
+

{t("eyebrow")}

+

{t("title")}

+

{t("subtitle")}

+
+
+
+ + {statusText} + + +
+
+ +
+ {metrics.map((item) => { + const Icon = item.icon; + return ( +
+
+
+

{item.label}

+

{item.value}

+
+
+

{item.detail}

+
+ ); + })} +
+ +
+
+
+ {stages.map((stage, index) => ( + + {index + 1} + {stage.value} + + ))} +
+
+ {(batches.length ? batches : []).map((batch) => ( +
+
+

+ {t(`targets.${batch.target ?? "unknown"}`)} +

+ {batch.plan_count ?? 0} +
+

+ {batch.status ?? "--"} +

+
+ ))} + {!loading && batches.length === 0 ? ( +
+ {t("emptyBatches")} +
+ ) : null} +
+
+ +
+
+ {[ + [t("policy.controlledApply"), policy.low_medium_high_controlled_apply_enabled ? t("values.on") : t("values.off")], + [t("policy.ownerReview"), policy.owner_review_required_for_low_medium_high ? t("values.required") : t("values.notRequired")], + [t("policy.critical"), policy.critical_break_glass_required ? t("values.required") : t("values.off")], + [t("policy.runtime"), rollups.runtime_dispatch_performed ? t("values.performed") : t("values.notPerformed")], + [t("policy.queue"), String(context.next_action_queue?.length ?? 0)], + [t("policy.evidence"), String(context.evidence_chain?.length ?? 0)], + ].map(([label, value]) => ( +
+

{label}

+

+ {value} +

+
+ ))} +
+

+ {error ? t("errorDetail", { error }) : t("boundary")} +

+
+
+
+ ); +} + function LegacyHitlBacklogPanel({ approvals, loading, @@ -1675,10 +1920,28 @@ export default function ApprovalsPage() { const [incidentTimeline, setIncidentTimeline] = useState(null); const [incidentLoading, setIncidentLoading] = useState(false); const [incidentError, setIncidentError] = useState(null); + const [logAutomationReadback, setLogAutomationReadback] = useState(null); + const [logAutomationLoading, setLogAutomationLoading] = useState(true); + const [logAutomationError, setLogAutomationError] = useState(null); const [lastRefresh, setLastRefresh] = useState(null); const [evidenceFilter, setEvidenceFilter] = useState<"" | RemediationStatus>(""); const intervalRef = useRef | null>(null); + const fetchLogAutomationMainline = useCallback(async () => { + setLogAutomationLoading(true); + setLogAutomationError(null); + const payload = await fetchJson( + `${API_BASE}/api/v1/agents/agent-log-controlled-writeback-executor-readback` + ); + if (payload) { + setLogAutomationReadback(payload); + } else { + setLogAutomationReadback(null); + setLogAutomationError(t("logAutomation.loadFailed")); + } + setLogAutomationLoading(false); + }, [t]); + const fetchApprovals = useCallback(async () => { const fetchJson = async (url: string) => { const res = await fetch(url); @@ -1727,6 +1990,10 @@ export default function ApprovalsPage() { fetchApprovals(); }, [fetchApprovals]); + useEffect(() => { + fetchLogAutomationMainline(); + }, [fetchLogAutomationMainline]); + useEffect(() => { let cancelled = false; if (!queryIncidentId) { @@ -1772,11 +2039,12 @@ export default function ApprovalsPage() { useEffect(() => { intervalRef.current = setInterval(() => { fetchApprovals(); + fetchLogAutomationMainline(); }, AUTO_REFRESH_INTERVAL); return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; - }, [fetchApprovals]); + }, [fetchApprovals, fetchLogAutomationMainline]); const legacyPendingCount = legacyApprovals.length; const totalPendingCount = approvals.length + legacyPendingCount; @@ -1887,7 +2155,11 @@ export default function ApprovalsPage() {
{t("page.autoRefresh")}