feat(governance): 新增 Agent 報告狀態總覽
All checks were successful
Code Review / ai-code-review (push) Successful in 12s
CD Pipeline / tests (push) Successful in 1m26s
CD Pipeline / build-and-deploy (push) Successful in 4m53s
CD Pipeline / post-deploy-checks (push) Successful in 18s

This commit is contained in:
Your Name
2026-06-13 14:37:46 +08:00
parent 151979d9bd
commit d0bbcc8dee
14 changed files with 1404 additions and 10 deletions

View File

@@ -52,6 +52,7 @@ import {
type AiAgentOwnerApprovedResultCaptureDryRunSnapshot,
type AiAgentOwnerApprovedResultCaptureReadbackSnapshot,
type AiAgentReportAutomationReviewSnapshot,
type AiAgentReportStatusBoardSnapshot,
type AiAgentReportRuntimeDryRunSnapshot,
type AiAgentReportRuntimeFixtureReadbackSnapshot,
type AiAgentReportRuntimeReadinessSnapshot,
@@ -421,6 +422,7 @@ export function AutomationInventoryTab() {
const [postWriteVerifierPackage, setPostWriteVerifierPackage] = useState<AiAgentPostWriteVerifierPackageSnapshot | null>(null)
const [runtimeVerifierEvidenceReview, setRuntimeVerifierEvidenceReview] = useState<AiAgentRuntimeVerifierEvidenceReviewSnapshot | null>(null)
const [reportAutomationReview, setReportAutomationReview] = useState<AiAgentReportAutomationReviewSnapshot | null>(null)
const [reportStatusBoard, setReportStatusBoard] = useState<AiAgentReportStatusBoardSnapshot | null>(null)
const [reportRuntimeReadiness, setReportRuntimeReadiness] = useState<AiAgentReportRuntimeReadinessSnapshot | null>(null)
const [reportRuntimeDryRun, setReportRuntimeDryRun] = useState<AiAgentReportRuntimeDryRunSnapshot | null>(null)
const [reportRuntimeFixtureReadback, setReportRuntimeFixtureReadback] = useState<AiAgentReportRuntimeFixtureReadbackSnapshot | null>(null)
@@ -465,6 +467,7 @@ export function AutomationInventoryTab() {
apiClient.getAiAgentPostWriteVerifierPackage(),
apiClient.getAiAgentRuntimeVerifierEvidenceReview(),
apiClient.getAiAgentReportAutomationReview(),
apiClient.getAiAgentReportStatusBoard(),
apiClient.getAiAgentReportRuntimeReadiness(),
apiClient.getAiAgentReportRuntimeDryRun(),
apiClient.getAiAgentReportRuntimeFixtureReadback(),
@@ -508,6 +511,7 @@ export function AutomationInventoryTab() {
postWriteVerifierPackageResult,
runtimeVerifierEvidenceReviewResult,
reportAutomationReviewResult,
reportStatusBoardResult,
reportRuntimeReadinessResult,
reportRuntimeDryRunResult,
reportRuntimeFixtureReadbackResult,
@@ -548,6 +552,7 @@ export function AutomationInventoryTab() {
setPostWriteVerifierPackage(postWriteVerifierPackageResult.status === 'fulfilled' ? postWriteVerifierPackageResult.value : null)
setRuntimeVerifierEvidenceReview(runtimeVerifierEvidenceReviewResult.status === 'fulfilled' ? runtimeVerifierEvidenceReviewResult.value : null)
setReportAutomationReview(reportAutomationReviewResult.status === 'fulfilled' ? reportAutomationReviewResult.value : null)
setReportStatusBoard(reportStatusBoardResult.status === 'fulfilled' ? reportStatusBoardResult.value : null)
setReportRuntimeReadiness(reportRuntimeReadinessResult.status === 'fulfilled' ? reportRuntimeReadinessResult.value : null)
setReportRuntimeDryRun(reportRuntimeDryRunResult.status === 'fulfilled' ? reportRuntimeDryRunResult.value : null)
setReportRuntimeFixtureReadback(reportRuntimeFixtureReadbackResult.status === 'fulfilled' ? reportRuntimeFixtureReadbackResult.value : null)
@@ -586,6 +591,7 @@ export function AutomationInventoryTab() {
postWriteVerifierPackageResult,
runtimeVerifierEvidenceReviewResult,
reportAutomationReviewResult,
reportStatusBoardResult,
reportRuntimeReadinessResult,
reportRuntimeDryRunResult,
reportRuntimeFixtureReadbackResult,
@@ -989,6 +995,11 @@ export function AutomationInventoryTab() {
return reportAutomationReview.report_charts.slice(0, 4)
}, [reportAutomationReview])
const visibleReportStatusCharts = useMemo(() => {
if (!reportStatusBoard) return []
return reportStatusBoard.visible_charts.slice(0, 3)
}, [reportStatusBoard])
const visibleReportRecommendations = useMemo(() => {
if (!reportAutomationReview) return []
const priority = { critical: 0, high: 0, medium: 1, low: 2 } as Record<string, number>
@@ -1692,7 +1703,7 @@ export function AutomationInventoryTab() {
)
}
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportStatusBoard || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
return (
<div style={{ padding: 20 }}>
<GlassCard variant="subtle" padding="lg">
@@ -1842,6 +1853,15 @@ export function AutomationInventoryTab() {
const reportAutoEnabled = reportAutomationReview.rollups.current_auto_execution_enabled_count
const reportLiveDelivery = reportAutomationReview.rollups.live_report_delivery_count
const reportLiveOptimization = reportAutomationReview.rollups.live_auto_optimization_count
const reportStatusOverall = reportStatusBoard.program_status.overall_completion_percent
const reportStatusCards = reportStatusBoard.rollups.report_card_count
const reportStatusAgents = reportStatusBoard.rollups.agent_status_count
const reportStatusCharts = reportStatusBoard.rollups.visible_chart_count
const reportStatusWorkload = reportStatusBoard.rollups.workload_unit_total
const reportStatusDone = reportStatusBoard.rollups.workload_done_total
const reportStatusWaitingApproval = reportStatusBoard.rollups.workload_waiting_approval_total
const reportStatusLiveDelivery = reportStatusBoard.rollups.live_delivery_count
const reportStatusLiveOptimization = reportStatusBoard.rollups.live_auto_optimization_count
const reportRuntimeOverall = reportRuntimeReadiness.program_status.overall_completion_percent
const reportRuntimeLanes = reportRuntimeReadiness.rollups.runtime_lane_count
const reportRuntimeReady = reportRuntimeReadiness.rollups.ready_contract_count
@@ -2780,6 +2800,179 @@ export function AutomationInventoryTab() {
</div>
</div>
<div style={{ padding: 12, border: '0.5px solid #c7d2fe', borderRadius: 7, background: '#f8faff', display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
<ShieldCheck size={14} style={{ color: '#4f46e5' }} />
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('reportStatusBoard.title')}
</span>
</div>
<Chip
value={t('reportStatusBoard.source', {
generated: formatDateTime(reportStatusBoard.generated_at),
current: reportStatusBoard.program_status.current_task_id,
next: reportStatusBoard.program_status.next_task_id,
})}
muted
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(128px, 1fr))', gap: 10 }} className="automation-inventory-live-read-kpi-grid">
<MetricCard label={t('reportStatusBoard.metrics.overall')} value={`${reportStatusOverall}%`} tone="ok" icon={<Gauge size={16} />} />
<MetricCard label={t('reportStatusBoard.metrics.reports')} value={reportStatusCards} tone="ok" icon={<FileText size={16} />} />
<MetricCard label={t('reportStatusBoard.metrics.agents')} value={reportStatusAgents} tone="ok" icon={<Boxes size={16} />} />
<MetricCard label={t('reportStatusBoard.metrics.charts')} value={reportStatusCharts} tone="ok" icon={<Layers3 size={16} />} />
<MetricCard label={t('reportStatusBoard.metrics.workload')} value={reportStatusWorkload} tone="warn" icon={<Target size={16} />} />
<MetricCard label={t('reportStatusBoard.metrics.done')} value={reportStatusDone} tone="ok" icon={<ShieldCheck size={16} />} />
<MetricCard label={t('reportStatusBoard.metrics.waitingApproval')} value={reportStatusWaitingApproval} tone={reportStatusWaitingApproval > 0 ? 'danger' : 'ok'} icon={<Lock size={16} />} />
<MetricCard label={t('reportStatusBoard.metrics.liveDelivery')} value={reportStatusLiveDelivery} tone={reportStatusLiveDelivery === 0 ? 'warn' : 'ok'} icon={<BellRing size={16} />} />
<MetricCard label={t('reportStatusBoard.metrics.liveOptimization')} value={reportStatusLiveOptimization} tone={reportStatusLiveOptimization === 0 ? 'warn' : 'ok'} icon={<PackageCheck size={16} />} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 12 }} className="automation-inventory-live-read-grid">
<div style={{ padding: 11, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('reportStatusBoard.truthTitle')}</SmallLabel>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#62636b', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{reportStatusBoard.report_completion_truth.truth_note}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportStatusBoard.flags.daily', { value: String(reportStatusBoard.report_completion_truth.daily_report_visible) })} />
<Chip value={t('reportStatusBoard.flags.weekly', { value: String(reportStatusBoard.report_completion_truth.weekly_report_visible) })} />
<Chip value={t('reportStatusBoard.flags.monthly', { value: String(reportStatusBoard.report_completion_truth.monthly_report_visible) })} />
<Chip value={t('reportStatusBoard.flags.perAgent', { value: String(reportStatusBoard.report_completion_truth.per_agent_status_visible) })} />
<Chip value={t('reportStatusBoard.flags.telegram', { value: String(reportStatusBoard.report_completion_truth.telegram_digest_draft_visible) })} muted />
</div>
</div>
<div style={{ padding: 11, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('reportStatusBoard.boundaryTitle')}</SmallLabel>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportStatusBoard.flags.scheduler', { value: String(reportStatusBoard.activation_boundaries.scheduler_enabled) })} muted />
<Chip value={t('reportStatusBoard.flags.queue', { value: String(reportStatusBoard.activation_boundaries.gateway_queue_write_enabled) })} muted />
<Chip value={t('reportStatusBoard.flags.send', { value: String(reportStatusBoard.activation_boundaries.telegram_send_enabled) })} muted />
<Chip value={t('reportStatusBoard.flags.analysis', { value: String(reportStatusBoard.activation_boundaries.ai_analysis_run_enabled) })} muted />
<Chip value={t('reportStatusBoard.flags.optimization', { value: String(reportStatusBoard.activation_boundaries.production_optimization_write_enabled) })} muted />
<Chip value={t('reportStatusBoard.flags.highApproval', { value: String(reportStatusBoard.activation_boundaries.high_risk_requires_human_approval) })} />
</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{reportStatusBoard.report_status_cards.map(card => (
<div key={card.cadence_id} style={{ padding: 10, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
{card.display_name}
</span>
<Chip value={redisDryRunValueLabel('agents', card.owner_agent)} muted />
</div>
<MiniBar
label={t('reportStatusBoard.labels.completion')}
value={card.completion_percent}
detail={t('reportStatusBoard.labels.reportDetail', {
sections: card.sections_count,
charts: card.chart_count,
work: card.work_units_total,
live: card.live_delivery_count,
})}
tone="ok"
/>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#62636b', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{card.next_gate}
</span>
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{reportStatusBoard.agent_status_reports.map(agent => {
const progress = agent.work_units_total > 0 ? Math.round((agent.work_units_done / agent.work_units_total) * 100) : 0
return (
<div key={agent.agent_id} style={{ padding: 10, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
{agent.display_name}
</span>
<Chip value={agent.current_state} muted />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#4f5565', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{agent.primary_role}
</span>
<MiniBar
label={t('reportStatusBoard.labels.workDone')}
value={progress}
detail={t('reportStatusBoard.labels.agentDetail', {
done: agent.work_units_done,
total: agent.work_units_total,
approval: agent.work_units_waiting_approval,
live: agent.live_runtime_work_units_24h,
})}
tone={progress >= 85 ? 'ok' : 'warn'}
/>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('reportStatusBoard.labels.sections', { count: agent.report_sections_owned })} muted />
<Chip value={t('reportStatusBoard.labels.recommendations', { count: agent.analysis_recommendations_owned })} muted />
<Chip value={agent.communication_state} muted />
<Chip value={agent.learning_state} muted />
<Chip value={agent.telegram_policy} muted />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#62636b', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{agent.status_note}
</span>
</div>
)
})}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{visibleReportStatusCharts.map(chart => {
const maxValue = Math.max(...chart.series.map(item => item.value), 1)
return (
<div key={chart.chart_id} style={{ padding: 10, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center' }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413' }}>
{chart.display_name}
</span>
<Chip value={chart.chart_type} muted />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
{chart.series.map(item => (
<div key={`${chart.chart_id}-${item.label}`} style={{ display: 'grid', gridTemplateColumns: '88px minmax(0, 1fr) 36px', gap: 8, alignItems: 'center', minWidth: 0 }}>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#605f59', overflowWrap: 'anywhere' }}>
{item.label}
</span>
<div style={{ height: 7, borderRadius: 999, background: '#e7e3d8', overflow: 'hidden' }}>
<div style={{ height: '100%', width: `${Math.max(4, Math.round((item.value / maxValue) * 100))}%`, borderRadius: 999, background: toneColor(item.tone) }} />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, fontWeight: 700, color: toneColor(item.tone), textAlign: 'right' }}>
{item.value}
</span>
</div>
))}
</div>
</div>
)
})}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
{reportStatusBoard.operator_answer_cards.map(answer => (
<div key={answer.answer_id} style={{ padding: 10, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
{answer.question}
</span>
<Chip value={t(`reportStatusBoard.answerStatuses.${answer.status}` as never)} muted={answer.status !== 'complete'} />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#62636b', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{answer.answer}
</span>
</div>
))}
</div>
</div>
<div style={{ padding: 12, border: '0.5px solid #b9d0dd', borderRadius: 7, background: '#f8fcff', display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>