/** * ReflectionLog - Self-reflection history and identity change approval UI * * Displays: * - Reflection history (patterns, improvements) * - Pending identity change proposals * - Approve/reject identity modifications * - Manual reflection trigger * * Part of ZCLAW L4 Self-Evolution capability. */ import { useState, useEffect, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Brain, Check, X, ChevronDown, ChevronRight, RefreshCw, AlertTriangle, TrendingUp, TrendingDown, Minus, FileText, History, Play, Settings, } from 'lucide-react'; import { intelligenceClient, type ReflectionResult, type IdentityChangeProposal, type ReflectionConfig, type PatternObservation, type ImprovementSuggestion, } from '../lib/intelligence-client'; // === Config Persistence === const REFLECTION_CONFIG_KEY = 'zclaw-reflection-config'; const DEFAULT_CONFIG: ReflectionConfig = { trigger_after_conversations: 5, allow_soul_modification: true, require_approval: true, use_llm: true, llm_fallback_to_rules: true, }; function loadConfig(): ReflectionConfig { try { const stored = localStorage.getItem(REFLECTION_CONFIG_KEY); if (stored) { const parsed = JSON.parse(stored); return { ...DEFAULT_CONFIG, ...parsed }; } } catch { console.warn('[ReflectionLog] Failed to load config from localStorage'); } return DEFAULT_CONFIG; } function saveConfig(config: ReflectionConfig): void { try { localStorage.setItem(REFLECTION_CONFIG_KEY, JSON.stringify(config)); } catch { console.warn('[ReflectionLog] Failed to save config to localStorage'); } } // === Types === interface ReflectionLogProps { className?: string; agentId?: string; onProposalApprove?: (proposal: IdentityChangeProposal) => void; onProposalReject?: (proposal: IdentityChangeProposal) => void; } // === Sentiment Config === const SENTIMENT_CONFIG: Record = { positive: { icon: TrendingUp, color: 'text-green-600 dark:text-green-400', bgColor: 'bg-green-100 dark:bg-green-900/30', }, negative: { icon: TrendingDown, color: 'text-red-600 dark:text-red-400', bgColor: 'bg-red-100 dark:bg-red-900/30', }, neutral: { icon: Minus, color: 'text-gray-600 dark:text-gray-400', bgColor: 'bg-gray-100 dark:bg-gray-800', }, }; // === Priority Config === const PRIORITY_CONFIG: Record = { high: { color: 'text-red-600 dark:text-red-400', bgColor: 'bg-red-100 dark:bg-red-900/30', }, medium: { color: 'text-yellow-600 dark:text-yellow-400', bgColor: 'bg-yellow-100 dark:bg-yellow-900/30', }, low: { color: 'text-blue-600 dark:text-blue-400', bgColor: 'bg-blue-100 dark:bg-blue-900/30', }, }; // === Components === function SentimentBadge({ sentiment }: { sentiment: string }) { const config = SENTIMENT_CONFIG[sentiment] || SENTIMENT_CONFIG.neutral; const Icon = config.icon; return ( {sentiment === 'positive' ? '积极' : sentiment === 'negative' ? '消极' : '中性'} ); } function PriorityBadge({ priority }: { priority: string }) { const config = PRIORITY_CONFIG[priority] || PRIORITY_CONFIG.medium; return ( {priority === 'high' ? '高' : priority === 'medium' ? '中' : '低'} ); } function PatternCard({ pattern }: { pattern: PatternObservation }) { const [expanded, setExpanded] = useState(false); return (
{expanded && pattern.evidence.length > 0 && (
证据
    {pattern.evidence.map((ev, i) => (
  • {ev}
  • ))}
)}
); } function ImprovementCard({ improvement }: { improvement: ImprovementSuggestion }) { return (
{improvement.area}

{improvement.suggestion}

); } function ProposalCard({ proposal, onApprove, onReject, }: { proposal: IdentityChangeProposal; onApprove: () => void; onReject: () => void; }) { const [expanded, setExpanded] = useState(false); const fileName = proposal.file.split('/').pop() || proposal.file; const fileType = fileName.toLowerCase().replace('.md', '').toUpperCase(); return (
{fileType} 变更提议 待审批

{proposal.reason}

{expanded && (
当前内容
                  {proposal.current_content.slice(0, 500)}
                  {proposal.current_content.length > 500 && '...'}
                
建议内容
                  {proposal.suggested_content.slice(0, 500)}
                  {proposal.suggested_content.length > 500 && '...'}
                
)}
); } function ReflectionEntry({ result, isExpanded, onToggle, }: { result: ReflectionResult; isExpanded: boolean; onToggle: () => void; }) { const positivePatterns = result.patterns.filter((p) => p.sentiment === 'positive').length; const negativePatterns = result.patterns.filter((p) => p.sentiment === 'negative').length; return (
{isExpanded && (
{/* Patterns */} {result.patterns.length > 0 && (

行为模式

{result.patterns.map((pattern, i) => ( ))}
)} {/* Improvements */} {result.improvements.length > 0 && (

改进建议

{result.improvements.map((improvement, i) => ( ))}
)} {/* Meta */}
新增记忆: {result.new_memories} 身份变更提议: {result.identity_proposals.length}
)}
); } // === Main Component === export function ReflectionLog({ className = '', agentId = 'zclaw-main', onProposalApprove, onProposalReject, }: ReflectionLogProps) { const [history, setHistory] = useState([]); const [pendingProposals, setPendingProposals] = useState([]); const [expandedId, setExpandedId] = useState(null); const [isReflecting, setIsReflecting] = useState(false); const [showConfig, setShowConfig] = useState(false); const [config, setConfig] = useState(() => loadConfig()); // Persist config changes useEffect(() => { saveConfig(config); }, [config]); // Load history and pending proposals useEffect(() => { const loadData = async () => { try { // Initialize reflection engine with config that allows soul modification await intelligenceClient.reflection.init(config); const loadedHistory = await intelligenceClient.reflection.getHistory(); setHistory([...loadedHistory].reverse()); // Most recent first const proposals = await intelligenceClient.identity.getPendingProposals(agentId); setPendingProposals(proposals); } catch (error) { console.error('[ReflectionLog] Failed to load data:', error); } }; loadData(); }, [agentId, config]); const handleReflect = useCallback(async () => { setIsReflecting(true); try { const result = await intelligenceClient.reflection.reflect(agentId, []); setHistory((prev) => [result, ...prev]); // Convert reflection identity_proposals to actual identity proposals // The reflection result contains proposals that need to be persisted if (result.identity_proposals && result.identity_proposals.length > 0) { for (const proposal of result.identity_proposals) { try { // Determine which file to modify based on the field const file: 'soul' | 'instructions' = proposal.field === 'soul' || proposal.field === 'instructions' ? (proposal.field as 'soul' | 'instructions') : proposal.field.toLowerCase().includes('soul') ? 'soul' : 'instructions'; // Persist the proposal to the identity system await intelligenceClient.identity.proposeChange( agentId, file, proposal.proposed_value, proposal.reason ); } catch (err) { console.warn('[ReflectionLog] Failed to create identity proposal:', err); } } // Refresh pending proposals from the identity system const proposals = await intelligenceClient.identity.getPendingProposals(agentId); setPendingProposals(proposals); } } catch (error) { console.error('[ReflectionLog] Reflection failed:', error); } finally { setIsReflecting(false); } }, [agentId]); const handleApproveProposal = useCallback( async (proposal: IdentityChangeProposal) => { await intelligenceClient.identity.approveProposal(proposal.id); setPendingProposals((prev: IdentityChangeProposal[]) => prev.filter((p: IdentityChangeProposal) => p.id !== proposal.id)); onProposalApprove?.(proposal); }, [onProposalApprove] ); const handleRejectProposal = useCallback( async (proposal: IdentityChangeProposal) => { await intelligenceClient.identity.rejectProposal(proposal.id); setPendingProposals((prev: IdentityChangeProposal[]) => prev.filter((p: IdentityChangeProposal) => p.id !== proposal.id)); onProposalReject?.(proposal); }, [onProposalReject] ); const stats = useMemo(() => { const totalReflections = history.length; const totalPatterns = history.reduce((sum: number, r: ReflectionResult) => sum + r.patterns.length, 0); const totalImprovements = history.reduce((sum: number, r: ReflectionResult) => sum + r.improvements.length, 0); const totalIdentityChanges = history.reduce((sum: number, r: ReflectionResult) => sum + r.identity_proposals.length, 0); return { totalReflections, totalPatterns, totalImprovements, totalIdentityChanges }; }, [history]); return (
{/* Header */}

反思日志

{/* Stats Bar */}
反思: {stats.totalReflections} 模式: {stats.totalPatterns} 建议: {stats.totalImprovements} 变更: {stats.totalIdentityChanges}
{/* Config Panel */} {showConfig && (
对话后触发反思 setConfig((prev) => ({ ...prev, trigger_after_conversations: parseInt(e.target.value) || 5 })) } className="w-16 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100" />
允许修改 SOUL.md
变更需审批
)}
{/* Content */}
{/* Pending Proposals */} {pendingProposals.length > 0 && (

待审批变更 ({pendingProposals.length})

{pendingProposals.map((proposal) => ( handleApproveProposal(proposal)} onReject={() => handleRejectProposal(proposal)} /> ))}
)} {/* History */}

反思历史

{history.length === 0 ? (

暂无反思记录

) : ( history.map((result) => ( setExpandedId((prev) => (prev === result.timestamp ? null : result.timestamp))} /> )) )}
); } export default ReflectionLog;