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
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:
@@ -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' }}>
|
||||
|
||||
Reference in New Issue
Block a user