fix(安全): 修复HTML导出中的XSS漏洞并清理调试日志
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

refactor(日志): 替换console.log为tracing日志系统
style(代码): 移除未使用的代码和依赖项

feat(测试): 添加端到端测试文档和CI工作流
docs(变更日志): 更新CHANGELOG.md记录0.1.0版本变更

perf(构建): 更新依赖版本并优化CI流程
This commit is contained in:
iven
2026-03-26 19:49:03 +08:00
parent b8d565a9eb
commit 978dc5cdd8
79 changed files with 3953 additions and 5724 deletions

View File

@@ -1,425 +0,0 @@
/**
* ActiveLearningStore - 主动学习状态管理
*
* 猡久学习事件和学习模式,学习建议的状态。
*/
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import {
type LearningEvent,
type LearningPattern,
type LearningSuggestion,
type LearningEventType,
type LearningConfig,
} from '../types/active-learning';
// === Types ===
interface ActiveLearningState {
events: LearningEvent[];
patterns: LearningPattern[];
suggestions: LearningSuggestion[];
config: LearningConfig;
isLoading: boolean;
error: string | null;
}
interface ActiveLearningActions {
recordEvent: (event: Omit<LearningEvent, 'id' | 'timestamp' | 'acknowledged'>) => Promise<LearningEvent>;
recordFeedback: (agentId: string, messageId: string, feedback: string, context?: string) => Promise<LearningEvent | null>;
acknowledgeEvent: (eventId: string) => void;
getPatterns: (agentId: string) => LearningPattern[];
getSuggestions: (agentId: string) => LearningSuggestion[];
applySuggestion: (suggestionId: string) => void;
dismissSuggestion: (suggestionId: string) => void;
getStats: (agentId: string) => ActiveLearningStats;
setConfig: (config: Partial<LearningConfig>) => void;
clearEvents: (agentId: string) => void;
exportLearningData: (agentId: string) => Promise<string>;
importLearningData: (agentId: string, data: string) => Promise<void>;
}
interface ActiveLearningStats {
totalEvents: number;
eventsByType: Record<LearningEventType, number>;
totalPatterns: number;
avgConfidence: number;
}
export type ActiveLearningStore = ActiveLearningState & ActiveLearningActions;
const STORAGE_KEY = 'zclaw-active-learning';
const MAX_EVENTS = 1000;
// === Helper Functions ===
function generateEventId(): string {
return `le-${Date.now()}-${Math.random().toString(36).slice(2)}`;
}
function analyzeSentiment(text: string): 'positive' | 'negative' | 'neutral' {
const positive = ['好的', '很棒', '谢谢', '完美', 'excellent', '喜欢', '爱了', 'good', 'great', 'nice', '满意'];
const negative = ['不好', '差', '糟糕', '错误', 'wrong', 'bad', '不喜欢', '讨厌', '问题', '失败', 'fail', 'error'];
const lowerText = text.toLowerCase();
if (positive.some(w => lowerText.includes(w))) return 'positive';
if (negative.some(w => lowerText.includes(w))) return 'negative';
return 'neutral';
}
function analyzeEventType(text: string): LearningEventType {
const lowerText = text.toLowerCase();
if (lowerText.includes('纠正') || lowerText.includes('不对') || lowerText.includes('修改')) {
return 'correction';
}
if (lowerText.includes('喜欢') || lowerText.includes('偏好') || lowerText.includes('风格')) {
return 'preference';
}
if (lowerText.includes('场景') || lowerText.includes('上下文') || lowerText.includes('情况')) {
return 'context';
}
if (lowerText.includes('总是') || lowerText.includes('经常') || lowerText.includes('习惯')) {
return 'behavior';
}
return 'feedback';
}
function inferPreference(feedback: string, sentiment: string): string {
if (sentiment === 'positive') {
if (feedback.includes('简洁')) return '用户偏好简洁的回复';
if (feedback.includes('详细')) return '用户偏好详细的回复';
if (feedback.includes('快速')) return '用户偏好快速响应';
return '用户对当前回复风格满意';
}
if (sentiment === 'negative') {
if (feedback.includes('太长')) return '用户偏好更短的回复';
if (feedback.includes('太短')) return '用户偏好更详细的回复';
if (feedback.includes('不准确')) return '用户偏好更准确的信息';
return '用户对当前回复风格不满意';
}
return '用户反馈中性';
}
// === Store ===
export const useActiveLearningStore = create<ActiveLearningStore>()(
persist(
(set, get) => ({
events: [],
patterns: [],
suggestions: [],
config: {
enabled: true,
minConfidence: 0.5,
maxEvents: MAX_EVENTS,
suggestionCooldown: 2,
},
isLoading: false,
error: null,
recordEvent: async (event) => {
const { events, config } = get();
if (!config.enabled) throw new Error('Learning is disabled');
// 检查重复事件
const existing = events.find(e =>
e.agentId === event.agentId &&
e.messageId === event.messageId &&
e.type === event.type
);
if (existing) {
// 更新现有事件
const updated = events.map(e =>
e.id === existing.id
? {
...e,
observation: e.observation + ' | ' + event.observation,
confidence: (e.confidence + event.confidence) / 2,
appliedCount: e.appliedCount + 1,
}
: e
);
set({ events: updated });
return existing;
}
// 创建新事件
const newEvent: LearningEvent = {
...event,
id: generateEventId(),
timestamp: Date.now(),
acknowledged: false,
appliedCount: 0,
};
// 提取模式
const newPatterns = extractPatterns(newEvent, get().patterns);
const newSuggestions = generateSuggestions(newEvent, newPatterns);
// 保持事件数量限制
const updatedEvents = [newEvent, ...events].slice(0, config.maxEvents);
set({
events: updatedEvents,
patterns: [...get().patterns, ...newPatterns],
suggestions: [...get().suggestions, ...newSuggestions],
});
return newEvent;
},
recordFeedback: async (agentId, messageId, feedback, context) => {
const { config } = get();
if (!config.enabled) return null;
const sentiment = analyzeSentiment(feedback);
const type = analyzeEventType(feedback);
return get().recordEvent({
type,
agentId,
messageId,
trigger: context || 'User feedback',
observation: feedback,
context,
inferredPreference: inferPreference(feedback, sentiment),
confidence: sentiment === 'positive' ? 0.8 : sentiment === 'negative' ? 0.5 : 0.3,
appliedCount: 0,
});
},
acknowledgeEvent: (eventId) => {
const { events } = get();
set({
events: events.map(e =>
e.id === eventId ? { ...e, acknowledged: true } : e
),
});
},
getPatterns: (agentId) => {
return get().patterns.filter(p => p.agentId === agentId);
},
getSuggestions: (agentId) => {
const now = Date.now();
return get().suggestions.filter(s =>
s.agentId === agentId &&
!s.dismissed &&
(!s.expiresAt || s.expiresAt.getTime() > now)
);
},
applySuggestion: (suggestionId) => {
const { suggestions, patterns } = get();
const suggestion = suggestions.find(s => s.id === suggestionId);
if (suggestion) {
// 更新模式置信度
const updatedPatterns = patterns.map(p =>
p.pattern === suggestion.pattern
? { ...p, confidence: Math.min(1, p.confidence + 0.1) }
: p
);
set({
suggestions: suggestions.map(s =>
s.id === suggestionId ? { ...s, dismissed: false } : s
),
patterns: updatedPatterns,
});
}
},
dismissSuggestion: (suggestionId) => {
const { suggestions } = get();
set({
suggestions: suggestions.map(s =>
s.id === suggestionId ? { ...s, dismissed: true } : s
),
});
},
getStats: (agentId) => {
const { events, patterns } = get();
const agentEvents = events.filter(e => e.agentId === agentId);
const agentPatterns = patterns.filter(p => p.agentId === agentId);
const eventsByType: Record<LearningEventType, number> = {
preference: 0,
correction: 0,
context: 0,
feedback: 0,
behavior: 0,
implicit: 0,
};
for (const event of agentEvents) {
eventsByType[event.type]++;
}
return {
totalEvents: agentEvents.length,
eventsByType,
totalPatterns: agentPatterns.length,
avgConfidence: agentPatterns.length > 0
? agentPatterns.reduce((sum, p) => sum + p.confidence, 0) / agentPatterns.length
: 0,
};
},
setConfig: (config) => {
set(state => ({
config: { ...state.config, ...config },
}));
},
clearEvents: (agentId) => {
const { events, patterns, suggestions } = get();
set({
events: events.filter(e => e.agentId !== agentId),
patterns: patterns.filter(p => p.agentId !== agentId),
suggestions: suggestions.filter(s => s.agentId !== agentId),
});
},
exportLearningData: async (agentId) => {
const { events, patterns, config } = get();
const data = {
events: events.filter(e => e.agentId === agentId),
patterns: patterns.filter(p => p.agentId === agentId),
config,
exportedAt: new Date().toISOString(),
};
return JSON.stringify(data, null, 2);
},
importLearningData: async (agentId, data) => {
try {
const parsed = JSON.parse(data);
const { events, patterns } = get();
// 合并导入的数据
const mergedEvents = [
...events,
...parsed.events.map((e: LearningEvent) => ({
...e,
id: generateEventId(),
agentId,
})),
].slice(0, MAX_EVENTS);
const mergedPatterns = [
...patterns,
...parsed.patterns.map((p: LearningPattern) => ({
...p,
agentId,
})),
];
set({
events: mergedEvents,
patterns: mergedPatterns,
});
} catch (err) {
throw new Error(`Failed to import learning data: ${err}`);
}
},
}),
{
name: STORAGE_KEY,
}
)
);
// === Pattern Extraction ===
function extractPatterns(
event: LearningEvent,
existingPatterns: LearningPattern[]
): LearningPattern[] {
const patterns: LearningPattern[] = [];
// 偏好模式
if (event.observation.includes('谢谢') || event.observation.includes('好的')) {
patterns.push({
type: 'preference',
pattern: 'positive_response_preference',
description: '用户偏好正面回复风格',
examples: [event.observation],
confidence: 0.8,
agentId: event.agentId,
});
}
// 精确性模式
if (event.type === 'correction') {
patterns.push({
type: 'rule',
pattern: 'precision_preference',
description: '用户对精确性有更高要求',
examples: [event.observation],
confidence: 0.9,
agentId: event.agentId,
});
}
// 上下文模式
if (event.context) {
patterns.push({
type: 'context',
pattern: 'context_aware',
description: 'Agent 需要关注上下文',
examples: [event.context],
confidence: 0.6,
agentId: event.agentId,
});
}
return patterns.filter(p =>
!existingPatterns.some(ep => ep.pattern === p.pattern && ep.agentId === p.agentId)
);
}
// === Suggestion Generation ===
function generateSuggestions(
event: LearningEvent,
patterns: LearningPattern[]
): LearningSuggestion[] {
const suggestions: LearningSuggestion[] = [];
const now = Date.now();
for (const pattern of patterns) {
const template = SUGGESTION_TEMPLATES[pattern.pattern];
if (template) {
suggestions.push({
id: `sug-${Date.now()}-${Math.random().toString(36).slice(2)}`,
agentId: event.agentId,
type: pattern.type,
pattern: pattern.pattern,
suggestion: template,
confidence: pattern.confidence,
createdAt: now,
expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000),
dismissed: false,
});
}
}
return suggestions;
}
const SUGGESTION_TEMPLATES: Record<string, string> = {
positive_response_preference:
'用户似乎偏好正面回复。建议在回复时保持积极和确认的语气。',
precision_preference:
'用户对精确性有更高要求。建议在提供信息时更加详细和准确。',
context_aware:
'Agent 需要关注上下文。建议在回复时考虑对话的背景和历史。',
};

View File

@@ -8,6 +8,9 @@ import { getAgentSwarm } from '../lib/agent-swarm';
import { getSkillDiscovery } from '../lib/skill-discovery';
import { useOfflineStore, isOffline } from './offlineStore';
import { useConnectionStore } from './connectionStore';
import { createLogger } from '../lib/logger';
const log = createLogger('ChatStore');
export interface MessageFile {
name: string;
@@ -307,7 +310,7 @@ export const useChatStore = create<ChatState>()(
if (isOffline()) {
const { queueMessage } = useOfflineStore.getState();
const queueId = queueMessage(content, effectiveAgentId, effectiveSessionKey);
console.log(`[Chat] Offline - message queued: ${queueId}`);
log.debug(`Offline - message queued: ${queueId}`);
// Show a system message about offline queueing
const systemMsg: Message = {
@@ -334,7 +337,7 @@ export const useChatStore = create<ChatState>()(
const messages = get().messages.map(m => ({ role: m.role, content: m.content }));
const check = await intelligenceClient.compactor.checkThreshold(messages);
if (check.should_compact) {
console.log(`[Chat] Context compaction triggered (${check.urgency}): ${check.current_tokens} tokens`);
log.debug(`Context compaction triggered (${check.urgency}): ${check.current_tokens} tokens`);
const result = await intelligenceClient.compactor.compact(
get().messages.map(m => ({
role: m.role,
@@ -355,7 +358,7 @@ export const useChatStore = create<ChatState>()(
set({ messages: compactedMsgs });
}
} catch (err) {
console.warn('[Chat] Context compaction check failed:', err);
log.warn('Context compaction check failed:', err);
}
// Build memory-enhanced content
@@ -375,7 +378,7 @@ export const useChatStore = create<ChatState>()(
enhancedContent = `<context>\n${systemPrompt}\n</context>\n\n${content}`;
}
} catch (err) {
console.warn('[Chat] Memory enhancement failed, proceeding without:', err);
log.warn('Memory enhancement failed, proceeding without:', err);
}
// Add user message (original content for display)
@@ -477,16 +480,16 @@ export const useChatStore = create<ChatState>()(
.filter(m => m.role === 'user' || m.role === 'assistant')
.map(m => ({ role: m.role, content: m.content }));
getMemoryExtractor().extractFromConversation(msgs, agentId, get().currentConversationId ?? undefined).catch(err => {
console.warn('[Chat] Memory extraction failed:', err);
log.warn('Memory extraction failed:', err);
});
// Track conversation for reflection trigger
intelligenceClient.reflection.recordConversation().catch(err => {
console.warn('[Chat] Recording conversation failed:', err);
log.warn('Recording conversation failed:', err);
});
intelligenceClient.reflection.shouldReflect().then(shouldReflect => {
if (shouldReflect) {
intelligenceClient.reflection.reflect(agentId, []).catch(err => {
console.warn('[Chat] Reflection failed:', err);
log.warn('Reflection failed:', err);
});
}
});
@@ -570,7 +573,7 @@ export const useChatStore = create<ChatState>()(
return result.task.id;
} catch (err) {
console.warn('[Chat] Swarm dispatch failed:', err);
log.warn('Swarm dispatch failed:', err);
return null;
}
},

View File

@@ -27,6 +27,9 @@ import {
type HealthStatus,
} from '../lib/health-check';
import { useConfigStore } from './configStore';
import { createLogger } from '../lib/logger';
const log = createLogger('ConnectionStore');
// === Mode Selection ===
// IMPORTANT: Check isTauriRuntime() at RUNTIME (inside functions), not at module load time.
@@ -57,7 +60,7 @@ function loadCustomModels(): CustomModel[] {
return JSON.parse(stored);
}
} catch (err) {
console.error('[connectionStore] Failed to parse models:', err);
log.error('Failed to parse models:', err);
}
return [];
}
@@ -88,7 +91,7 @@ export function getDefaultModelConfig(): { provider: string; model: string; apiK
}
}
} catch (err) {
console.warn('[connectionStore] Failed to read chatStore:', err);
log.warn('Failed to read chatStore:', err);
}
}
@@ -213,10 +216,10 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
// === Internal Kernel Mode (Tauri) ===
// Check at RUNTIME, not at module load time, to ensure __TAURI_INTERNALS__ is available
const useInternalKernel = isTauriRuntime();
console.log('[ConnectionStore] isTauriRuntime():', useInternalKernel);
log.debug('isTauriRuntime():', useInternalKernel);
if (useInternalKernel) {
console.log('[ConnectionStore] Using internal ZCLAW Kernel (no external process needed)');
log.debug('Using internal ZCLAW Kernel (no external process needed)');
const kernelClient = getKernelClient();
// Get model config from custom models settings
@@ -230,7 +233,7 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
throw new Error(`模型 ${modelConfig.model} 未配置 API Key请在"模型与 API"设置页面配置`);
}
console.log('[ConnectionStore] Model config:', {
log.debug('Model config:', {
provider: modelConfig.provider,
model: modelConfig.model,
hasApiKey: !!modelConfig.apiKey,
@@ -269,9 +272,9 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
await kernelClient.connect();
// Set version
set({ gatewayVersion: '0.2.0-internal' });
set({ gatewayVersion: '0.1.0-internal' });
console.log('[ConnectionStore] Connected to internal ZCLAW Kernel');
log.debug('Connected to internal ZCLAW Kernel');
return;
}
@@ -312,7 +315,7 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
// Resolve effective token
const effectiveToken = token || useConfigStore.getState().quickConfig?.gatewayToken || getStoredGatewayToken();
console.log('[ConnectionStore] Connecting with token:', effectiveToken ? '[REDACTED]' : '(empty)');
log.debug('Connecting with token:', effectiveToken ? '[REDACTED]' : '(empty)');
const candidateUrls = await resolveCandidates();
let lastError: unknown = null;
@@ -351,7 +354,7 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
set({ gatewayVersion: health?.version });
} catch { /* health may not return version */ }
console.log('[ConnectionStore] Connected to:', connectedUrl);
log.debug('Connected to:', connectedUrl);
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
set({ error: errorMessage });

View File

@@ -35,8 +35,6 @@ export type { SessionStore, SessionStateSlice, SessionActionsSlice, Session, Ses
export { useMemoryGraphStore } from './memoryGraphStore';
export type { MemoryGraphStore, GraphNode, GraphEdge, GraphFilter, GraphLayout } from './memoryGraphStore';
export { useActiveLearningStore } from './activeLearningStore';
export type { ActiveLearningStore } from './activeLearningStore';
// === Browser Hand Store ===
export { useBrowserHandStore } from './browserHandStore';

View File

@@ -1,161 +0,0 @@
/**
* Mesh Store - State management for Adaptive Intelligence Mesh
*
* Manages workflow recommendations and behavior patterns.
*/
import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core';
import type {
WorkflowRecommendation,
BehaviorPattern,
MeshConfig,
MeshAnalysisResult,
PatternContext,
ActivityType,
} from '../lib/intelligence-client';
// === Types ===
export interface MeshState {
// State
recommendations: WorkflowRecommendation[];
patterns: BehaviorPattern[];
config: MeshConfig;
isLoading: boolean;
error: string | null;
lastAnalysis: string | null;
// Actions
analyze: () => Promise<void>;
acceptRecommendation: (recommendationId: string) => Promise<void>;
dismissRecommendation: (recommendationId: string) => Promise<void>;
recordActivity: (activity: ActivityType, context: PatternContext) => Promise<void>;
getPatterns: () => Promise<void>;
updateConfig: (config: Partial<MeshConfig>) => Promise<void>;
decayPatterns: () => Promise<void>;
clearError: () => void;
}
// === Store ===
export const useMeshStore = create<MeshState>((set, get) => ({
// Initial state
recommendations: [],
patterns: [],
config: {
enabled: true,
min_confidence: 0.6,
max_recommendations: 5,
analysis_window_hours: 24,
},
isLoading: false,
error: null,
lastAnalysis: null,
// Actions
analyze: async () => {
set({ isLoading: true, error: null });
try {
const agentId = localStorage.getItem('currentAgentId') || 'default';
const result = await invoke<MeshAnalysisResult>('mesh_analyze', { agentId });
set({
recommendations: result.recommendations,
patterns: [], // Will be populated by getPatterns
lastAnalysis: result.timestamp,
isLoading: false,
});
// Also fetch patterns
await get().getPatterns();
} catch (err) {
set({
error: err instanceof Error ? err.message : String(err),
isLoading: false,
});
}
},
acceptRecommendation: async (recommendationId: string) => {
try {
const agentId = localStorage.getItem('currentAgentId') || 'default';
await invoke('mesh_accept_recommendation', { agentId, recommendationId });
// Remove from local state
set((state) => ({
recommendations: state.recommendations.filter((r) => r.id !== recommendationId),
}));
} catch (err) {
set({ error: err instanceof Error ? err.message : String(err) });
}
},
dismissRecommendation: async (recommendationId: string) => {
try {
const agentId = localStorage.getItem('currentAgentId') || 'default';
await invoke('mesh_dismiss_recommendation', { agentId, recommendationId });
// Remove from local state
set((state) => ({
recommendations: state.recommendations.filter((r) => r.id !== recommendationId),
}));
} catch (err) {
set({ error: err instanceof Error ? err.message : String(err) });
}
},
recordActivity: async (activity: ActivityType, context: PatternContext) => {
try {
const agentId = localStorage.getItem('currentAgentId') || 'default';
await invoke('mesh_record_activity', { agentId, activityType: activity, context });
} catch (err) {
console.error('Failed to record activity:', err);
}
},
getPatterns: async () => {
try {
const agentId = localStorage.getItem('currentAgentId') || 'default';
const patterns = await invoke<BehaviorPattern[]>('mesh_get_patterns', { agentId });
set({ patterns });
} catch (err) {
console.error('Failed to get patterns:', err);
}
},
updateConfig: async (config: Partial<MeshConfig>) => {
try {
const agentId = localStorage.getItem('currentAgentId') || 'default';
const newConfig = { ...get().config, ...config };
await invoke('mesh_update_config', { agentId, config: newConfig });
set({ config: newConfig });
} catch (err) {
set({ error: err instanceof Error ? err.message : String(err) });
}
},
decayPatterns: async () => {
try {
const agentId = localStorage.getItem('currentAgentId') || 'default';
await invoke('mesh_decay_patterns', { agentId });
// Refresh patterns after decay
await get().getPatterns();
} catch (err) {
console.error('Failed to decay patterns:', err);
}
},
clearError: () => set({ error: null }),
}));
// === Types for intelligence-client ===
export type {
WorkflowRecommendation,
BehaviorPattern,
MeshConfig,
MeshAnalysisResult,
PatternContext,
ActivityType,
};

View File

@@ -1,195 +0,0 @@
/**
* Persona Evolution Store
*
* Manages persona evolution state and proposals.
*/
import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core';
import type {
EvolutionResult,
EvolutionProposal,
PersonaEvolverConfig,
PersonaEvolverState,
MemoryEntryForAnalysis,
} from '../lib/intelligence-client';
export interface PersonaEvolutionStore {
// State
currentAgentId: string;
proposals: EvolutionProposal[];
history: EvolutionResult[];
isLoading: boolean;
error: string | null;
config: PersonaEvolverConfig | null;
state: PersonaEvolverState | null;
showProposalsPanel: boolean;
// Actions
setCurrentAgentId: (agentId: string) => void;
setShowProposalsPanel: (show: boolean) => void;
// Evolution Actions
runEvolution: (memories: MemoryEntryForAnalysis[]) => Promise<EvolutionResult | null>;
loadEvolutionHistory: (limit?: number) => Promise<void>;
loadEvolverState: () => Promise<void>;
loadEvolverConfig: () => Promise<void>;
updateConfig: (config: Partial<PersonaEvolverConfig>) => Promise<void>;
// Proposal Actions
getPendingProposals: () => EvolutionProposal[];
applyProposal: (proposal: EvolutionProposal) => Promise<boolean>;
dismissProposal: (proposalId: string) => void;
clearProposals: () => void;
}
export const usePersonaEvolutionStore = create<PersonaEvolutionStore>((set, get) => ({
// Initial State
currentAgentId: '',
proposals: [],
history: [],
isLoading: false,
error: null,
config: null,
state: null,
showProposalsPanel: false,
// Setters
setCurrentAgentId: (agentId: string) => set({ currentAgentId: agentId }),
setShowProposalsPanel: (show: boolean) => set({ showProposalsPanel: show }),
// Run evolution cycle for current agent
runEvolution: async (memories: MemoryEntryForAnalysis[]) => {
const { currentAgentId } = get();
if (!currentAgentId) {
set({ error: 'No agent selected' });
return null;
}
set({ isLoading: true, error: null });
try {
const result = await invoke<EvolutionResult>('persona_evolve', {
agentId: currentAgentId,
memories,
});
// Update state with results
set((state) => ({
history: [result, ...state.history].slice(0, 20),
proposals: [...result.proposals, ...state.proposals],
isLoading: false,
showProposalsPanel: result.proposals.length > 0,
}));
return result;
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
set({ error: errorMsg, isLoading: false });
return null;
}
},
// Load evolution history
loadEvolutionHistory: async (limit = 10) => {
set({ isLoading: true, error: null });
try {
const history = await invoke<EvolutionResult[]>('persona_evolution_history', {
limit,
});
set({ history, isLoading: false });
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
set({ error: errorMsg, isLoading: false });
}
},
// Load evolver state
loadEvolverState: async () => {
try {
const state = await invoke<PersonaEvolverState>('persona_evolver_state');
set({ state });
} catch (err) {
console.error('[PersonaStore] Failed to load evolver state:', err);
}
},
// Load evolver config
loadEvolverConfig: async () => {
try {
const config = await invoke<PersonaEvolverConfig>('persona_evolver_config');
set({ config });
} catch (err) {
console.error('[PersonaStore] Failed to load evolver config:', err);
}
},
// Update evolver config
updateConfig: async (newConfig: Partial<PersonaEvolverConfig>) => {
const { config } = get();
if (!config) return;
const updatedConfig = { ...config, ...newConfig };
try {
await invoke('persona_evolver_update_config', { config: updatedConfig });
set({ config: updatedConfig });
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
set({ error: errorMsg });
}
},
// Get pending proposals sorted by confidence
getPendingProposals: () => {
const { proposals } = get();
return proposals
.filter((p) => p.status === 'pending')
.sort((a, b) => b.confidence - a.confidence);
},
// Apply a proposal (approve)
applyProposal: async (proposal: EvolutionProposal) => {
set({ isLoading: true, error: null });
try {
await invoke('persona_apply_proposal', { proposal });
// Remove from pending list
set((state) => ({
proposals: state.proposals.filter((p) => p.id !== proposal.id),
isLoading: false,
}));
return true;
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
set({ error: errorMsg, isLoading: false });
return false;
}
},
// Dismiss a proposal (reject)
dismissProposal: (proposalId: string) => {
set((state) => ({
proposals: state.proposals.filter((p) => p.id !== proposalId),
}));
},
// Clear all proposals
clearProposals: () => set({ proposals: [] }),
}));
// Export convenience hooks
export const usePendingProposals = () =>
usePersonaEvolutionStore((state) => state.getPendingProposals());
export const useEvolutionHistory = () =>
usePersonaEvolutionStore((state) => state.history);
export const useEvolverConfig = () =>
usePersonaEvolutionStore((state) => state.config);
export const useEvolverState = () =>
usePersonaEvolutionStore((state) => state.state);

View File

@@ -1,360 +0,0 @@
/**
* * skillMarketStore.ts - 技能市场状态管理
*
* * 猛攻状态管理技能浏览、搜索、安装/卸载等功能
*/
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { Skill, SkillReview, SkillMarketState } from '../types/skill-market';
// === 存储键 ===
const STORAGE_KEY = 'zclaw-skill-market';
const INSTALLED_KEY = 'zclaw-installed-skills';
// === 默认状态 ===
const initialState: SkillMarketState = {
skills: [],
installedSkills: [],
searchResults: [],
selectedSkill: null,
searchQuery: '',
categoryFilter: 'all',
isLoading: false,
error: null,
};
// === Store 定义 ===
interface SkillMarketActions {
// 技能加载
loadSkills: () => Promise<void>;
// 技能搜索
searchSkills: (query: string) => void;
// 分类过滤
filterByCategory: (category: string) => void;
// 选择技能
selectSkill: (skill: Skill | null) => void;
// 安装技能
installSkill: (skillId: string) => Promise<boolean>;
// 卸载技能
uninstallSkill: (skillId: string) => Promise<boolean>;
// 获取技能详情
getSkillDetails: (skillId: string) => Promise<Skill | null>;
// 加载评论
loadReviews: (skillId: string) => Promise<SkillReview[]>;
// 添加评论
addReview: (skillId: string, review: Omit<SkillReview, 'id' | 'skillId' | 'createdAt'>) => Promise<void>;
// 刷新技能列表
refreshSkills: () => Promise<void>;
// 清除错误
clearError: () => void;
// 重置状态
reset: () => void;
}
// === Store 创建 ===
export const useSkillMarketStore = create<SkillMarketState & SkillMarketActions>()(
persist({
key: STORAGE_KEY,
storage: localStorage,
partialize: (state) => ({
installedSkills: state.installedSkills,
categoryFilter: state.categoryFilter,
}),
}),
initialState,
{
// === 技能加载 ===
loadSkills: async () => {
set({ isLoading: true, error: null });
try {
// 扫描 skills 目录获取可用技能
const skills = await scanSkillsDirectory();
// 从 localStorage 恢复安装状态
const stored = localStorage.getItem(INSTALLED_KEY);
const installedSkills: string[] = stored ? JSON.parse(stored) : [];
// 更新技能的安装状态
const updatedSkills = skills.map(skill => ({
...skill,
installed: installedSkills.includes(skill.id),
})));
set({
skills: updatedSkills,
installedSkills,
isLoading: false,
});
} catch (err) {
set({
isLoading: false,
error: err instanceof Error ? err.message : '加载技能失败',
});
}
},
// === 技能搜索 ===
searchSkills: (query: string) => {
const { skills } = get();
set({ searchQuery: query });
if (!query.trim()) {
set({ searchResults: [] });
return;
}
const queryLower = query.toLowerCase();
const results = skills.filter(skill => {
return (
skill.name.toLowerCase().includes(queryLower) ||
skill.description.toLowerCase().includes(queryLower) ||
skill.triggers.some(t => t.toLowerCase().includes(queryLower)) ||
skill.capabilities.some(c => c.toLowerCase().includes(queryLower)) ||
skill.tags?.some(t => t.toLowerCase().includes(queryLower))
);
});
set({ searchResults: results });
},
// === 分类过滤 ===
filterByCategory: (category: string) => {
set({ categoryFilter: category });
},
// === 选择技能 ===
selectSkill: (skill: Skill | null) => {
set({ selectedSkill: skill });
},
// === 安装技能 ===
installSkill: async (skillId: string) => {
const { skills, installedSkills } = get();
const skill = skills.find(s => s.id === skillId);
if (!skill) return false;
try {
// 更新安装状态
const newInstalledSkills = [...installedSkills, skillId];
const updatedSkills = skills.map(s => ({
...s,
installed: s.id === skillId ? true : s.installed,
installedAt: s.id === skillId ? new Date().toISOString() : s.installedAt,
}));
// 持久化安装列表
localStorage.setItem(INSTALLED_KEY, JSON.stringify(newInstalledSkills));
set({
skills: updatedSkills,
installedSkills: newInstalledSkills,
});
return true;
} catch (err) {
set({
error: err instanceof Error ? err.message : '安装技能失败',
});
return false;
}
},
// === 卸载技能 ===
uninstallSkill: async (skillId: string) => {
const { skills, installedSkills } = get();
try {
// 更新安装状态
const newInstalledSkills = installedSkills.filter(id => id !== skillId);
const updatedSkills = skills.map(s => ({
...s,
installed: s.id === skillId ? false : s.installed,
installedAt: s.id === skillId ? undefined : s.installedAt,
}));
// 持久化安装列表
localStorage.setItem(INSTALLED_KEY, JSON.stringify(newInstalledSkills));
set({
skills: updatedSkills,
installedSkills: newInstalledSkills,
});
return true;
} catch (err) {
set({
error: err instanceof Error ? err.message : '卸载技能失败',
});
return false;
}
},
// === 获取技能详情 ===
getSkillDetails: async (skillId: string) => {
const { skills } = get();
return skills.find(s => s.id === skillId) || null;
},
// === 加载评论 ===
loadReviews: async (skillId: string) => {
// MVP: 从 localStorage 模拟加载评论
const reviewsKey = `zclaw-skill-reviews-${skillId}`;
const stored = localStorage.getItem(reviewsKey);
const reviews: SkillReview[] = stored ? JSON.parse(stored) : [];
return reviews;
},
// === 添加评论 ===
addReview: async (skillId: string, review: Omit<SkillReview, 'id' | 'skillId' | 'createdAt'>) => {
const reviews = await get().loadReviews(skillId);
const newReview: SkillReview = {
...review,
id: `review-${Date.now()}`,
skillId,
createdAt: new Date().toISOString(),
};
const updatedReviews = [...reviews, newReview];
// 更新技能的评分和评论数
const { skills } = get();
const updatedSkills = skills.map(s => {
if (s.id === skillId) {
const totalRating = updatedReviews.reduce((sum, r) => sum + r.rating, 0);
const avgRating = totalRating / updatedReviews.length;
return {
...s,
rating: Math.round(avgRating * 10) / 10,
reviewCount: updatedReviews.length,
};
}
return s;
});
// 持久化评论
const reviewsKey = `zclaw-skill-reviews-${skillId}`;
localStorage.setItem(reviewsKey, JSON.stringify(updatedReviews));
set({ skills: updatedSkills });
},
// === 刷新技能列表 ===
refreshSkills: async () => {
// 清除缓存并重新加载
localStorage.removeItem(STORAGE_KEY);
await get().loadSkills();
},
// === 清除错误 ===
clearError: () => {
set({ error: null });
},
// === 重置状态 ===
reset: () => {
localStorage.removeItem(STORAGE_KEY);
localStorage.removeItem(INSTALLED_KEY);
set(initialState);
},
}
);
// === 辅助函数 ===
/**
* 扫描 skills 目录获取可用技能
* 从后端获取技能列表
*/
async function scanSkillsDirectory(): Promise<Skill[]> {
try {
// 动态导入 invoke 以避免循环依赖
const { invoke } = await import('@tauri-apps/api/core');
// 调用后端 skill_list 命令
interface BackendSkill {
id: string;
name: string;
description: string;
version: string;
capabilities: string[];
tags: string[];
mode: string;
enabled: boolean;
}
const backendSkills = await invoke<BackendSkill[]>('skill_list');
// 转换为前端 Skill 格式
const skills: Skill[] = backendSkills.map((s): Skill => ({
id: s.id,
name: s.name,
description: s.description,
triggers: s.tags, // 使用 tags 作为触发器
capabilities: s.capabilities,
toolDeps: [], // 后端暂不提供 toolDeps
category: 'discovered', // 后端发现的技能
installed: s.enabled,
tags: s.tags,
}));
return skills;
} catch (err) {
console.warn('[skillMarketStore] Failed to load skills from backend, using fallback:', err);
// 如果后端调用失败,返回空数组而不是模拟数据
return [];
}
}
tags: ['文件', '目录', '读写'],
},
{
id: 'security-engineer',
name: '安全工程师',
description: '安全工程师 - 负责安全审计、漏洞检测、合规检查',
triggers: ['安全审计', '漏洞检测', '安全检查', 'security', '渗透测试'],
capabilities: ['漏洞扫描', '合规检查', '安全加固', '威胁建模'],
toolDeps: ['read', 'grep', 'shell'],
category: 'security',
installed: false,
tags: ['安全', '审计', '漏洞'],
},
{
id: 'ai-engineer',
name: 'AI 工程师',
description: 'AI/ML 工程师 - 专注机器学习模型开发、LLM 集成和生产系统部署',
triggers: ['AI工程师', '机器学习', 'ML模型', 'LLM集成', '深度学习', '模型训练'],
capabilities: ['ML 框架', 'LLM 集成', 'RAG 系统', '向量数据库'],
toolDeps: ['bash', 'read', 'write', 'grep', 'glob'],
category: 'development',
installed: false,
tags: ['AI', 'ML', 'LLM'],
},
{
id: 'senior-developer',
name: '高级开发',
description: '高级开发工程师 - 端到端功能实现、复杂问题解决',
triggers: ['高级开发', 'senior developer', '端到端', '复杂功能', '架构实现'],
capabilities: ['端到端实现', '架构设计', '性能优化', '代码重构'],
toolDeps: ['bash', 'read', 'write', 'grep', 'glob'],
category: 'development',
installed: false,
tags: ['开发', '架构', '实现'],
},
{
id: 'frontend-developer',
name: '前端开发',
description: '前端开发专家 - 擅长 React/Vue/CSS/TypeScript',
triggers: ['前端开发', '页面开发', 'UI开发', 'React', 'Vue', 'CSS'],
capabilities: ['组件开发', '样式调整', '性能优化', '响应式设计'],
toolDeps: ['read', 'write', 'shell'],
category: 'development',
installed: false,
types: ['前端', 'UI', '组件'],
},
{
id: 'backend-architect',
name: '后端架构',
description: '后端架构设计、API设计、数据库建模',
triggers: ['后端架构', 'API设计', '数据库设计', '系统架构', '微服务'],
capabilities: ['架构设计', 'API规范', '数据库建模', '性能优化'],
toolDeps: ['read', 'write', 'shell'],
category: 'development',
installed: false,
tags: ['后端', '架构', 'API'],
},
{
id: 'devops-automator',
name: 'DevOps 自动化',
description: 'CI/CD、Docker、K8s、自动化部署',
triggers: ['DevOps', 'CI/CD', 'Docker', '部署', '自动化', 'K8s'],
capabilities: ['CI/CD配置', '容器化', '自动化部署', '监控告警'],
toolDeps: ['shell', 'read', 'write'],
category: 'ops',
installed: false,
tags: ['DevOps', 'Docker', 'CI/CD'],
},
{
id: 'senior-pm',
name: '高级PM',
description: '项目管理、需求分析、迭代规划',
triggers: ['项目管理', '需求分析', '迭代规划', '产品设计', 'PRD'],
capabilities: ['需求拆解', '迭代排期', '风险评估', '文档撰写'],
toolDeps: ['read', 'write'],
category: 'management',
installed: false,
tags: ['PM', '需求', '迭代'],
},
];
return skills;
}