feat(governance): 接入三 Agent 佈建布局
This commit is contained in:
@@ -35,6 +35,7 @@ import { GlassCard } from '@/components/ui/glass-card'
|
||||
import { StatusOrb } from '@/components/ui/status-orb'
|
||||
import {
|
||||
apiClient,
|
||||
type AiAgentDeploymentLayoutSnapshot,
|
||||
type AiProviderRouteMatrixSnapshot,
|
||||
type AiAgentAutomationBacklogSnapshot,
|
||||
type AiAgentAutomationInventorySnapshot,
|
||||
@@ -311,6 +312,7 @@ export function AutomationInventoryTab() {
|
||||
const [giteaHealth, setGiteaHealth] = useState<GiteaWorkflowRunnerHealthSnapshot | null>(null)
|
||||
const [observabilityMatrix, setObservabilityMatrix] = useState<ObservabilityContractMatrixSnapshot | null>(null)
|
||||
const [providerRouteMatrix, setProviderRouteMatrix] = useState<AiProviderRouteMatrixSnapshot | null>(null)
|
||||
const [deploymentLayout, setDeploymentLayout] = useState<AiAgentDeploymentLayoutSnapshot | null>(null)
|
||||
const [serviceHealthGapMatrix, setServiceHealthGapMatrix] = useState<ServiceHealthGapMatrixSnapshot | null>(null)
|
||||
const [serviceHealthNotificationPolicy, setServiceHealthNotificationPolicy] = useState<ServiceHealthFailureNotificationPolicySnapshot | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -329,6 +331,7 @@ export function AutomationInventoryTab() {
|
||||
apiClient.getGiteaWorkflowRunnerHealth(),
|
||||
apiClient.getObservabilityContractMatrix(),
|
||||
apiClient.getAiProviderRouteMatrix(),
|
||||
apiClient.getAiAgentDeploymentLayout(),
|
||||
apiClient.getServiceHealthGapMatrix(),
|
||||
apiClient.getServiceHealthFailureNotificationPolicy(),
|
||||
] as const
|
||||
@@ -346,6 +349,7 @@ export function AutomationInventoryTab() {
|
||||
giteaHealthResult,
|
||||
observabilityMatrixResult,
|
||||
providerRouteMatrixResult,
|
||||
deploymentLayoutResult,
|
||||
serviceHealthGapMatrixResult,
|
||||
serviceHealthNotificationPolicyResult,
|
||||
] = results
|
||||
@@ -360,6 +364,7 @@ export function AutomationInventoryTab() {
|
||||
setGiteaHealth(giteaHealthResult.status === 'fulfilled' ? giteaHealthResult.value : null)
|
||||
setObservabilityMatrix(observabilityMatrixResult.status === 'fulfilled' ? observabilityMatrixResult.value : null)
|
||||
setProviderRouteMatrix(providerRouteMatrixResult.status === 'fulfilled' ? providerRouteMatrixResult.value : null)
|
||||
setDeploymentLayout(deploymentLayoutResult.status === 'fulfilled' ? deploymentLayoutResult.value : null)
|
||||
setServiceHealthGapMatrix(serviceHealthGapMatrixResult.status === 'fulfilled' ? serviceHealthGapMatrixResult.value : null)
|
||||
setServiceHealthNotificationPolicy(serviceHealthNotificationPolicyResult.status === 'fulfilled' ? serviceHealthNotificationPolicyResult.value : null)
|
||||
setError([
|
||||
@@ -372,6 +377,7 @@ export function AutomationInventoryTab() {
|
||||
giteaHealthResult,
|
||||
observabilityMatrixResult,
|
||||
providerRouteMatrixResult,
|
||||
deploymentLayoutResult,
|
||||
serviceHealthGapMatrixResult,
|
||||
serviceHealthNotificationPolicyResult,
|
||||
].some(result => result.status === 'rejected'))
|
||||
@@ -409,6 +415,35 @@ export function AutomationInventoryTab() {
|
||||
.filter(group => group.items.length > 0)
|
||||
}, [backlog])
|
||||
|
||||
const visibleDeploymentTargets = useMemo(() => {
|
||||
if (!deploymentLayout) return []
|
||||
const statePriority = {
|
||||
blocked_by_gate: 0,
|
||||
active_governed: 1,
|
||||
read_only_layout: 2,
|
||||
planned: 3,
|
||||
candidate_only: 4,
|
||||
} as Record<string, number>
|
||||
const telegramPriority = {
|
||||
approval_required: 0,
|
||||
action_required: 1,
|
||||
failure_only: 2,
|
||||
daily_summary_only: 3,
|
||||
no_direct_notify: 4,
|
||||
} as Record<string, number>
|
||||
return [...deploymentLayout.deployment_targets]
|
||||
.sort((a, b) => {
|
||||
const stateLeft = statePriority[a.deployment_state] ?? 5
|
||||
const stateRight = statePriority[b.deployment_state] ?? 5
|
||||
if (stateLeft !== stateRight) return stateLeft - stateRight
|
||||
const telegramLeft = telegramPriority[a.telegram_policy] ?? 5
|
||||
const telegramRight = telegramPriority[b.telegram_policy] ?? 5
|
||||
if (telegramLeft !== telegramRight) return telegramLeft - telegramRight
|
||||
return a.target_id.localeCompare(b.target_id)
|
||||
})
|
||||
.slice(0, 12)
|
||||
}, [deploymentLayout])
|
||||
|
||||
const visibleReadinessRows = useMemo(() => {
|
||||
if (!backupReadiness) return []
|
||||
const priority = { blocked: 0, action_required: 1, deferred: 2, ready: 3 } as Record<string, number>
|
||||
@@ -552,7 +587,7 @@ export function AutomationInventoryTab() {
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<GlassCard variant="subtle" padding="lg">
|
||||
@@ -619,6 +654,12 @@ export function AutomationInventoryTab() {
|
||||
+ providerRouteMatrix.rollups.shadow_or_canary_allowed_count
|
||||
+ providerRouteMatrix.rollups.runtime_route_change_allowed_count
|
||||
)
|
||||
const deploymentLayoutTargets = deploymentLayout.rollups.total_targets
|
||||
const deploymentLayoutOpenClawTargets = deploymentLayout.rollups.by_primary_agent.openclaw ?? 0
|
||||
const deploymentLayoutHermesTargets = deploymentLayout.rollups.by_primary_agent.hermes ?? 0
|
||||
const deploymentLayoutNemotronTargets = deploymentLayout.rollups.by_primary_agent.nemotron ?? 0
|
||||
const deploymentLayoutBlockedTargets = deploymentLayout.rollups.blocked_target_ids.length
|
||||
const deploymentLayoutApprovalTargets = deploymentLayout.rollups.approval_required_target_ids.length
|
||||
const serviceHealthActions = serviceHealthGapMatrix.rollups.target_ids_requiring_action.length
|
||||
const serviceHealthStaleGaps = serviceHealthGapMatrix.rollups.stale_endpoint_ids.length
|
||||
const serviceHealthOperatorReviews = serviceHealthGapMatrix.rollups.health_gap_ids.length
|
||||
@@ -771,6 +812,14 @@ export function AutomationInventoryTab() {
|
||||
}
|
||||
}
|
||||
|
||||
const deploymentLayoutValueLabel = (group: string, value: string) => {
|
||||
try {
|
||||
return t(`deploymentLayout.${group}.${value}` as never)
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const serviceHealthValueLabel = (value: string) => {
|
||||
try {
|
||||
return t(`serviceHealth.values.${value}` as never)
|
||||
@@ -942,6 +991,124 @@ export function AutomationInventoryTab() {
|
||||
<MetricCard label={t('metrics.explicitApprovalTasks')} value={explicitApprovalTaskCount} tone={explicitApprovalTaskCount > 0 ? 'warn' : 'ok'} icon={<ShieldCheck size={16} />} />
|
||||
</div>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
|
||||
<Route size={14} style={{ color: '#d97757' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('deploymentLayout.title')}
|
||||
</span>
|
||||
</div>
|
||||
<Chip
|
||||
value={t('deploymentLayout.source', {
|
||||
generated: formatDateTime(deploymentLayout.generated_at),
|
||||
current: deploymentLayout.program_status.current_task_id,
|
||||
next: deploymentLayout.program_status.next_task_id,
|
||||
})}
|
||||
muted
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, minmax(0, 1fr))', gap: 12 }} className="automation-inventory-deployment-layout-kpi-grid">
|
||||
<MetricCard label={t('deploymentLayout.metrics.targets')} value={deploymentLayoutTargets} icon={<Boxes size={16} />} />
|
||||
<MetricCard label={t('deploymentLayout.metrics.openclaw')} value={deploymentLayoutOpenClawTargets} icon={<ShieldCheck size={16} />} />
|
||||
<MetricCard label={t('deploymentLayout.metrics.hermes')} value={deploymentLayoutHermesTargets} icon={<FileText size={16} />} />
|
||||
<MetricCard label={t('deploymentLayout.metrics.nemotron')} value={deploymentLayoutNemotronTargets} tone="warn" icon={<Target size={16} />} />
|
||||
<MetricCard label={t('deploymentLayout.metrics.approval')} value={deploymentLayoutApprovalTargets} tone={deploymentLayoutApprovalTargets > 0 ? 'warn' : 'ok'} icon={<Lock size={16} />} />
|
||||
<MetricCard label={t('deploymentLayout.metrics.blocked')} value={deploymentLayoutBlockedTargets} tone={deploymentLayoutBlockedTargets > 0 ? 'danger' : 'ok'} icon={<AlertTriangle size={16} />} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.25fr) minmax(0, 0.75fr)', gap: 12 }} className="automation-inventory-deployment-layout-grid">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('deploymentLayout.targetsTitle')}
|
||||
</span>
|
||||
<Chip value={t('deploymentLayout.targetsShown', { count: visibleDeploymentTargets.length, total: deploymentLayoutTargets })} muted />
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-deployment-layout-target-grid">
|
||||
{visibleDeploymentTargets.map(target => (
|
||||
<div key={target.target_id} style={{ padding: 11, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, minWidth: 0 }}>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, fontWeight: 700, color: '#141413' }}>
|
||||
{target.target_id}
|
||||
</span>
|
||||
<Chip value={deploymentLayoutValueLabel('deploymentStates', target.deployment_state)} muted={target.deployment_state === 'active_governed'} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
<Chip value={`${t('deploymentLayout.labels.domain')}: ${deploymentLayoutValueLabel('domains', target.domain_id)}`} muted />
|
||||
<Chip value={`${t('deploymentLayout.labels.primary')}: ${deploymentLayoutValueLabel('agents', target.primary_agent)}`} />
|
||||
<Chip value={`${t('deploymentLayout.labels.telegram')}: ${deploymentLayoutValueLabel('telegramPolicies', target.telegram_policy)}`} muted={target.telegram_policy !== 'approval_required'} />
|
||||
<Chip value={`${t('deploymentLayout.labels.automation')}: ${deploymentLayoutValueLabel('automationLevels', target.automation_level)}`} muted={target.automation_level === 'observe_only'} />
|
||||
</div>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{target.next_action}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
{target.supporting_agents.length > 0 ? (
|
||||
<Chip value={`${t('deploymentLayout.labels.support')}: ${target.supporting_agents.map(agent => deploymentLayoutValueLabel('agents', agent)).join(' / ')}`} muted />
|
||||
) : null}
|
||||
<Chip value={`${t('deploymentLayout.labels.evidence')}: ${target.evidence_refs.length}`} muted />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
|
||||
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 9, minWidth: 0 }}>
|
||||
<SmallLabel>{t('deploymentLayout.agentContractTitle')}</SmallLabel>
|
||||
{deploymentLayout.agent_contracts.map(contract => (
|
||||
<div key={contract.agent_id} style={{ display: 'flex', flexDirection: 'column', gap: 5, paddingBottom: 7, borderBottom: '0.5px solid #eee9dd' }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413' }}>
|
||||
{deploymentLayoutValueLabel('agents', contract.agent_id)}
|
||||
</span>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45 }}>
|
||||
{contract.primary_specialty}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
{contract.allowed_autonomy.slice(0, 2).map(item => (
|
||||
<Chip key={`${contract.agent_id}-${item}`} value={item} muted />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<SmallLabel>{t('deploymentLayout.telegramTitle')}</SmallLabel>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{t('deploymentLayout.telegramDetail', {
|
||||
gateway: deploymentLayout.telegram_contract.primary_gateway,
|
||||
classes: deploymentLayout.telegram_contract.notification_classes.length,
|
||||
})}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={t('deploymentLayout.redactionLocked')} />
|
||||
<Chip value={t('deploymentLayout.directSendBlocked')} muted />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<SmallLabel>{t('deploymentLayout.learningTitle')}</SmallLabel>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{t('deploymentLayout.learningDetail', {
|
||||
sources: deploymentLayout.learning_contract.event_sources.length,
|
||||
loops: deploymentLayout.learning_contract.feedback_loops.length,
|
||||
metrics: deploymentLayout.learning_contract.growth_metrics.length,
|
||||
})}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={`${t('deploymentLayout.labels.bus')}: ${deploymentLayout.collaboration_contract.message_bus}`} muted />
|
||||
<Chip value={t('deploymentLayout.frontendSafe')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
@@ -2389,6 +2556,9 @@ export function AutomationInventoryTab() {
|
||||
.automation-inventory-provider-map-grid,
|
||||
.automation-inventory-provider-grid,
|
||||
.automation-inventory-provider-route-grid,
|
||||
.automation-inventory-deployment-layout-kpi-grid,
|
||||
.automation-inventory-deployment-layout-grid,
|
||||
.automation-inventory-deployment-layout-target-grid,
|
||||
.automation-inventory-service-health-kpi-grid,
|
||||
.automation-inventory-service-health-map-grid,
|
||||
.automation-inventory-service-health-evidence-grid,
|
||||
|
||||
Reference in New Issue
Block a user