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
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:
@@ -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 需要关注上下文。建议在回复时考虑对话的背景和历史。',
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user