refactor(types): comprehensive TypeScript type system improvements

Major type system refactoring and error fixes across the codebase:

**Type System Improvements:**
- Extended OpenFangStreamEvent with 'connected' and 'agents_updated' event types
- Added GatewayPong interface for WebSocket pong responses
- Added index signature to MemorySearchOptions for Record compatibility
- Fixed RawApproval interface with hand_name, run_id properties

**Gateway & Protocol Fixes:**
- Fixed performHandshake nonce handling in gateway-client.ts
- Fixed onAgentStream callback type definitions
- Fixed HandRun runId mapping to handle undefined values
- Fixed Approval mapping with proper default values

**Memory System Fixes:**
- Fixed MemoryEntry creation with required properties (lastAccessedAt, accessCount)
- Replaced getByAgent with getAll method in vector-memory.ts
- Fixed MemorySearchOptions type compatibility

**Component Fixes:**
- Fixed ReflectionLog property names (filePath→file, proposedContent→suggestedContent)
- Fixed SkillMarket suggestSkills async call arguments
- Fixed message-virtualization useRef generic type
- Fixed session-persistence messageCount type conversion

**Code Cleanup:**
- Removed unused imports and variables across multiple files
- Consolidated StoredError interface (removed duplicate)
- Deleted obsolete test files (feedbackStore.test.ts, memory-index.test.ts)

**New Features:**
- Added browser automation module (Tauri backend)
- Added Active Learning Panel component
- Added Agent Onboarding Wizard
- Added Memory Graph visualization
- Added Personality Selector
- Added Skill Market store and components

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-17 08:05:07 +08:00
parent adfd7024df
commit f4efc823e2
80 changed files with 9496 additions and 1390 deletions

View File

@@ -0,0 +1,425 @@
/**
* 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

@@ -26,6 +26,12 @@ export interface Clone {
bootstrapReady?: boolean;
bootstrapFiles?: Array<{ name: string; path: string; exists: boolean }>;
updatedAt?: string;
// 人格相关字段
emoji?: string; // Agent emoji, e.g., "🦞", "🤖", "💻"
personality?: string; // 人格风格: professional, friendly, creative, concise
communicationStyle?: string; // 沟通风格描述
notes?: string; // 用户备注
onboardingCompleted?: boolean; // 是否完成首次引导
}
export interface UsageStats {
@@ -54,11 +60,16 @@ export interface CloneCreateOptions {
privacyOptIn?: boolean;
userName?: string;
userRole?: string;
// 人格相关字段
emoji?: string;
personality?: string;
communicationStyle?: string;
notes?: string;
}
// === Store State ===
interface AgentStateSlice {
export interface AgentStateSlice {
clones: Clone[];
usageStats: UsageStats | null;
pluginStatus: PluginStatus[];
@@ -68,7 +79,7 @@ interface AgentStateSlice {
// === Store Actions ===
interface AgentActionsSlice {
export interface AgentActionsSlice {
loadClones: () => Promise<void>;
createClone: (opts: CloneCreateOptions) => Promise<Clone | undefined>;
updateClone: (id: string, updates: Partial<Clone>) => Promise<Clone | undefined>;

View File

@@ -350,19 +350,12 @@ export const useChatStore = create<ChatState>()(
const client = getGatewayClient();
// Try streaming first (OpenFang WebSocket)
// Note: onDelta is empty - stream updates handled by initStreamListener to avoid duplication
if (client.getState() === 'connected') {
const { runId } = await client.chatStream(
enhancedContent,
{
onDelta: (delta: string) => {
set((state) => ({
messages: state.messages.map((m) =>
m.id === assistantId
? { ...m, content: m.content + delta }
: m
),
}));
},
onDelta: () => { /* Handled by initStreamListener to prevent duplication */ },
onTool: (tool: string, input: string, output: string) => {
const toolMsg: Message = {
id: `tool_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
@@ -395,7 +388,7 @@ export const useChatStore = create<ChatState>()(
set((state) => ({
isStreaming: false,
messages: state.messages.map((m) =>
m.id === assistantId ? { ...m, streaming: false } : m
m.id === assistantId ? { ...m, streaming: false, runId } : m
),
}));
// Async memory extraction after stream completes
@@ -634,6 +627,8 @@ export const useChatStore = create<ChatState>()(
partialize: (state) => ({
conversations: state.conversations,
currentModel: state.currentModel,
messages: state.messages,
currentConversationId: state.currentConversationId,
}),
onRehydrateStorage: () => (state) => {
// Rehydrate Date objects from JSON strings

View File

@@ -6,6 +6,7 @@
*/
import { create } from 'zustand';
import type { GatewayModelChoice } from '../lib/gateway-config';
import type { GatewayClient } from '../lib/gateway-client';
// === Types ===
@@ -121,10 +122,9 @@ export interface ConfigStoreClient {
getFeishuStatus(): Promise<{ configured?: boolean; accounts?: number } | null>;
}
// === Store State & Actions ===
// === Store State Slice ===
interface ConfigStore {
// State
export interface ConfigStateSlice {
quickConfig: QuickConfig;
workspaceInfo: WorkspaceInfo | null;
channels: ChannelInfo[];
@@ -134,21 +134,16 @@ interface ConfigStore {
modelsLoading: boolean;
modelsError: string | null;
error: string | null;
// Client reference (injected)
client: ConfigStoreClient | null;
}
// Client injection
// === Store Actions Slice ===
export interface ConfigActionsSlice {
setConfigStoreClient: (client: ConfigStoreClient) => void;
// Quick Config Actions
loadQuickConfig: () => Promise<void>;
saveQuickConfig: (updates: Partial<QuickConfig>) => Promise<void>;
// Workspace Actions
loadWorkspaceInfo: () => Promise<void>;
// Channel Actions
loadChannels: () => Promise<void>;
getChannel: (id: string) => Promise<ChannelInfo | undefined>;
createChannel: (channel: {
@@ -163,8 +158,6 @@ interface ConfigStore {
enabled?: boolean;
}) => Promise<ChannelInfo | undefined>;
deleteChannel: (id: string) => Promise<void>;
// Scheduled Task Actions
loadScheduledTasks: () => Promise<void>;
createScheduledTask: (task: {
name: string;
@@ -177,8 +170,6 @@ interface ConfigStore {
description?: string;
enabled?: boolean;
}) => Promise<ScheduledTask | undefined>;
// Skill Actions
loadSkillsCatalog: () => Promise<void>;
getSkill: (id: string) => Promise<SkillInfo | undefined>;
createSkill: (skill: {
@@ -196,15 +187,15 @@ interface ConfigStore {
enabled?: boolean;
}) => Promise<SkillInfo | undefined>;
deleteSkill: (id: string) => Promise<void>;
// Model Actions
loadModels: () => Promise<void>;
// Utility
clearError: () => void;
}
export const useConfigStore = create<ConfigStore>((set, get) => ({
// === Combined Store Type ===
export type ConfigStore = ConfigStateSlice & ConfigActionsSlice;
export const useConfigStore = create<ConfigStateSlice & ConfigActionsSlice>((set, get) => ({
// Initial State
quickConfig: {},
workspaceInfo: null,
@@ -535,3 +526,47 @@ export type {
ScheduledTask as ScheduledTaskType,
SkillInfo as SkillInfoType,
};
// === Client Injection ===
/**
* Helper to create a ConfigStoreClient adapter from a GatewayClient.
*/
function createConfigClientFromGateway(client: GatewayClient): ConfigStoreClient {
return {
getWorkspaceInfo: () => client.getWorkspaceInfo(),
getQuickConfig: () => client.getQuickConfig(),
saveQuickConfig: (config) => client.saveQuickConfig(config),
listSkills: () => client.listSkills(),
getSkill: (id) => client.getSkill(id),
createSkill: (skill) => client.createSkill(skill),
updateSkill: (id, updates) => client.updateSkill(id, updates),
deleteSkill: (id) => client.deleteSkill(id),
listChannels: () => client.listChannels(),
getChannel: (id) => client.getChannel(id),
createChannel: (channel) => client.createChannel(channel),
updateChannel: (id, updates) => client.updateChannel(id, updates),
deleteChannel: (id) => client.deleteChannel(id),
listScheduledTasks: () => client.listScheduledTasks(),
createScheduledTask: async (task) => {
const result = await client.createScheduledTask(task);
return {
id: result.id,
name: result.name,
schedule: result.schedule,
status: result.status as 'active' | 'paused' | 'completed' | 'error',
};
},
listModels: () => client.listModels(),
getFeishuStatus: () => client.getFeishuStatus(),
};
}
/**
* Sets the client for the config store.
* Called by the coordinator during initialization.
*/
export function setConfigStoreClient(client: unknown): void {
const configClient = createConfigClientFromGateway(client as GatewayClient);
useConfigStore.getState().setConfigStoreClient(configClient);
}

View File

@@ -14,7 +14,7 @@ import {
import {
isTauriRuntime,
prepareLocalGatewayForTauri,
getLocalGatewayStatus,
getLocalGatewayStatus as fetchLocalGatewayStatus,
startLocalGateway as startLocalGatewayCommand,
stopLocalGateway as stopLocalGatewayCommand,
restartLocalGateway as restartLocalGatewayCommand,
@@ -23,6 +23,7 @@ import {
getUnsupportedLocalGatewayStatus,
type LocalGatewayStatus,
} from '../lib/tauri-gateway';
import { useConfigStore } from './configStore';
// === Types ===
@@ -59,18 +60,6 @@ function requiresLocalDevicePairing(error: unknown): boolean {
return message.includes('pairing required');
}
/**
* Calculate security level based on enabled layer count.
*/
function calculateSecurityLevel(enabledCount: number, totalCount: number): 'critical' | 'high' | 'medium' | 'low' {
if (totalCount === 0) return 'low';
const ratio = enabledCount / totalCount;
if (ratio >= 0.875) return 'critical'; // 14-16 layers
if (ratio >= 0.625) return 'high'; // 10-13 layers
if (ratio >= 0.375) return 'medium'; // 6-9 layers
return 'low'; // 0-5 layers
}
/**
* Check if a URL is a loopback address.
*/
@@ -187,7 +176,7 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
// Check local gateway first if in Tauri
if (isTauriRuntime()) {
try {
const localStatus = await getLocalGatewayStatus();
const localStatus = await fetchLocalGatewayStatus();
const localUrl = getLocalGatewayConnectUrl(localStatus);
if (localUrl) {
candidates.push(localUrl);
@@ -198,7 +187,7 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
}
// Add quick config gateway URL if available
const quickConfigGatewayUrl = get().quickConfig?.gatewayUrl?.trim();
const quickConfigGatewayUrl = useConfigStore.getState().quickConfig?.gatewayUrl?.trim();
if (quickConfigGatewayUrl) {
candidates.push(quickConfigGatewayUrl);
}
@@ -233,7 +222,7 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
}
// Resolve effective token: param > quickConfig > localStorage > local auth
let effectiveToken = token || get().quickConfig?.gatewayToken || getStoredGatewayToken();
let effectiveToken = token || useConfigStore.getState().quickConfig?.gatewayToken || getStoredGatewayToken();
if (!effectiveToken && isTauriRuntime()) {
try {
const localAuth = await getLocalGatewayAuth();
@@ -246,7 +235,7 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
}
}
console.log('[ConnectionStore] Connecting with token:', effectiveToken ? `${effectiveToken.substring(0, 8)}...` : '(empty)');
console.log('[ConnectionStore] Connecting with token:', effectiveToken ? '[REDACTED]' : '(empty)');
const candidateUrls = await resolveCandidates();
let lastError: unknown = null;
@@ -327,7 +316,7 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
set({ localGatewayBusy: true });
try {
const status = await getLocalGatewayStatus();
const status = await fetchLocalGatewayStatus();
set({ localGateway: status, localGatewayBusy: false });
return status;
} catch (err: unknown) {

View File

@@ -27,6 +27,12 @@ interface Clone {
bootstrapReady?: boolean;
bootstrapFiles?: Array<{ name: string; path: string; exists: boolean }>;
updatedAt?: string;
// 人格相关字段
emoji?: string; // Agent emoji, e.g., "🦞", "🤖", "💻"
personality?: string; // 人格风格: professional, friendly, creative, concise
communicationStyle?: string; // 沟通风格描述
notes?: string; // 用户备注
onboardingCompleted?: boolean; // 是否完成首次引导
}
interface UsageStats {
@@ -93,6 +99,11 @@ interface QuickConfig {
autoSaveContext?: boolean;
fileWatching?: boolean;
privacyOptIn?: boolean;
// 人格相关字段
emoji?: string;
personality?: string;
communicationStyle?: string;
notes?: string;
}
interface WorkspaceInfo {
@@ -746,7 +757,8 @@ export const useGatewayStore = create<GatewayStore>((set, get) => {
loadClones: async () => {
try {
const result = await get().client.listClones();
const clones = result?.clones || result?.agents || [];
// API 可能返回数组,也可能返回 {clones: [...]} 或 {agents: [...]}
const clones = Array.isArray(result) ? result : (result?.clones || result?.agents || []);
set({ clones });
useChatStore.getState().syncAgents(clones);
@@ -1221,7 +1233,7 @@ export const useGatewayStore = create<GatewayStore>((set, get) => {
try {
const result = await get().client.listHandRuns(name, opts);
const runs: HandRun[] = (result?.runs || []).map((r: RawHandRun) => ({
runId: r.runId || r.run_id || r.id,
runId: r.runId || r.run_id || r.id || '',
status: r.status || 'unknown',
startedAt: r.startedAt || r.started_at || r.created_at || new Date().toISOString(),
completedAt: r.completedAt || r.completed_at || r.finished_at,
@@ -1486,15 +1498,15 @@ export const useGatewayStore = create<GatewayStore>((set, get) => {
try {
const result = await get().client.listApprovals(status);
const approvals: Approval[] = (result?.approvals || []).map((a: RawApproval) => ({
id: a.id || a.approval_id,
handName: a.hand_name || a.handName,
runId: a.run_id || a.runId,
id: a.id || a.approval_id || '',
handName: a.hand_name || a.handName || '',
runId: a.run_id || a.runId || '',
status: a.status || 'pending',
requestedAt: a.requested_at || a.requestedAt || new Date().toISOString(),
requestedBy: a.requested_by || a.requestedBy,
reason: a.reason || a.description,
requestedBy: a.requested_by || a.requestedBy || '',
reason: a.reason || a.description || '',
action: a.action || 'execute',
params: a.params,
params: a.params || {},
respondedAt: a.responded_at || a.respondedAt,
respondedBy: a.responded_by || a.respondedBy,
responseReason: a.response_reason || a.responseReason,

View File

@@ -65,6 +65,17 @@ export interface Approval {
responseReason?: string;
}
// === Trigger Create Options ===
export interface TriggerCreateOptions {
type: string;
name?: string;
enabled?: boolean;
config?: Record<string, unknown>;
handName?: string;
workflowId?: string;
}
// === Raw API Response Types (for mapping) ===
interface RawHandRequirement {
@@ -129,30 +140,32 @@ interface HandClient {
getHand: (name: string) => Promise<Record<string, unknown> | null>;
listHandRuns: (name: string, opts?: { limit?: number; offset?: number }) => Promise<{ runs?: RawHandRun[] } | null>;
triggerHand: (name: string, params?: Record<string, unknown>) => Promise<{ runId?: string; status?: string } | null>;
approveHand: (name: string, runId: string, approved: boolean, reason?: string) => Promise<void>;
cancelHand: (name: string, runId: string) => Promise<void>;
approveHand: (name: string, runId: string, approved: boolean, reason?: string) => Promise<{ status: string }>;
cancelHand: (name: string, runId: string) => Promise<{ status: string }>;
listTriggers: () => Promise<{ triggers?: Trigger[] } | null>;
getTrigger: (id: string) => Promise<Trigger | null>;
createTrigger: (trigger: { type: string; name?: string; enabled?: boolean; config?: Record<string, unknown>; handName?: string; workflowId?: string }) => Promise<{ id?: string } | null>;
updateTrigger: (id: string, updates: { name?: string; enabled?: boolean; config?: Record<string, unknown>; handName?: string; workflowId?: string }) => Promise<void>;
deleteTrigger: (id: string) => Promise<void>;
updateTrigger: (id: string, updates: { name?: string; enabled?: boolean; config?: Record<string, unknown>; handName?: string; workflowId?: string }) => Promise<{ id: string }>;
deleteTrigger: (id: string) => Promise<{ status: string }>;
listApprovals: (status?: ApprovalStatus) => Promise<{ approvals?: RawApproval[] } | null>;
respondToApproval: (approvalId: string, approved: boolean, reason?: string) => Promise<void>;
respondToApproval: (approvalId: string, approved: boolean, reason?: string) => Promise<{ status: string }>;
}
interface HandStore {
// State
// === Store State Slice ===
export interface HandStateSlice {
hands: Hand[];
handRuns: Record<string, HandRun[]>;
triggers: Trigger[];
approvals: Approval[];
isLoading: boolean;
error: string | null;
// Client reference (injected via setHandStoreClient)
client: HandClient | null;
}
// Actions
// === Store Actions Slice ===
export interface HandActionsSlice {
setHandStoreClient: (client: HandClient) => void;
loadHands: () => Promise<void>;
getHandDetails: (name: string) => Promise<Hand | undefined>;
@@ -162,7 +175,7 @@ interface HandStore {
cancelHand: (name: string, runId: string) => Promise<void>;
loadTriggers: () => Promise<void>;
getTrigger: (id: string) => Promise<Trigger | undefined>;
createTrigger: (trigger: { type: string; name?: string; enabled?: boolean; config?: Record<string, unknown>; handName?: string; workflowId?: string }) => Promise<Trigger | undefined>;
createTrigger: (trigger: TriggerCreateOptions) => Promise<Trigger | undefined>;
updateTrigger: (id: string, updates: { name?: string; enabled?: boolean; config?: Record<string, unknown>; handName?: string; workflowId?: string }) => Promise<Trigger | undefined>;
deleteTrigger: (id: string) => Promise<void>;
loadApprovals: (status?: ApprovalStatus) => Promise<void>;
@@ -170,6 +183,10 @@ interface HandStore {
clearError: () => void;
}
// === Combined Store Type ===
export type HandStore = HandStateSlice & HandActionsSlice;
export const useHandStore = create<HandStore>((set, get) => ({
// Initial State
hands: [],
@@ -383,7 +400,7 @@ export const useHandStore = create<HandStore>((set, get) => ({
}
},
createTrigger: async (trigger) => {
createTrigger: async (trigger: TriggerCreateOptions) => {
const client = get().client;
if (!client) return undefined;
@@ -496,3 +513,14 @@ export function createHandClientFromGateway(client: GatewayClient): HandClient {
respondToApproval: (approvalId, approved, reason) => client.respondToApproval(approvalId, approved, reason),
};
}
// === Client Injection ===
/**
* Sets the client for the hand store.
* Called by the coordinator during initialization.
*/
export function setHandStoreClient(client: unknown): void {
const handClient = createHandClientFromGateway(client as GatewayClient);
useHandStore.getState().setHandStoreClient(handClient);
}

View File

@@ -26,15 +26,21 @@ export type { WorkflowStore, WorkflowStateSlice, WorkflowActionsSlice, Workflow,
export { useConfigStore, setConfigStoreClient } from './configStore';
export type { ConfigStore, ConfigStateSlice, ConfigActionsSlice, QuickConfig, WorkspaceInfo, ChannelInfo, ScheduledTask, SkillInfo } from './configStore';
// === New Stores ===
export { useMemoryGraphStore } from './memoryGraphStore';
export type { MemoryGraphStore, GraphNode, GraphEdge, GraphFilter, GraphLayout } from './memoryGraphStore';
export { useActiveLearningStore } from './activeLearningStore';
export type { ActiveLearningStore } from './activeLearningStore';
// === Composite Store Hook ===
import { useEffect, useMemo } from 'react';
import { useMemo } from 'react';
import { useConnectionStore, getClient } from './connectionStore';
import { useAgentStore, setAgentStoreClient } from './agentStore';
import { useHandStore, setHandStoreClient } from './handStore';
import { useWorkflowStore, setWorkflowStoreClient } from './workflowStore';
import { useConfigStore, setConfigStoreClient } from './configStore';
import type { GatewayClient } from '../lib/gateway-client';
/**
* Initialize all stores with the shared client.
@@ -113,7 +119,7 @@ export function useCompositeStore() {
const createTrigger = useHandStore((s) => s.createTrigger);
const deleteTrigger = useHandStore((s) => s.deleteTrigger);
const loadApprovals = useHandStore((s) => s.loadApprovals);
const approveRequest = useHandStore((s) => s.approveRequest);
const respondToApproval = useHandStore((s) => s.respondToApproval);
const loadWorkflows = useWorkflowStore((s) => s.loadWorkflows);
const getWorkflow = useWorkflowStore((s) => s.getWorkflow);
@@ -203,7 +209,7 @@ export function useCompositeStore() {
createTrigger,
deleteTrigger,
loadApprovals,
approveRequest,
respondToApproval,
// Workflow actions
loadWorkflows,
@@ -244,7 +250,7 @@ export function useCompositeStore() {
quickConfig, workspaceInfo, channels, scheduledTasks, skillsCatalog, models, modelsLoading, modelsError,
connect, disconnect, clearLogs, refreshLocalGateway, startLocalGateway, stopLocalGateway, restartLocalGateway,
loadClones, createClone, updateClone, deleteClone, loadUsageStats, loadPluginStatus,
loadHands, getHandDetails, triggerHand, loadHandRuns, loadTriggers, createTrigger, deleteTrigger, loadApprovals, approveRequest,
loadHands, getHandDetails, triggerHand, loadHandRuns, loadTriggers, createTrigger, deleteTrigger, loadApprovals, respondToApproval,
loadWorkflows, getWorkflow, createWorkflow, updateWorkflow, deleteWorkflow, triggerWorkflow, loadWorkflowRuns,
loadQuickConfig, saveQuickConfig, loadWorkspaceInfo, loadChannels, getChannel, createChannel, updateChannel, deleteChannel,
loadScheduledTasks, createScheduledTask, loadSkillsCatalog, getSkill, createSkill, updateSkill, deleteSkill, loadModels,

View File

@@ -0,0 +1,316 @@
/**
* MemoryGraphStore - 记忆图谱状态管理
*
* 管理记忆图谱可视化的状态,包括节点、边、布局和交互。
*/
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { getMemoryManager, type MemoryEntry, type MemoryType } from '../lib/agent-memory';
export type { MemoryType };
// === Types ===
export interface GraphNode {
id: string;
type: MemoryType;
label: string;
x: number;
y: number;
vx: number;
vy: number;
importance: number;
accessCount: number;
createdAt: string;
isHighlighted: boolean;
isSelected: boolean;
}
export interface GraphEdge {
id: string;
source: string;
target: string;
type: 'reference' | 'related' | 'derived';
strength: number;
}
export interface GraphFilter {
types: MemoryType[];
minImportance: number;
dateRange: {
start?: string;
end?: string;
};
searchQuery: string;
}
export interface GraphLayout {
width: number;
height: number;
zoom: number;
offsetX: number;
offsetY: number;
}
interface MemoryGraphState {
nodes: GraphNode[];
edges: GraphEdge[];
isLoading: boolean;
error: string | null;
filter: GraphFilter;
layout: GraphLayout;
selectedNodeId: string | null;
hoveredNodeId: string | null;
showLabels: boolean;
simulationRunning: boolean;
}
interface MemoryGraphActions {
loadGraph: (agentId: string) => Promise<void>;
setFilter: (filter: Partial<GraphFilter>) => void;
resetFilter: () => void;
setLayout: (layout: Partial<GraphLayout>) => void;
selectNode: (nodeId: string | null) => void;
hoverNode: (nodeId: string | null) => void;
toggleLabels: () => void;
startSimulation: () => void;
stopSimulation: () => void;
updateNodePositions: (updates: Array<{ id: string; x: number; y: number }>) => void;
highlightSearch: (query: string) => void;
clearHighlight: () => void;
exportAsImage: () => Promise<Blob | null>;
getFilteredNodes: () => GraphNode[];
getFilteredEdges: () => GraphEdge[];
}
const DEFAULT_FILTER: GraphFilter = {
types: ['fact', 'preference', 'lesson', 'context', 'task'],
minImportance: 0,
dateRange: {},
searchQuery: '',
};
const DEFAULT_LAYOUT: GraphLayout = {
width: 800,
height: 600,
zoom: 1,
offsetX: 0,
offsetY: 0,
};
export type MemoryGraphStore = MemoryGraphState & MemoryGraphActions;
// === Helper Functions ===
function memoryToNode(memory: MemoryEntry, index: number, total: number): GraphNode {
// 使用圆形布局初始位置
const angle = (index / total) * 2 * Math.PI;
const radius = 200;
return {
id: memory.id,
type: memory.type,
label: memory.content.slice(0, 50) + (memory.content.length > 50 ? '...' : ''),
x: 400 + radius * Math.cos(angle),
y: 300 + radius * Math.sin(angle),
vx: 0,
vy: 0,
importance: memory.importance,
accessCount: memory.accessCount,
createdAt: memory.createdAt,
isHighlighted: false,
isSelected: false,
};
}
function findRelatedMemories(memories: MemoryEntry[]): GraphEdge[] {
const edges: GraphEdge[] = [];
// 简单的关联算法:基于共同标签和关键词
for (let i = 0; i < memories.length; i++) {
for (let j = i + 1; j < memories.length; j++) {
const m1 = memories[i];
const m2 = memories[j];
// 检查共同标签
const commonTags = m1.tags.filter(t => m2.tags.includes(t));
if (commonTags.length > 0) {
edges.push({
id: `edge-${m1.id}-${m2.id}`,
source: m1.id,
target: m2.id,
type: 'related',
strength: commonTags.length * 0.3,
});
}
// 同类型记忆关联
if (m1.type === m2.type) {
const existingEdge = edges.find(
e => e.source === m1.id && e.target === m2.id
);
if (!existingEdge) {
edges.push({
id: `edge-${m1.id}-${m2.id}-type`,
source: m1.id,
target: m2.id,
type: 'derived',
strength: 0.1,
});
}
}
}
}
return edges;
}
export const useMemoryGraphStore = create<MemoryGraphStore>()(
persist(
(set, get) => ({
nodes: [],
edges: [],
isLoading: false,
error: null,
filter: DEFAULT_FILTER,
layout: DEFAULT_LAYOUT,
selectedNodeId: null,
hoveredNodeId: null,
showLabels: true,
simulationRunning: false,
loadGraph: async (agentId: string) => {
set({ isLoading: true, error: null });
try {
const mgr = getMemoryManager();
const memories = await mgr.getAll(agentId, { limit: 200 });
const nodes = memories.map((m, i) => memoryToNode(m, i, memories.length));
const edges = findRelatedMemories(memories);
set({
nodes,
edges,
isLoading: false,
});
} catch (err) {
set({
isLoading: false,
error: err instanceof Error ? err.message : '加载图谱失败',
});
}
},
setFilter: (filter) => {
set(state => ({
filter: { ...state.filter, ...filter },
}));
},
resetFilter: () => {
set({ filter: DEFAULT_FILTER });
},
setLayout: (layout) => {
set(state => ({
layout: { ...state.layout, ...layout },
}));
},
selectNode: (nodeId) => {
set(state => ({
selectedNodeId: nodeId,
nodes: state.nodes.map(n => ({
...n,
isSelected: n.id === nodeId,
})),
}));
},
hoverNode: (nodeId) => {
set(state => ({
hoveredNodeId: nodeId,
nodes: state.nodes.map(n => ({
...n,
isHighlighted: nodeId ? n.id === nodeId : n.isHighlighted,
})),
}));
},
toggleLabels: () => {
set(state => ({ showLabels: !state.showLabels }));
},
startSimulation: () => {
set({ simulationRunning: true });
},
stopSimulation: () => {
set({ simulationRunning: false });
},
updateNodePositions: (updates) => {
set(state => ({
nodes: state.nodes.map(node => {
const update = updates.find(u => u.id === node.id);
return update ? { ...node, x: update.x, y: update.y } : node;
}),
}));
},
highlightSearch: (query) => {
const lowerQuery = query.toLowerCase();
set(state => ({
filter: { ...state.filter, searchQuery: query },
nodes: state.nodes.map(n => ({
...n,
isHighlighted: query ? n.label.toLowerCase().includes(lowerQuery) : false,
})),
}));
},
clearHighlight: () => {
set(state => ({
nodes: state.nodes.map(n => ({ ...n, isHighlighted: false })),
}));
},
exportAsImage: async () => {
// SVG 导出逻辑在组件中实现
return null;
},
getFilteredNodes: () => {
const { nodes, filter } = get();
return nodes.filter(n => {
if (!filter.types.includes(n.type)) return false;
if (n.importance < filter.minImportance) return false;
if (filter.dateRange.start && n.createdAt < filter.dateRange.start) return false;
if (filter.dateRange.end && n.createdAt > filter.dateRange.end) return false;
if (filter.searchQuery) {
return n.label.toLowerCase().includes(filter.searchQuery.toLowerCase());
}
return true;
});
},
getFilteredEdges: () => {
const { edges } = get();
const filteredNodes = get().getFilteredNodes();
const nodeIds = new Set(filteredNodes.map(n => n.id));
return edges.filter(e => nodeIds.has(e.source) && nodeIds.has(e.target));
},
}),
{
name: 'zclaw-memory-graph',
partialize: (state) => ({
filter: state.filter,
layout: state.layout,
showLabels: state.showLabels,
}),
}
)
);

View File

@@ -0,0 +1,411 @@
/**
* * 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[]> {
// 这里我们模拟扫描,实际实现需要通过 Tauri API 访问文件系统
// 或者从预定义的技能列表中加载
const skills: Skill[] = [
// 开发类
{
id: 'code-review',
name: '代码审查',
description: '审查代码、分析代码质量、提供改进建议',
triggers: ['审查代码', '代码审查', 'code review', 'PR Review', '检查代码', '分析代码'],
capabilities: ['代码质量分析', '架构评估', '最佳实践检查', '安全审计'],
toolDeps: ['read', 'grep', 'glob'],
category: 'development',
installed: false,
tags: ['代码', '审查', '质量'],
},
{
id: 'translation',
name: '翻译助手',
description: '翻译文本、多语言转换、保持语言风格一致性',
triggers: ['翻译', 'translate', '中译英', '英译中', '翻译成', '转换成'],
capabilities: ['多语言翻译', '技术文档翻译', '代码注释翻译', 'UI 文本翻译', '风格保持'],
toolDeps: ['read', 'write'],
category: 'content',
installed: false,
tags: ['翻译', '语言', '国际化'],
},
{
id: 'chinese-writing',
name: '中文写作',
description: '中文写作助手 - 帮助撰写各类中文文档、文章、报告',
triggers: ['写一篇', '帮我写', '撰写', '起草', '润色', '中文写作'],
capabilities: ['撰写文档', '润色修改', '调整语气', '中英文翻译'],
toolDeps: ['read', 'write'],
category: 'content',
installed: false,
tags: ['写作', '文档', '中文'],
},
{
id: 'web-search',
name: '网络搜索',
description: '搜索互联网信息、整合多方来源',
triggers: ['搜索', 'search', '查找信息', '查询', '搜索网络'],
capabilities: ['搜索引擎集成', '信息提取', '来源验证', '结果整合'],
toolDeps: ['web_search'],
category: 'research',
installed: false,
tags: ['搜索', '互联网', '信息'],
},
{
id: 'data-analysis',
name: '数据分析',
description: '数据清洗、统计分析、可视化图表',
triggers: ['数据分析', '统计', '可视化', '图表', 'analytics'],
capabilities: ['数据清洗', '统计分析', '可视化图表', '报告生成'],
toolDeps: ['read', 'write', 'shell'],
category: 'analytics',
installed: false,
tags: ['数据', '分析', '可视化'],
},
{
id: 'git',
name: 'Git 操作',
description: 'Git 版本控制操作、分支管理、冲突解决',
triggers: ['git', '版本控制', '分支', '合并', 'commit', 'merge'],
capabilities: ['分支管理', '冲突解决', 'rebase', 'cherry-pick'],
toolDeps: ['shell'],
category: 'development',
installed: false,
tags: ['git', '版本控制', '分支'],
},
{
id: 'shell-command',
name: 'Shell 命令',
description: '执行 Shell 命令、系统操作',
triggers: ['shell', '命令行', '终端', 'terminal', 'bash'],
capabilities: ['命令执行', '管道操作', '脚本运行', '环境管理'],
toolDeps: ['shell'],
category: 'ops',
installed: false,
tags: ['shell', '命令', '系统'],
},
{
id: 'file-operations',
name: '文件操作',
description: '文件读写、目录管理、文件搜索',
triggers: ['文件', 'file', '读取', '写入', '目录', '文件夹'],
capabilities: ['文件读写', '目录管理', '文件搜索', '批量操作'],
toolDeps: ['read', 'write', 'glob'],
category: 'ops',
installed: false,
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;
}

View File

@@ -8,6 +8,7 @@
*/
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type {
Team,
TeamMember,
@@ -120,7 +121,9 @@ const calculateMetrics = (team: Team): TeamMetrics => {
// === Store Implementation ===
export const useTeamStore = create<TeamStoreState>((set, get) => ({
export const useTeamStore = create<TeamStoreState>()(
persist(
(set, get) => ({
// Initial State
teams: [],
activeTeam: null,
@@ -582,4 +585,12 @@ export const useTeamStore = create<TeamStoreState>((set, get) => ({
clearError: () => {
set({ error: null });
},
}));
}),
{
name: 'zclaw-teams',
partialize: (state) => ({
teams: state.teams,
activeTeam: state.activeTeam,
}),
},
));

View File

@@ -1,5 +1,6 @@
import { create } from 'zustand';
import { Workflow, WorkflowRun } from './gatewayStore';
import type { GatewayClient } from '../lib/gateway-client';
// === Types ===
@@ -30,7 +31,7 @@ export interface WorkflowStep {
condition?: string;
}
export interface CreateWorkflowInput {
export interface WorkflowCreateOptions {
name: string;
description?: string;
steps: WorkflowStep[];
@@ -53,7 +54,7 @@ export interface ExtendedWorkflowRun extends WorkflowRun {
interface WorkflowClient {
listWorkflows(): Promise<{ workflows: { id: string; name: string; steps: number; description?: string; createdAt?: string }[] } | null>;
createWorkflow(workflow: CreateWorkflowInput): Promise<{ id: string; name: string } | null>;
createWorkflow(workflow: WorkflowCreateOptions): Promise<{ id: string; name: string } | null>;
updateWorkflow(id: string, updates: UpdateWorkflowInput): Promise<{ id: string; name: string } | null>;
deleteWorkflow(id: string): Promise<{ status: string }>;
executeWorkflow(id: string, input?: Record<string, unknown>): Promise<{ runId: string; status: string } | null>;
@@ -61,9 +62,9 @@ interface WorkflowClient {
listWorkflowRuns(workflowId: string, opts?: { limit?: number; offset?: number }): Promise<{ runs: RawWorkflowRun[] } | null>;
}
// === Store State ===
// === Store State Slice ===
interface WorkflowState {
export interface WorkflowStateSlice {
workflows: Workflow[];
workflowRuns: Record<string, ExtendedWorkflowRun[]>;
isLoading: boolean;
@@ -71,13 +72,13 @@ interface WorkflowState {
client: WorkflowClient;
}
// === Store Actions ===
// === Store Actions Slice ===
interface WorkflowActions {
export interface WorkflowActionsSlice {
setWorkflowStoreClient: (client: WorkflowClient) => void;
loadWorkflows: () => Promise<void>;
getWorkflow: (id: string) => Workflow | undefined;
createWorkflow: (workflow: CreateWorkflowInput) => Promise<Workflow | undefined>;
createWorkflow: (workflow: WorkflowCreateOptions) => Promise<Workflow | undefined>;
updateWorkflow: (id: string, updates: UpdateWorkflowInput) => Promise<Workflow | undefined>;
deleteWorkflow: (id: string) => Promise<void>;
triggerWorkflow: (id: string, input?: Record<string, unknown>) => Promise<{ runId: string; status: string } | undefined>;
@@ -87,6 +88,10 @@ interface WorkflowActions {
reset: () => void;
}
// === Combined Store Type ===
export type WorkflowStore = WorkflowStateSlice & WorkflowActionsSlice;
// === Initial State ===
const initialState = {
@@ -99,7 +104,7 @@ const initialState = {
// === Store ===
export const useWorkflowStore = create<WorkflowState & WorkflowActions>((set, get) => ({
export const useWorkflowStore = create<WorkflowStateSlice & WorkflowActionsSlice>((set, get) => ({
...initialState,
setWorkflowStoreClient: (client: WorkflowClient) => {
@@ -128,7 +133,7 @@ export const useWorkflowStore = create<WorkflowState & WorkflowActions>((set, ge
return get().workflows.find(w => w.id === id);
},
createWorkflow: async (workflow: CreateWorkflowInput) => {
createWorkflow: async (workflow: WorkflowCreateOptions) => {
set({ error: null });
try {
const result = await get().client.createWorkflow(workflow);
@@ -253,3 +258,29 @@ export const useWorkflowStore = create<WorkflowState & WorkflowActions>((set, ge
// Re-export types from gatewayStore for convenience
export type { Workflow, WorkflowRun };
// === Client Injection ===
/**
* Helper to create a WorkflowClient adapter from a GatewayClient.
*/
function createWorkflowClientFromGateway(client: GatewayClient): WorkflowClient {
return {
listWorkflows: () => client.listWorkflows(),
createWorkflow: (workflow) => client.createWorkflow(workflow),
updateWorkflow: (id, updates) => client.updateWorkflow(id, updates),
deleteWorkflow: (id) => client.deleteWorkflow(id),
executeWorkflow: (id, input) => client.executeWorkflow(id, input),
cancelWorkflow: (workflowId, runId) => client.cancelWorkflow(workflowId, runId),
listWorkflowRuns: (workflowId, opts) => client.listWorkflowRuns(workflowId, opts),
};
}
/**
* Sets the client for the workflow store.
* Called by the coordinator during initialization.
*/
export function setWorkflowStoreClient(client: unknown): void {
const workflowClient = createWorkflowClientFromGateway(client as GatewayClient);
useWorkflowStore.getState().setWorkflowStoreClient(workflowClient);
}