feat(governance): 新增 AI Agent 活動動畫
This commit is contained in:
@@ -3024,11 +3024,27 @@
|
||||
"error": "無法載入待辦佇列",
|
||||
"retry": "重試"
|
||||
},
|
||||
"agentActivity": {
|
||||
"automationTitle": "AI Agent 協作脈衝",
|
||||
"automationSubtitle": "OpenClaw 仲裁,Hermes 記憶,NemoTron 執行;目前只讀讀回,正式寫入維持 0。",
|
||||
"marketTitle": "Agent 市場觀測脈衝",
|
||||
"marketSubtitle": "外部候選先進入觀測與評分,Agent 只產生證據與批准包,不自動替換。",
|
||||
"status": "狀態",
|
||||
"footnote": "原創動態參考官方 Agent 視覺語言,不使用官方商標或素材。"
|
||||
},
|
||||
"agentMarket": {
|
||||
"title": "Agent 市場治理",
|
||||
"generatedAt": "產生時間",
|
||||
"error": "無法載入 Agent 市場治理快照",
|
||||
"retry": "重試",
|
||||
"agentActivity": {
|
||||
"metrics": {
|
||||
"candidates": "候選數",
|
||||
"prescreenReady": "可預篩",
|
||||
"blocked": "已阻擋",
|
||||
"approvals": "Runtime 批准"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"candidates": "候選數",
|
||||
"sources": "來源數",
|
||||
@@ -3141,6 +3157,14 @@
|
||||
"readOnly": "只讀模式",
|
||||
"error": "無法載入自動化盤點快照",
|
||||
"retry": "重試",
|
||||
"agentActivity": {
|
||||
"metrics": {
|
||||
"backlog": "待辦進度",
|
||||
"readback": "讀回關卡",
|
||||
"gates": "人工 gate",
|
||||
"liveWrites": "正式寫入"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"progress": "整體進度",
|
||||
"assets": "資產數",
|
||||
|
||||
@@ -3024,11 +3024,27 @@
|
||||
"error": "無法載入待辦佇列",
|
||||
"retry": "重試"
|
||||
},
|
||||
"agentActivity": {
|
||||
"automationTitle": "AI Agent 協作脈衝",
|
||||
"automationSubtitle": "OpenClaw 仲裁,Hermes 記憶,NemoTron 執行;目前只讀讀回,正式寫入維持 0。",
|
||||
"marketTitle": "Agent 市場觀測脈衝",
|
||||
"marketSubtitle": "外部候選先進入觀測與評分,Agent 只產生證據與批准包,不自動替換。",
|
||||
"status": "狀態",
|
||||
"footnote": "原創動態參考官方 Agent 視覺語言,不使用官方商標或素材。"
|
||||
},
|
||||
"agentMarket": {
|
||||
"title": "Agent 市場治理",
|
||||
"generatedAt": "產生時間",
|
||||
"error": "無法載入 Agent 市場治理快照",
|
||||
"retry": "重試",
|
||||
"agentActivity": {
|
||||
"metrics": {
|
||||
"candidates": "候選數",
|
||||
"prescreenReady": "可預篩",
|
||||
"blocked": "已阻擋",
|
||||
"approvals": "Runtime 批准"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"candidates": "候選數",
|
||||
"sources": "來源數",
|
||||
@@ -3141,6 +3157,14 @@
|
||||
"readOnly": "只讀模式",
|
||||
"error": "無法載入自動化盤點快照",
|
||||
"retry": "重試",
|
||||
"agentActivity": {
|
||||
"metrics": {
|
||||
"backlog": "待辦進度",
|
||||
"readback": "讀回關卡",
|
||||
"gates": "人工 gate",
|
||||
"liveWrites": "正式寫入"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"progress": "整體進度",
|
||||
"assets": "資產數",
|
||||
|
||||
@@ -13,6 +13,7 @@ import { AlertTriangle, Ban, CalendarClock, CheckCircle2, ListChecks, Lock, Refr
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { GlassCard } from '@/components/ui/glass-card'
|
||||
import { StatusOrb } from '@/components/ui/status-orb'
|
||||
import { AgentActivityConstellation } from '@/components/governance/agent-activity-constellation'
|
||||
import { apiClient, type AgentMarketGovernanceSnapshot } from '@/lib/api-client'
|
||||
|
||||
// =============================================================================
|
||||
@@ -306,6 +307,17 @@ export function AgentMarketTab() {
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<AgentActivityConstellation
|
||||
mode="market"
|
||||
statusValue={snapshot.current_decision}
|
||||
metrics={[
|
||||
{ label: t('agentActivity.metrics.candidates'), value: summary.candidate_count, tone: 'neutral' },
|
||||
{ label: t('agentActivity.metrics.prescreenReady'), value: summary.eligible_for_market_scorecard_prescreen, tone: 'ok' },
|
||||
{ label: t('agentActivity.metrics.blocked'), value: summary.blocked_from_integration, tone: summary.blocked_from_integration > 0 ? 'warn' : 'ok' },
|
||||
{ label: t('agentActivity.metrics.approvals'), value: allApprovals, tone: allApprovals === 0 ? 'ok' : 'warn' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, minmax(0, 1fr))',
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { GlassCard } from '@/components/ui/glass-card'
|
||||
import { StatusOrb } from '@/components/ui/status-orb'
|
||||
import { AgentActivityConstellation } from '@/components/governance/agent-activity-constellation'
|
||||
import {
|
||||
apiClient,
|
||||
type AiAgentCandidateOperationDryRunEvidenceSnapshot,
|
||||
@@ -3576,6 +3577,17 @@ export function AutomationInventoryTab() {
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<AgentActivityConstellation
|
||||
mode="automation"
|
||||
statusValue={`${snapshot.program_status.current_task_id} → ${snapshot.program_status.next_task_id}`}
|
||||
metrics={[
|
||||
{ label: t('agentActivity.metrics.backlog'), value: `${backlogProgressPercent}%`, tone: 'ok' },
|
||||
{ label: t('agentActivity.metrics.readback'), value: `${resultCaptureReleaseReadbackOverall}%`, tone: 'ok' },
|
||||
{ label: t('agentActivity.metrics.gates'), value: explicitApprovalTaskCount, tone: explicitApprovalTaskCount > 0 ? 'warn' : 'ok' },
|
||||
{ label: t('agentActivity.metrics.liveWrites'), value: resultCaptureReleaseReadbackLiveWrites, tone: resultCaptureReleaseReadbackLiveWrites === 0 ? 'ok' : 'danger' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.05fr) minmax(0, 0.95fr)', gap: 12 }} className="automation-inventory-command-grid">
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
|
||||
|
||||
@@ -0,0 +1,472 @@
|
||||
'use client'
|
||||
|
||||
import { BrainCircuit, Cpu, RadioTower, Route, ShieldCheck } from 'lucide-react'
|
||||
import type { CSSProperties } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { GlassCard } from '@/components/ui/glass-card'
|
||||
|
||||
type AgentActivityMode = 'automation' | 'market'
|
||||
type AgentActivityTone = 'ok' | 'warn' | 'danger' | 'neutral'
|
||||
|
||||
export type AgentActivityMetric = {
|
||||
label: string
|
||||
value: string | number
|
||||
tone?: AgentActivityTone
|
||||
}
|
||||
|
||||
export type AgentActivityConstellationProps = {
|
||||
mode: AgentActivityMode
|
||||
statusValue: string
|
||||
metrics: AgentActivityMetric[]
|
||||
}
|
||||
|
||||
function toneColor(tone: AgentActivityTone = 'neutral') {
|
||||
if (tone === 'ok') return '#22C55E'
|
||||
if (tone === 'warn') return '#F59E0B'
|
||||
if (tone === 'danger') return '#EF4444'
|
||||
return '#4A90D9'
|
||||
}
|
||||
|
||||
export function AgentActivityConstellation({
|
||||
mode,
|
||||
statusValue,
|
||||
metrics,
|
||||
}: AgentActivityConstellationProps) {
|
||||
const t = useTranslations('governance.agentActivity')
|
||||
const isMarket = mode === 'market'
|
||||
const title = isMarket ? t('marketTitle') : t('automationTitle')
|
||||
const subtitle = isMarket ? t('marketSubtitle') : t('automationSubtitle')
|
||||
|
||||
return (
|
||||
<GlassCard variant="subtle" padding="md" className="agent-activity-constellation min-w-0">
|
||||
<div className="agent-activity-layout">
|
||||
<div
|
||||
className={`agent-activity-stage ${isMarket ? 'is-market' : 'is-automation'}`}
|
||||
aria-label={title}
|
||||
role="img"
|
||||
>
|
||||
<div className="agent-activity-orbit agent-activity-orbit-outer" />
|
||||
<div className="agent-activity-orbit agent-activity-orbit-inner" />
|
||||
<div className="agent-activity-bus">
|
||||
<RadioTower size={18} strokeWidth={1.8} />
|
||||
</div>
|
||||
<div className="agent-activity-node agent-activity-node-openclaw">
|
||||
<ShieldCheck size={17} strokeWidth={1.9} />
|
||||
<span>OpenClaw</span>
|
||||
</div>
|
||||
<div className="agent-activity-node agent-activity-node-hermes">
|
||||
<BrainCircuit size={17} strokeWidth={1.9} />
|
||||
<span>Hermes</span>
|
||||
</div>
|
||||
<div className="agent-activity-node agent-activity-node-nemotron">
|
||||
<Cpu size={17} strokeWidth={1.9} />
|
||||
<span>NemoTron</span>
|
||||
</div>
|
||||
<div className="agent-activity-packet agent-activity-packet-a" />
|
||||
<div className="agent-activity-packet agent-activity-packet-b" />
|
||||
<div className="agent-activity-packet agent-activity-packet-c" />
|
||||
</div>
|
||||
|
||||
<div className="agent-activity-copy">
|
||||
<div className="agent-activity-heading">
|
||||
<Route size={14} strokeWidth={1.8} />
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
<p>{subtitle}</p>
|
||||
<div className="agent-activity-status">
|
||||
<span>{t('status')}</span>
|
||||
<strong>{statusValue}</strong>
|
||||
</div>
|
||||
<div className="agent-activity-metrics">
|
||||
{metrics.map((metric) => (
|
||||
<div
|
||||
key={`${metric.label}-${metric.value}`}
|
||||
className="agent-activity-metric"
|
||||
style={{ '--metric-color': toneColor(metric.tone) } as CSSProperties}
|
||||
>
|
||||
<span>{metric.label}</span>
|
||||
<strong>{metric.value}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="agent-activity-footnote">{t('footnote')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style jsx>{`
|
||||
.agent-activity-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, 0.82fr) minmax(0, 1.18fr);
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-activity-stage {
|
||||
position: relative;
|
||||
min-height: 190px;
|
||||
border-radius: 8px;
|
||||
border: 0.5px solid rgba(20, 20, 19, 0.06);
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(20, 20, 19, 0.035) 1px, transparent 1px),
|
||||
linear-gradient(0deg, rgba(20, 20, 19, 0.035) 1px, transparent 1px),
|
||||
radial-gradient(circle at 50% 50%, rgba(74, 144, 217, 0.13), transparent 56%),
|
||||
#fbfaf5;
|
||||
background-size: 28px 28px, 28px 28px, auto, auto;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.agent-activity-stage::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -40%;
|
||||
background: conic-gradient(
|
||||
from 0deg,
|
||||
transparent 0deg,
|
||||
rgba(74, 144, 217, 0.2) 62deg,
|
||||
transparent 126deg,
|
||||
rgba(34, 197, 94, 0.18) 206deg,
|
||||
transparent 300deg
|
||||
);
|
||||
animation: agent-activity-sweep 9s linear infinite;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.agent-activity-stage.is-market::before {
|
||||
background: conic-gradient(
|
||||
from 0deg,
|
||||
transparent 0deg,
|
||||
rgba(245, 158, 11, 0.2) 62deg,
|
||||
transparent 142deg,
|
||||
rgba(74, 144, 217, 0.2) 230deg,
|
||||
transparent 300deg
|
||||
);
|
||||
}
|
||||
|
||||
.agent-activity-orbit {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(20, 20, 19, 0.08);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.agent-activity-orbit-outer {
|
||||
width: 172px;
|
||||
height: 124px;
|
||||
border-style: dashed;
|
||||
animation: agent-activity-drift 12s linear infinite;
|
||||
}
|
||||
|
||||
.agent-activity-orbit-inner {
|
||||
width: 104px;
|
||||
height: 72px;
|
||||
border-color: rgba(74, 144, 217, 0.24);
|
||||
animation: agent-activity-drift-reverse 10s linear infinite;
|
||||
}
|
||||
|
||||
.agent-activity-bus {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
color: #141413;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
border: 0.5px solid rgba(20, 20, 19, 0.08);
|
||||
box-shadow: 0 12px 26px rgba(20, 20, 19, 0.08);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.agent-activity-node {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: calc(100% - 24px);
|
||||
min-height: 30px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 7px;
|
||||
border: 0.5px solid rgba(20, 20, 19, 0.08);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 10px 22px rgba(20, 20, 19, 0.06);
|
||||
color: #141413;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 10px;
|
||||
line-height: 1.15;
|
||||
white-space: nowrap;
|
||||
animation: agent-activity-node-pulse 3.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.agent-activity-node-openclaw {
|
||||
left: 16px;
|
||||
top: 20px;
|
||||
color: #4a90d9;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.agent-activity-node-hermes {
|
||||
right: 14px;
|
||||
top: 56px;
|
||||
color: #d97757;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.agent-activity-node-nemotron {
|
||||
left: 50%;
|
||||
bottom: 18px;
|
||||
color: #22c55e;
|
||||
transform: translateX(-50%);
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.agent-activity-packet {
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: #4a90d9;
|
||||
box-shadow: 0 0 18px rgba(74, 144, 217, 0.55);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.agent-activity-packet-a {
|
||||
animation: agent-activity-packet-a 4.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
.agent-activity-packet-b {
|
||||
background: #d97757;
|
||||
box-shadow: 0 0 18px rgba(217, 119, 87, 0.5);
|
||||
animation: agent-activity-packet-b 5.4s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
.agent-activity-packet-c {
|
||||
background: #22c55e;
|
||||
box-shadow: 0 0 18px rgba(34, 197, 94, 0.5);
|
||||
animation: agent-activity-packet-c 6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
.agent-activity-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-activity-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
color: #d97757;
|
||||
}
|
||||
|
||||
.agent-activity-heading span {
|
||||
color: #141413;
|
||||
font-family: Syne, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-copy p {
|
||||
margin: 0;
|
||||
color: #625f58;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.55;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
color: #87867f;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 10px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-status strong {
|
||||
color: #141413;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-activity-metric {
|
||||
min-width: 0;
|
||||
border-left: 2px solid var(--metric-color);
|
||||
padding: 7px 8px;
|
||||
border-radius: 6px;
|
||||
background: rgba(250, 249, 243, 0.74);
|
||||
}
|
||||
|
||||
.agent-activity-metric span,
|
||||
.agent-activity-footnote {
|
||||
display: block;
|
||||
color: #87867f;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 9px;
|
||||
line-height: 1.3;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-metric strong {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
color: #141413;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-footnote {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
@keyframes agent-activity-sweep {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-drift {
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-drift-reverse {
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-node-pulse {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 10px 22px rgba(20, 20, 19, 0.06);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 12px 30px rgba(74, 144, 217, 0.18);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-packet-a {
|
||||
0%,
|
||||
100% {
|
||||
left: 52px;
|
||||
top: 46px;
|
||||
opacity: 0.2;
|
||||
}
|
||||
42% {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
opacity: 1;
|
||||
}
|
||||
72% {
|
||||
left: calc(100% - 58px);
|
||||
top: 76px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-packet-b {
|
||||
0%,
|
||||
100% {
|
||||
right: 58px;
|
||||
top: 82px;
|
||||
opacity: 0.18;
|
||||
}
|
||||
42% {
|
||||
right: 50%;
|
||||
top: 50%;
|
||||
opacity: 1;
|
||||
}
|
||||
72% {
|
||||
right: calc(50% - 6px);
|
||||
top: calc(100% - 45px);
|
||||
opacity: 0.78;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-packet-c {
|
||||
0%,
|
||||
100% {
|
||||
left: 50%;
|
||||
bottom: 38px;
|
||||
opacity: 0.18;
|
||||
}
|
||||
42% {
|
||||
left: calc(50% - 2px);
|
||||
bottom: 50%;
|
||||
opacity: 1;
|
||||
}
|
||||
72% {
|
||||
left: 54px;
|
||||
bottom: calc(100% - 54px);
|
||||
opacity: 0.72;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.agent-activity-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.agent-activity-stage {
|
||||
min-height: 176px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.agent-activity-metrics {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.agent-activity-node {
|
||||
font-size: 9px;
|
||||
padding: 5px 7px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.agent-activity-stage::before,
|
||||
.agent-activity-orbit,
|
||||
.agent-activity-node,
|
||||
.agent-activity-packet {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.agent-activity-packet {
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,29 @@
|
||||
## 2026-06-14|AI Agent 活動動畫本地完成
|
||||
|
||||
**背景**:統帥要求在相關治理頁加入 AI Agent 動畫,讓使用者能直覺看見 OpenClaw、Hermes、NemoTron 正在分工、溝通與產生治理證據;同時不得把工作視窗對話內容顯示到前端頁面。
|
||||
|
||||
**完成內容**:
|
||||
- 新增 `AgentActivityConstellation` client component,使用原創抽象視覺呈現 `OpenClaw = 仲裁`、`Hermes = 記憶 / 學習`、`NemoTron = 執行 / 推理` 與中樞訊號匯流。
|
||||
- Governance `automation-inventory` 頁新增「AI Agent 協作脈衝」,顯示待辦進度、讀回關卡、人工 gate、正式寫入等既有 snapshot 指標。
|
||||
- Governance `agent-market` 頁新增「Agent 市場觀測脈衝」,顯示候選數、可預篩、已阻擋與 Runtime 批准等既有 market snapshot 指標。
|
||||
- 視覺參考 NVIDIA Nemotron 官方公開定位的 agentic / throughput / multi-agent execution,以及 Nous Hermes Agent 官方公開定位的 persistent memory / learning loop / skill growth;未使用官方 Logo、商標、圖片或素材。
|
||||
- 動畫支援 `prefers-reduced-motion: reduce`,小螢幕改為單欄與兩欄指標,避免文字溢出。
|
||||
- 前端只讀取既有 API snapshot 與翻譯 key;不新增後端寫入、不新增 Telegram send、不新增 Bot API、不新增 secret、不新增依賴、不新增 runtime 權限。
|
||||
|
||||
**本地驗證**:
|
||||
- JSON parse:`apps/web/messages/zh-TW.json`、`apps/web/messages/en.json` 通過。
|
||||
- Web typecheck:`pnpm --filter @awoooi/web typecheck` 通過。
|
||||
- Web production build:`NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --filter @awoooi/web build` 通過,92 個 static pages 生成完成。
|
||||
- Guard:`git diff --check`、`doc-secrets-sanity-check.py docs .gitea`、`source-control-owner-response-guard.py --root .`、`security-mirror-progress-guard.py --root .` 全部通過。
|
||||
- 本機 browser 預覽 `http://127.0.0.1:3011/zh-TW/governance?tab=automation-inventory` 因本機來源無法載入正式自動化 snapshot,只顯示既有「無法載入自動化盤點快照」狀態;不將此視為 production UI 真相。
|
||||
- 本機頁面讀回未出現 `批准!繼續`、`My request for Codex`、`In app browser`、`work_window_transcript`、`raw prompt`、`private reasoning`、`chain-of-thought` 等工作視窗內容。
|
||||
|
||||
**安全邊界**:
|
||||
- 本輪只是可視化層與翻譯層整合,不改 OpenClaw / Hermes / NemoTron runtime 分工、不改批准政策、不開啟正式寫入、不送 Telegram、不呼叫 Bot API、不讀 secret、不做 destructive action。
|
||||
|
||||
**下一步**:
|
||||
- 推送 Gitea main 後等待 CD;取得 deploy marker 後再做 production browser smoke,確認 `automation-inventory` 與 `agent-market` 皆可見 Agent 活動動畫、水平溢位 `0`、正式寫入仍為 `0`、禁用內部協作片語命中 `0`。
|
||||
|
||||
## 2026-06-14|P2-136 釋出驗證器預檢關卡本地完成
|
||||
|
||||
**背景**:P2-135 已把 release authorization readback gate 正式驗證完成;但授權讀回仍不得被誤讀成 post-release verifier ready、release authorization granted / passed、rollback release passed 或 live apply release passed。P2-136 因此只建立 release verifier preflight gate,把 release authorization readback、rollback release readback、maintenance window readback hold、live-apply release readback hold 與 blocked release readback transition 轉成釋出驗證器預檢視圖,供 operator / owner 後續審核。
|
||||
|
||||
Reference in New Issue
Block a user