fix(web): keep autonomous cockpit visible during readback fallback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m15s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-07-02 00:17:01 +08:00
parent 8a659e1dd3
commit 6b8edd8ffe

View File

@@ -1084,6 +1084,263 @@ function AutonomyWorkspaceScene({
)
}
function AutonomousRuntimeControlCockpitCard({
control,
t,
}: {
control: AiAgentAutonomousRuntimeControlSnapshot
t: ReturnType<typeof useTranslations>
}) {
const rollups = control.rollups
const runtimeReadback = control.runtime_receipt_readback
const loopLedger = runtimeReadback?.autonomous_execution_loop_ledger
const loopClosed = (rollups.live_autonomous_execution_loop_closed_count ?? 0) > 0 || Boolean(loopLedger?.closed)
const stages = loopLedger?.stages ?? []
const stageById = new Map(stages.map(stage => [stage.stage_id, stage]))
const flowRows = [
{ key: 'candidate', stage: stageById.get('candidate'), label: t('globalControl.currentAutonomy.flow.candidate'), icon: <Target size={15} /> },
{ key: 'checkMode', stage: stageById.get('check_mode'), label: t('globalControl.currentAutonomy.flow.checkMode'), icon: <ClipboardCheck size={15} /> },
{ key: 'apply', stage: stageById.get('controlled_apply'), label: t('globalControl.currentAutonomy.flow.apply'), icon: <Route size={15} /> },
{ key: 'verifier', stage: stageById.get('post_apply_verifier'), label: t('globalControl.currentAutonomy.flow.verifier'), icon: <ShieldCheck size={15} /> },
{ key: 'learning', stage: stageById.get('km_learning_writeback'), label: t('globalControl.currentAutonomy.flow.learning'), icon: <BookOpenCheck size={15} /> },
{ key: 'telegram', stage: stageById.get('telegram_receipt'), label: t('globalControl.currentAutonomy.flow.telegram'), icon: <BellRing size={15} /> },
]
const receiptStats = [
{
key: 'apply',
label: t('globalControl.currentAutonomy.cockpit.apply'),
value: rollups.live_ansible_apply_executed_count ?? 0,
detail: t('globalControl.currentAutonomy.cockpit.receiptDetail'),
tone: (rollups.live_ansible_apply_executed_count ?? 0) > 0 ? 'ok' as const : 'warn' as const,
icon: <Route size={15} />,
},
{
key: 'verifier',
label: t('globalControl.currentAutonomy.cockpit.verifier'),
value: rollups.live_post_apply_verifier_count ?? 0,
detail: t('globalControl.currentAutonomy.cockpit.verifierDetail'),
tone: (rollups.live_post_apply_verifier_count ?? 0) > 0 ? 'ok' as const : 'warn' as const,
icon: <ShieldCheck size={15} />,
},
{
key: 'km',
label: t('globalControl.currentAutonomy.cockpit.km'),
value: rollups.live_km_writeback_count ?? 0,
detail: t('globalControl.currentAutonomy.cockpit.kmDetail'),
tone: (rollups.live_km_writeback_count ?? 0) > 0 ? 'ok' as const : 'warn' as const,
icon: <BookOpenCheck size={15} />,
},
{
key: 'telegram',
label: t('globalControl.currentAutonomy.cockpit.telegram'),
value: rollups.live_telegram_receipt_count ?? 0,
detail: t('globalControl.currentAutonomy.cockpit.telegramDetail'),
tone: (rollups.live_telegram_receipt_count ?? 0) > 0 ? 'ok' as const : 'warn' as const,
icon: <BellRing size={15} />,
},
]
return (
<GlassCard variant="subtle" padding="md">
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }} className="automation-inventory-autonomy-cockpit">
<div style={{
display: 'grid',
gridTemplateColumns: 'minmax(0, 1fr) auto',
gap: 12,
alignItems: 'start',
padding: 14,
border: '0.5px solid #cfe7d7',
borderRadius: 8,
background: 'linear-gradient(135deg, rgba(248,255,250,0.96), rgba(255,255,255,0.98))',
minWidth: 0,
}} className="automation-inventory-autonomy-cockpit-header">
<div style={{ display: 'grid', gridTemplateColumns: '38px minmax(0, 1fr)', gap: 10, alignItems: 'start', minWidth: 0 }}>
<div style={{
width: 38,
height: 38,
borderRadius: 8,
border: '0.5px solid #15803d40',
background: 'rgba(21,128,61,0.08)',
color: '#15803d',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}}>
<BellRing size={18} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 5, minWidth: 0 }}>
<SmallLabel>{t('globalControl.currentAutonomy.cockpit.liveLabel')}</SmallLabel>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 22, fontWeight: 780, color: '#141413', lineHeight: 1.05, overflowWrap: 'anywhere' }}>
{t('globalControl.currentAutonomy.cockpit.title')}
</span>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, color: '#4f6156', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{t('globalControl.currentAutonomy.cockpit.subtitle')}
</span>
</div>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'flex-end', gap: 6, minWidth: 0, maxWidth: 460 }}>
<Chip value={control.program_status.current_task_id} />
<Chip value={runtimeReadback?.db_read_status === 'ok' ? t('globalControl.currentAutonomy.cockpit.dbOk') : t('globalControl.currentAutonomy.cockpit.dbReview')} />
<Chip value={t('globalControl.currentAutonomy.badges.gateway')} muted={rollups.telegram_gateway_delivery_enabled_count === 0} />
<Chip value={t('globalControl.currentAutonomy.badges.deployMarker')} muted />
</div>
</div>
<AutonomyWorkspaceScene
stations={receiptStats}
completion={control.program_status.implementation_completion_percent}
status={loopClosed ? t('globalControl.currentAutonomy.cockpit.closed') : t('globalControl.currentAutonomy.cockpit.review')}
/>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(260px, 0.85fr) minmax(0, 1.15fr)', gap: 12 }} className="automation-inventory-autonomy-cockpit-hero-grid">
<div style={{ padding: 14, border: '0.5px solid #d9eadf', borderRadius: 8, background: '#fff', display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
<SmallLabel>{t('globalControl.currentAutonomy.metrics.completion')}</SmallLabel>
<Chip value={t('globalControl.currentAutonomy.cockpit.production')} />
</div>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 52, fontWeight: 800, color: '#15803d', lineHeight: 0.95 }}>
{control.program_status.implementation_completion_percent}%
</span>
<div style={{ height: 8, borderRadius: 999, background: '#edf3ea', overflow: 'hidden' }}>
<div style={{ width: `${Math.min(100, control.program_status.implementation_completion_percent)}%`, height: '100%', background: '#15803d' }} />
</div>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#5c675d', lineHeight: 1.35, overflowWrap: 'anywhere' }}>
{control.program_status.deploy_readback_marker}
</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-autonomy-cockpit-metric-grid">
{receiptStats.map(stat => (
<AutonomyCockpitMetric
key={stat.key}
label={stat.label}
value={stat.value}
detail={stat.detail}
tone={stat.tone}
icon={stat.icon}
/>
))}
</div>
</div>
<div style={{ padding: 12, border: '0.5px solid #d7e5d8', borderRadius: 8, background: '#f9fff9', display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
<SmallLabel>{t('globalControl.currentAutonomy.cockpit.loopTitle')}</SmallLabel>
<Chip value={loopClosed ? t('globalControl.currentAutonomy.cockpit.closed') : t('globalControl.currentAutonomy.cockpit.review')} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, minmax(0, 1fr))', gap: 9 }} className="automation-inventory-autonomy-cockpit-flow-grid">
{flowRows.map(row => (
<AutonomyFlowStep
key={row.key}
label={row.label}
status={row.stage?.status ?? t('globalControl.currentAutonomy.cockpit.missing')}
detail={row.stage?.writes_runtime_state ? t('globalControl.currentAutonomy.cockpit.runtimeWrite') : t('globalControl.currentAutonomy.cockpit.noRuntimeWrite')}
tone={autonomyStageTone(row.stage, loopClosed)}
icon={row.icon}
/>
))}
</div>
</div>
</div>
<style jsx global suppressHydrationWarning>{`
.automation-inventory-autonomy-workspace-rail {
position: absolute;
inset: 28px;
border: 0.5px solid rgba(21, 128, 61, 0.18);
border-radius: 8px;
background:
linear-gradient(90deg, rgba(21, 128, 61, 0.08) 1px, transparent 1px),
linear-gradient(0deg, rgba(21, 128, 61, 0.06) 1px, transparent 1px);
background-size: 44px 44px;
opacity: 0.88;
}
.automation-inventory-autonomy-workspace-packet {
position: absolute;
z-index: 1;
width: 10px;
height: 10px;
border-radius: 3px;
background: #15803d;
box-shadow: 0 0 0 4px rgba(21, 128, 61, 0.12), 0 8px 18px rgba(21, 128, 61, 0.22);
}
.automation-inventory-autonomy-workspace-packet-a {
top: 48%;
left: 10%;
animation: autonomy-packet-x 4.8s linear infinite;
}
.automation-inventory-autonomy-workspace-packet-b {
top: 22%;
left: 48%;
animation: autonomy-packet-y 5.4s linear infinite;
animation-delay: 0.8s;
}
.automation-inventory-autonomy-workspace-packet-c {
top: 72%;
left: 16%;
animation: autonomy-packet-x 6.2s linear infinite;
animation-delay: 1.6s;
}
.automation-inventory-autonomy-workspace-station {
animation: autonomy-station-breathe 3.8s ease-in-out infinite;
}
@keyframes autonomy-packet-x {
0% { left: 10%; opacity: 0; }
12% { opacity: 1; }
88% { opacity: 1; }
100% { left: 88%; opacity: 0; }
}
@keyframes autonomy-packet-y {
0% { top: 18%; opacity: 0; }
12% { opacity: 1; }
88% { opacity: 1; }
100% { top: 78%; opacity: 0; }
}
@keyframes autonomy-station-breathe {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-2px); }
}
@media (max-width: 900px) {
.automation-inventory-autonomy-cockpit-header,
.automation-inventory-autonomy-cockpit-hero-grid,
.automation-inventory-autonomy-cockpit-metric-grid,
.automation-inventory-autonomy-cockpit-flow-grid,
.automation-inventory-autonomy-workspace-grid {
grid-template-columns: 1fr !important;
}
.automation-inventory-autonomy-workspace-scene {
min-height: 430px !important;
}
.automation-inventory-autonomy-workspace-console {
position: relative !important;
inset: auto !important;
transform: none !important;
width: 100% !important;
height: auto !important;
min-height: 104px;
margin-bottom: 10px;
}
.automation-inventory-autonomy-workspace-rail,
.automation-inventory-autonomy-workspace-packet {
display: none;
}
}
`}</style>
</GlassCard>
)
}
function AutonomousRuntimeControlReadbackGrid({
control,
t,
@@ -3370,7 +3627,10 @@ export function AutomationInventoryTab() {
if (loading) {
if (reportTruthActionabilityReview) {
return (
<div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
<div className="automation-inventory-tab-root" style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0, maxWidth: '100%', overflowX: 'hidden' }}>
{autonomousRuntimeControl ? (
<AutonomousRuntimeControlCockpitCard control={autonomousRuntimeControl} t={t} />
) : null}
<GlassCard variant="subtle" padding="md">
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
@@ -3390,9 +3650,6 @@ export function AutomationInventoryTab() {
t={t}
pendingReadModelCount={1}
/>
{autonomousRuntimeControl ? (
<AutonomousRuntimeControlPriorityPanel control={autonomousRuntimeControl} t={t} />
) : null}
<GlassCard variant="subtle" padding="md">
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, color: '#87867f', lineHeight: 1.55, overflowWrap: 'anywhere' }}>
{t('readModelPartial.boundary')}
@@ -3416,9 +3673,9 @@ export function AutomationInventoryTab() {
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !warRoom || !professionalTaskExpansion || !receiptReadbackOwnerReview || !reportNoWriteAnalysisRuntime || !lowMediumRiskWhitelist || !highRiskOwnerReviewQueue || !controlledExecutorHandoff || !actionAuditLedger || !actionOwnerAcceptanceEventBus || !hostRunawayAiops || !proactiveOperations || !versionLifecycleProposal || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportStatusBoard || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !runtimeReadbackApprovalPackage || !runtimeReadbackImplementationReview || !reportLiveDeliveryApprovalPackage || !runtimeReadbackFixtureApproval || !runtimeReadbackPromotionGate || !ownerApprovedFixturePromotionGate || !canonicalRuntimeReadbackOwnerAcceptance || !failureReceiptNoSendReplay || !reviewerQueueNoWriteReadback || !resultCaptureNoWriteReadback || !resultCapturePromotionApprovalGate || !ownerApprovedResultCapturePromotionDryRun || !resultCaptureWriteGateReview || !resultCaptureWriterImplementationReview || !resultCaptureWriterDryRunFixture || !resultCaptureWriterDryRunReadback || !resultCaptureOwnerPromotionReview || !resultCaptureOwnerApprovedExecutionRehearsal || !resultCaptureOwnerAcceptanceMaintenanceGate || !resultCaptureOwnerAcceptanceReadbackPreflightHold || !resultCaptureOwnerApprovedPreflightReleasePackage || !resultCaptureOwnerApprovedReleaseReadinessReadback || !resultCaptureOwnerReleaseApprovalGate || !resultCapturePostReleaseVerifierRollbackGate || !resultCaptureFinalReleaseCandidateReadback || !resultCaptureReleaseAuthorizationHold || !resultCaptureReleaseAuthorizationReadbackGate || !resultCaptureReleaseVerifierPreflightGate || !resultCaptureReleaseVerifierOwnerReviewPacket || !resultCaptureReleaseDecisionHold || !resultCaptureReleaseDecisionReadback || !resultCaptureReleaseDecisionNextHandoff || !resultCaptureReleaseDecisionInputPrep || !resultCaptureReleaseDecisionOwnerResponsePreflight || !resultCaptureReleaseDecisionOwnerResponseReadback || !resultCaptureReleaseDecisionOwnerResponseAcceptanceGate || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !dependencySupplyChainDriftMonitor || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
return (
<div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div className="automation-inventory-tab-root" style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0, maxWidth: '100%', overflowX: 'hidden' }}>
{autonomousRuntimeControl ? (
<AutonomousRuntimeControlPriorityPanel control={autonomousRuntimeControl} t={t} />
<AutonomousRuntimeControlCockpitCard control={autonomousRuntimeControl} t={t} />
) : null}
<GlassCard variant="subtle" padding="lg">
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12, padding: '24px 0' }}>