feat(intelligence): complete migration to Rust backend

- Unify all intelligence modules to use intelligenceClient
- Delete legacy TS implementations (agent-memory, reflection-engine, heartbeat-engine, context-compactor, agent-identity, memory-index)
- Update all consumers to use snake_case backend types
- Remove deprecated llm-integration.test.ts

This eliminates code duplication between frontend and backend, resolves
localStorage limitations, and enables persistent intelligence features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-21 15:17:39 +08:00
parent 17fb1e69aa
commit f3ec3c8d4c
24 changed files with 1172 additions and 3095 deletions

View File

@@ -29,14 +29,13 @@ import {
Settings,
} from 'lucide-react';
import {
ReflectionEngine,
intelligenceClient,
type ReflectionResult,
type IdentityChangeProposal,
type ReflectionConfig,
type PatternObservation,
type ImprovementSuggestion,
type ReflectionConfig,
DEFAULT_REFLECTION_CONFIG,
} from '../lib/reflection-engine';
import { getAgentIdentityManager, type IdentityChangeProposal } from '../lib/agent-identity';
} from '../lib/intelligence-client';
// === Types ===
@@ -231,8 +230,8 @@ function ProposalCard({
</h5>
<pre className="text-xs text-gray-600 dark:text-gray-300 bg-white dark:bg-gray-800 p-2 rounded overflow-x-auto whitespace-pre-wrap">
{proposal.currentContent.slice(0, 500)}
{proposal.currentContent.length > 500 && '...'}
{proposal.current_content.slice(0, 500)}
{proposal.current_content.length > 500 && '...'}
</pre>
</div>
<div>
@@ -240,8 +239,8 @@ function ProposalCard({
</h5>
<pre className="text-xs text-gray-600 dark:text-gray-300 bg-white dark:bg-gray-800 p-2 rounded overflow-x-auto whitespace-pre-wrap">
{proposal.suggestedContent.slice(0, 500)}
{proposal.suggestedContent.length > 500 && '...'}
{proposal.suggested_content.slice(0, 500)}
{proposal.suggested_content.length > 500 && '...'}
</pre>
</div>
</div>
@@ -309,9 +308,9 @@ function ReflectionEntry({
<span className="text-gray-500 dark:text-gray-400">
{result.improvements.length}
</span>
{result.identityProposals.length > 0 && (
{result.identity_proposals.length > 0 && (
<span className="text-yellow-600 dark:text-yellow-400">
{result.identityProposals.length}
{result.identity_proposals.length}
</span>
)}
</div>
@@ -362,8 +361,8 @@ function ReflectionEntry({
{/* Meta */}
<div className="flex items-center gap-4 text-xs text-gray-500 dark:text-gray-400 pt-2 border-t border-gray-200 dark:border-gray-700">
<span>: {result.newMemories}</span>
<span>: {result.identityProposals.length}</span>
<span>: {result.new_memories}</span>
<span>: {result.identity_proposals.length}</span>
</div>
</div>
</motion.div>
@@ -381,56 +380,63 @@ export function ReflectionLog({
onProposalApprove,
onProposalReject,
}: ReflectionLogProps) {
const [engine] = useState(() => new ReflectionEngine());
const [history, setHistory] = useState<ReflectionResult[]>([]);
const [pendingProposals, setPendingProposals] = useState<IdentityChangeProposal[]>([]);
const [expandedId, setExpandedId] = useState<string | null>(null);
const [isReflecting, setIsReflecting] = useState(false);
const [config, setConfig] = useState<ReflectionConfig>(DEFAULT_REFLECTION_CONFIG);
const [showConfig, setShowConfig] = useState(false);
const [config, setConfig] = useState<ReflectionConfig>({
trigger_after_conversations: 5,
allow_soul_modification: true,
require_approval: true,
});
// Load history and pending proposals
useEffect(() => {
const loadedHistory = engine.getHistory();
setHistory([...loadedHistory].reverse()); // Most recent first
const loadData = async () => {
try {
const loadedHistory = await intelligenceClient.reflection.getHistory();
setHistory([...loadedHistory].reverse()); // Most recent first
const identityManager = getAgentIdentityManager();
const proposals = identityManager.getPendingProposals(agentId);
setPendingProposals(proposals);
}, [engine, agentId]);
const proposals = await intelligenceClient.identity.getPendingProposals(agentId);
setPendingProposals(proposals);
} catch (error) {
console.error('[ReflectionLog] Failed to load data:', error);
}
};
loadData();
}, [agentId]);
const handleReflect = useCallback(async () => {
setIsReflecting(true);
try {
const result = await engine.reflect(agentId);
const result = await intelligenceClient.reflection.reflect(agentId, []);
setHistory((prev) => [result, ...prev]);
// Update pending proposals
if (result.identityProposals.length > 0) {
setPendingProposals((prev) => [...prev, ...result.identityProposals]);
if (result.identity_proposals.length > 0) {
setPendingProposals((prev) => [...prev, ...result.identity_proposals]);
}
} catch (error) {
console.error('[ReflectionLog] Reflection failed:', error);
} finally {
setIsReflecting(false);
}
}, [engine, agentId]);
}, [agentId]);
const handleApproveProposal = useCallback(
(proposal: IdentityChangeProposal) => {
const identityManager = getAgentIdentityManager();
identityManager.approveProposal(proposal.id);
setPendingProposals((prev) => prev.filter((p) => p.id !== proposal.id));
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(
(proposal: IdentityChangeProposal) => {
const identityManager = getAgentIdentityManager();
identityManager.rejectProposal(proposal.id);
setPendingProposals((prev) => prev.filter((p) => p.id !== proposal.id));
async (proposal: IdentityChangeProposal) => {
await intelligenceClient.identity.rejectProposal(proposal.id);
setPendingProposals((prev: IdentityChangeProposal[]) => prev.filter((p: IdentityChangeProposal) => p.id !== proposal.id));
onProposalReject?.(proposal);
},
[onProposalReject]
@@ -438,9 +444,9 @@ export function ReflectionLog({
const stats = useMemo(() => {
const totalReflections = history.length;
const totalPatterns = history.reduce((sum, r) => sum + r.patterns.length, 0);
const totalImprovements = history.reduce((sum, r) => sum + r.improvements.length, 0);
const totalIdentityChanges = history.reduce((sum, r) => sum + r.identityProposals.length, 0);
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]);
@@ -507,9 +513,9 @@ export function ReflectionLog({
type="number"
min="1"
max="20"
value={config.triggerAfterConversations}
value={config.trigger_after_conversations || 5}
onChange={(e) =>
setConfig((prev) => ({ ...prev, triggerAfterConversations: parseInt(e.target.value) || 5 }))
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"
/>
@@ -517,13 +523,13 @@ export function ReflectionLog({
<div className="flex items-center justify-between">
<span className="text-sm text-gray-700 dark:text-gray-300"> SOUL.md</span>
<button
onClick={() => setConfig((prev) => ({ ...prev, allowSoulModification: !prev.allowSoulModification }))}
onClick={() => setConfig((prev) => ({ ...prev, allow_soul_modification: !prev.allow_soul_modification }))}
className={`relative w-9 h-5 rounded-full transition-colors ${
config.allowSoulModification ? 'bg-purple-500' : 'bg-gray-300 dark:bg-gray-600'
config.allow_soul_modification ? 'bg-purple-500' : 'bg-gray-300 dark:bg-gray-600'
}`}
>
<motion.div
animate={{ x: config.allowSoulModification ? 18 : 0 }}
animate={{ x: config.allow_soul_modification ? 18 : 0 }}
className="absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow"
/>
</button>
@@ -531,13 +537,13 @@ export function ReflectionLog({
<div className="flex items-center justify-between">
<span className="text-sm text-gray-700 dark:text-gray-300"></span>
<button
onClick={() => setConfig((prev) => ({ ...prev, requireApproval: !prev.requireApproval }))}
onClick={() => setConfig((prev) => ({ ...prev, require_approval: !prev.require_approval }))}
className={`relative w-9 h-5 rounded-full transition-colors ${
config.requireApproval ? 'bg-purple-500' : 'bg-gray-300 dark:bg-gray-600'
config.require_approval ? 'bg-purple-500' : 'bg-gray-300 dark:bg-gray-600'
}`}
>
<motion.div
animate={{ x: config.requireApproval ? 18 : 0 }}
animate={{ x: config.require_approval ? 18 : 0 }}
className="absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow"
/>
</button>