# ZCLAW 架构优化 - Phase 2: 领域重组 实施计划 > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 按领域重组代码结构,迁移到 Valtio 状态管理,引入 XState 状态机 **Architecture:** 创建 domains/ 目录,按业务领域组织代码,使用 Valtio 替代 Zustand,使用 XState 管理 Hands 状态 **Tech Stack:** TypeScript, Valtio, XState, React **Spec Reference:** `docs/superpowers/specs/2026-03-21-architecture-optimization-design.md` **Duration:** 4 周 (16 人日) --- ## File Structure ### New Files ``` desktop/src/ ├── domains/ │ ├── chat/ │ │ ├── index.ts # 导出入口 │ │ ├── store.ts # Valtio store │ │ ├── types.ts # 类型定义 │ │ ├── api.ts # API 调用 │ │ └── hooks.ts # React hooks │ ├── hands/ │ │ ├── index.ts # 导出入口 │ │ ├── store.ts # Valtio store │ │ ├── machine.ts # XState 状态机 │ │ ├── types.ts # 类型定义 │ │ └── hooks.ts # React hooks │ ├── intelligence/ │ │ ├── index.ts # 导出入口 │ │ ├── client.ts # 统一客户端 │ │ ├── cache.ts # 缓存策略 │ │ └── types.ts # 类型定义 │ └── skills/ │ ├── index.ts # 导出入口 │ ├── store.ts # Valtio store │ └── types.ts # 类型定义 └── shared/ ├── index.ts # 导出入口 ├── error-handling.ts # 统一错误处理 ├── logging.ts # 统一日志 └── types.ts # 共享类型 ``` ### Modified Files ``` desktop/ ├── package.json # 添加 Valtio, XState 依赖 ├── src/store/chatStore.ts # 重导出 domains/chat ├── src/store/handStore.ts # 重导出 domains/hands └── src/components/ # 更新导入路径 ``` --- ## Chunk 1: 依赖安装和目录结构 ### Task 1.1: 安装 Valtio 和 XState **Files:** - Modify: `desktop/package.json` - [ ] **Step 1: 安装 Valtio** Run: ```bash cd g:/ZClaw_openfang/desktop && pnpm add valtio ``` Expected: valtio 安装成功 - [ ] **Step 2: 安装 XState** Run: ```bash cd g:/ZClaw_openfang/desktop && pnpm add xstate @xstate/react ``` Expected: xstate 和 @xstate/react 安装成功 - [ ] **Step 3: 验证安装** Run: ```bash cd g:/ZClaw_openfang/desktop && pnpm list valtio xstate @xstate/react ``` Expected: 显示已安装版本 - [ ] **Step 4: 提交依赖更新** ```bash cd g:/ZClaw_openfang && git add desktop/package.json desktop/pnpm-lock.yaml git commit -m "$(cat <<'EOF' feat(deps): add Valtio and XState for Phase 2 - Add valtio for Proxy-based state management - Add xstate and @xstate/react for state machines Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ### Task 1.2: 创建领域目录结构 **Files:** - Create: `desktop/src/domains/chat/` directory - Create: `desktop/src/domains/hands/` directory - Create: `desktop/src/domains/intelligence/` directory - Create: `desktop/src/domains/skills/` directory - Create: `desktop/src/shared/` directory - [ ] **Step 1: 创建目录** Run: ```bash cd g:/ZClaw_openfang/desktop/src && mkdir -p domains/chat domains/hands domains/intelligence domains/skills shared ``` - [ ] **Step 2: 提交目录结构** ```bash cd g:/ZClaw_openfang && git add desktop/src/domains desktop/src/shared git commit -m "$(cat <<'EOF' refactor: create domains directory structure - Create domains/chat for chat system - Create domains/hands for automation - Create domains/intelligence for AI layer - Create domains/skills for skill system - Create shared for common utilities Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ## Chunk 2: Chat Domain 迁移 ### Task 2.1: 创建 Chat Domain 类型定义 **Files:** - Create: `desktop/src/domains/chat/types.ts` - [ ] **Step 1: 提取类型定义** Create `desktop/src/domains/chat/types.ts`: ```typescript /** * Chat Domain Types * * Core types for the chat system. */ export interface MessageFile { name: string; path?: string; size?: number; type?: string; } export interface CodeBlock { language?: string; filename?: string; content?: string; } export interface Message { id: string; role: 'user' | 'assistant' | 'tool' | 'hand' | 'workflow'; content: string; timestamp: Date; runId?: string; streaming?: boolean; toolName?: string; toolInput?: string; toolOutput?: string; error?: string; handName?: string; handStatus?: string; handResult?: unknown; workflowId?: string; workflowStep?: string; workflowStatus?: string; workflowResult?: unknown; files?: MessageFile[]; codeBlocks?: CodeBlock[]; } export interface Conversation { id: string; title: string; messages: Message[]; sessionKey: string | null; agentId: string | null; createdAt: Date; updatedAt: Date; } export interface Agent { id: string; name: string; icon: string; color: string; lastMessage: string; time: string; } export interface AgentProfileLike { id: string; name: string; nickname?: string; role?: string; } export interface ChatState { messages: Message[]; conversations: Conversation[]; currentConversationId: string | null; agents: Agent[]; currentAgent: Agent | null; isStreaming: boolean; currentModel: string; sessionKey: string | null; } ``` - [ ] **Step 2: 提交类型定义** ```bash cd g:/ZClaw_openfang && git add desktop/src/domains/chat/types.ts git commit -m "$(cat <<'EOF' refactor(chat): extract chat domain types Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ### Task 2.2: 创建 Valtio Chat Store **Files:** - Create: `desktop/src/domains/chat/store.ts` - [ ] **Step 1: 创建 Valtio Store** Create `desktop/src/domains/chat/store.ts`: ```typescript /** * Chat Domain Store * * Valtio-based state management for chat. * Replaces Zustand for better performance with fine-grained reactivity. */ import { proxy, subscribe } from 'valtio'; import type { Message, Conversation, Agent, AgentProfileLike, ChatState } from './types'; // Default agent const DEFAULT_AGENT: Agent = { id: '1', name: 'ZCLAW', icon: '🦞', color: 'bg-gradient-to-br from-orange-500 to-red-500', lastMessage: '发送消息开始对话', time: '', }; // Helper functions function generateConvId(): string { return `conv_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`; } function deriveTitle(messages: Message[]): string { const firstUser = messages.find(m => m.role === 'user'); if (firstUser) { const text = firstUser.content.trim(); return text.length > 30 ? text.slice(0, 30) + '...' : text; } return '新对话'; } export function toChatAgent(profile: AgentProfileLike): Agent { return { id: profile.id, name: profile.name, icon: profile.nickname?.slice(0, 1) || '🦞', color: 'bg-gradient-to-br from-orange-500 to-red-500', lastMessage: profile.role || '新分身', time: '', }; } // State interface with actions interface ChatStore extends ChatState { // Actions addMessage: (message: Message) => void; updateMessage: (id: string, updates: Partial) => void; setCurrentAgent: (agent: Agent) => void; syncAgents: (profiles: AgentProfileLike[]) => void; setCurrentModel: (model: string) => void; newConversation: () => void; switchConversation: (id: string) => void; deleteConversation: (id: string) => void; clearMessages: () => void; } // Create proxy state export const chatStore = proxy({ // Initial state messages: [], conversations: [], currentConversationId: null, agents: [DEFAULT_AGENT], currentAgent: DEFAULT_AGENT, isStreaming: false, currentModel: 'glm-5', sessionKey: null, // Actions addMessage: (message: Message) => { chatStore.messages.push(message); }, updateMessage: (id: string, updates: Partial) => { const msg = chatStore.messages.find(m => m.id === id); if (msg) { Object.assign(msg, updates); } }, setCurrentAgent: (agent: Agent) => { chatStore.currentAgent = agent; }, syncAgents: (profiles: AgentProfileLike[]) => { if (profiles.length === 0) { chatStore.agents = [DEFAULT_AGENT]; } else { chatStore.agents = profiles.map(toChatAgent); } }, setCurrentModel: (model: string) => { chatStore.currentModel = model; }, newConversation: () => { // Save current conversation if has messages if (chatStore.messages.length > 0) { const conversation: Conversation = { id: chatStore.currentConversationId || generateConvId(), title: deriveTitle(chatStore.messages), messages: [...chatStore.messages], sessionKey: chatStore.sessionKey, agentId: chatStore.currentAgent?.id || null, createdAt: new Date(), updatedAt: new Date(), }; chatStore.conversations.unshift(conversation); } // Reset for new conversation chatStore.messages = []; chatStore.sessionKey = null; chatStore.isStreaming = false; chatStore.currentConversationId = null; }, switchConversation: (id: string) => { const conv = chatStore.conversations.find(c => c.id === id); if (conv) { // Save current first if (chatStore.messages.length > 0) { const currentConv: Conversation = { id: chatStore.currentConversationId || generateConvId(), title: deriveTitle(chatStore.messages), messages: [...chatStore.messages], sessionKey: chatStore.sessionKey, agentId: chatStore.currentAgent?.id || null, createdAt: new Date(), updatedAt: new Date(), }; const existingIndex = chatStore.conversations.findIndex( c => c.id === chatStore.currentConversationId ); if (existingIndex >= 0) { chatStore.conversations[existingIndex] = currentConv; } else { chatStore.conversations.unshift(currentConv); } } // Switch to new chatStore.messages = [...conv.messages]; chatStore.sessionKey = conv.sessionKey; chatStore.currentConversationId = conv.id; } }, deleteConversation: (id: string) => { const index = chatStore.conversations.findIndex(c => c.id === id); if (index >= 0) { chatStore.conversations.splice(index, 1); // If deleting current, clear messages if (chatStore.currentConversationId === id) { chatStore.messages = []; chatStore.sessionKey = null; chatStore.currentConversationId = null; } } }, clearMessages: () => { chatStore.messages = []; }, }); // Optional: Subscribe to changes for debugging if (import.meta.env.DEV) { subscribe(chatStore, (ops) => { console.log('[ChatStore] Changes:', ops); }); } ``` - [ ] **Step 2: 提交 Valtio Store** ```bash cd g:/ZClaw_openfang && git add desktop/src/domains/chat/store.ts git commit -m "$(cat <<'EOF' refactor(chat): create Valtio-based chat store - Replace Zustand with Valtio for fine-grained reactivity - Implement core actions: addMessage, updateMessage, etc. - Add conversation management: new, switch, delete Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ### Task 2.3: 创建 Chat Domain Hooks **Files:** - Create: `desktop/src/domains/chat/hooks.ts` - [ ] **Step 1: 创建 React Hooks** Create `desktop/src/domains/chat/hooks.ts`: ```typescript /** * Chat Domain Hooks * * React hooks for accessing chat state with Valtio. */ import { useSnapshot } from 'valtio'; import { chatStore } from './store'; import type { Message, Agent, Conversation } from './types'; /** * Hook to access the full chat state. * Only re-renders when accessed properties change. */ export function useChatState() { return useSnapshot(chatStore); } /** * Hook to access messages only. * Only re-renders when messages change. */ export function useMessages(): readonly Message[] { const { messages } = useSnapshot(chatStore); return messages; } /** * Hook to access streaming state. * Only re-renders when isStreaming changes. */ export function useIsStreaming(): boolean { const { isStreaming } = useSnapshot(chatStore); return isStreaming; } /** * Hook to access current agent. */ export function useCurrentAgent(): Agent | null { const { currentAgent } = useSnapshot(chatStore); return currentAgent; } /** * Hook to access conversations. */ export function useConversations(): readonly Conversation[] { const { conversations } = useSnapshot(chatStore); return conversations; } /** * Hook to access chat actions. * Returns the store directly for calling actions. */ export function useChatActions() { return chatStore; } ``` - [ ] **Step 2: 创建 Domain Index** Create `desktop/src/domains/chat/index.ts`: ```typescript /** * Chat Domain * * Public API for the chat system. */ // Types export type { Message, MessageFile, CodeBlock, Conversation, Agent, AgentProfileLike, ChatState, } from './types'; // Store export { chatStore, toChatAgent } from './store'; // Hooks export { useChatState, useMessages, useIsStreaming, useCurrentAgent, useConversations, useChatActions, } from './hooks'; ``` - [ ] **Step 3: 提交 Hooks 和 Index** ```bash cd g:/ZClaw_openfang && git add desktop/src/domains/chat/hooks.ts desktop/src/domains/chat/index.ts git commit -m "$(cat <<'EOF' refactor(chat): add chat domain hooks and public API - Add useChatState, useMessages, useIsStreaming hooks - Export types, store, and hooks from domain index Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ## Chunk 3: Hands Domain 迁移 ### Task 3.1: 创建 Hands Domain 类型定义 **Files:** - Create: `desktop/src/domains/hands/types.ts` - [ ] **Step 1: 创建类型定义** Create `desktop/src/domains/hands/types.ts`: ```typescript /** * Hands Domain Types * * Core types for the automation/hands system. */ export interface HandRequirement { description: string; met: boolean; details?: string; } export interface Hand { id: string; name: string; description: string; status: HandStatus; currentRunId?: string; requirements_met?: boolean; category?: string; icon?: string; provider?: string; model?: string; requirements?: HandRequirement[]; tools?: string[]; metrics?: string[]; toolCount?: number; metricCount?: number; } export type HandStatus = | 'idle' | 'running' | 'needs_approval' | 'error' | 'unavailable' | 'setup_needed'; export interface HandRun { runId: string; status: string; startedAt: string; completedAt?: string; result?: unknown; error?: string; } export interface Trigger { id: string; type: string; enabled: boolean; } export interface ApprovalRequest { id: string; handName: string; action: string; params: Record; createdAt: Date; } export interface HandsState { hands: Hand[]; runs: Record; triggers: Trigger[]; approvalQueue: ApprovalRequest[]; isLoading: boolean; error: string | null; } // XState Events export type HandsEvent = | { type: 'START'; handId: string } | { type: 'APPROVE'; requestId: string } | { type: 'REJECT'; requestId: string } | { type: 'COMPLETE'; runId: string; result: unknown } | { type: 'ERROR'; runId: string; error: string } | { type: 'RESET' }; ``` - [ ] **Step 2: 提交类型定义** ```bash cd g:/ZClaw_openfang && git add desktop/src/domains/hands/types.ts git commit -m "$(cat <<'EOF' refactor(hands): extract hands domain types Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ### Task 3.2: 创建 XState 状态机 **Files:** - Create: `desktop/src/domains/hands/machine.ts` - [ ] **Step 1: 创建状态机** Create `desktop/src/domains/hands/machine.ts`: ```typescript /** * Hands State Machine * * XState machine for managing hand execution lifecycle. */ import { setup, assign } from 'xstate'; import type { HandStatus, HandsEvent, Hand } from './types'; export interface HandContext { handId: string; handName: string; runId: string | null; error: string | null; result: unknown; } export type HandState = | { value: 'idle'; context: HandContext } | { value: 'running'; context: HandContext } | { value: 'needs_approval'; context: HandContext } | { value: 'success'; context: HandContext } | { value: 'error'; context: HandContext }; export const handMachine = setup({ types: { context: {} as HandContext, events: {} as HandsEvent, }, actions: { setRunId: assign({ runId: (_, params: { runId: string }) => params.runId, }), setError: assign({ error: (_, params: { error: string }) => params.error, }), setResult: assign({ result: (_, params: { result: unknown }) => params.result, }), clearError: assign({ error: null, }), }, }).createMachine({ id: 'hand', initial: 'idle', context: { handId: '', handName: '', runId: null, error: null, result: null, }, states: { idle: { on: { START: { target: 'running', actions: { type: 'setRunId', params: ({ event }) => ({ runId: `run_${Date.now()}` }), }, }, }, }, running: { on: { APPROVE: 'needs_approval', COMPLETE: { target: 'success', actions: { type: 'setResult', params: ({ event }) => ({ result: event.result }), }, }, ERROR: { target: 'error', actions: { type: 'setError', params: ({ event }) => ({ error: event.error }), }, }, }, }, needs_approval: { on: { APPROVE: 'running', REJECT: 'idle', }, }, success: { on: { RESET: { target: 'idle', actions: 'clearError', }, }, }, error: { on: { RESET: { target: 'idle', actions: 'clearError', }, START: 'running', }, }, }, }); ``` - [ ] **Step 2: 提交状态机** ```bash cd g:/ZClaw_openfang && git add desktop/src/domains/hands/machine.ts git commit -m "$(cat <<'EOF' refactor(hands): create XState machine for hand execution - Define states: idle, running, needs_approval, success, error - Define events: START, APPROVE, REJECT, COMPLETE, ERROR, RESET - Add context for tracking runId, error, result Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ### Task 3.3: 创建 Hands Valtio Store **Files:** - Create: `desktop/src/domains/hands/store.ts` - Create: `desktop/src/domains/hands/hooks.ts` - Create: `desktop/src/domains/hands/index.ts` - [ ] **Step 1: 创建 Store** Create `desktop/src/domains/hands/store.ts`: ```typescript /** * Hands Domain Store * * Valtio-based state management for hands/automation. */ import { proxy } from 'valtio'; import type { Hand, HandRun, Trigger, ApprovalRequest, HandsState } from './types'; interface HandsStore extends HandsState { // Actions setHands: (hands: Hand[]) => void; updateHand: (id: string, updates: Partial) => void; addRun: (run: HandRun) => void; updateRun: (runId: string, updates: Partial) => void; setTriggers: (triggers: Trigger[]) => void; addApproval: (request: ApprovalRequest) => void; removeApproval: (id: string) => void; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; } export const handsStore = proxy({ // Initial state hands: [], runs: {}, triggers: [], approvalQueue: [], isLoading: false, error: null, // Actions setHands: (hands: Hand[]) => { handsStore.hands = hands; }, updateHand: (id: string, updates: Partial) => { const hand = handsStore.hands.find(h => h.id === id); if (hand) { Object.assign(hand, updates); } }, addRun: (run: HandRun) => { handsStore.runs[run.runId] = run; }, updateRun: (runId: string, updates: Partial) => { if (handsStore.runs[runId]) { Object.assign(handsStore.runs[runId], updates); } }, setTriggers: (triggers: Trigger[]) => { handsStore.triggers = triggers; }, addApproval: (request: ApprovalRequest) => { handsStore.approvalQueue.push(request); }, removeApproval: (id: string) => { const index = handsStore.approvalQueue.findIndex(a => a.id === id); if (index >= 0) { handsStore.approvalQueue.splice(index, 1); } }, setLoading: (loading: boolean) => { handsStore.isLoading = loading; }, setError: (error: string | null) => { handsStore.error = error; }, }); ``` - [ ] **Step 2: 创建 Hooks** Create `desktop/src/domains/hands/hooks.ts`: ```typescript /** * Hands Domain Hooks */ import { useSnapshot } from 'valtio'; import { handsStore } from './store'; export function useHandsState() { return useSnapshot(handsStore); } export function useHands() { const { hands } = useSnapshot(handsStore); return hands; } export function useApprovalQueue() { const { approvalQueue } = useSnapshot(handsStore); return approvalQueue; } export function useHandsActions() { return handsStore; } ``` - [ ] **Step 3: 创建 Index** Create `desktop/src/domains/hands/index.ts`: ```typescript /** * Hands Domain */ export * from './types'; export { handMachine } from './machine'; export { handsStore } from './store'; export { useHandsState, useHands, useApprovalQueue, useHandsActions } from './hooks'; ``` - [ ] **Step 4: 提交 Hands Domain** ```bash cd g:/ZClaw_openfang && git add desktop/src/domains/hands/ git commit -m "$(cat <<'EOF' refactor(hands): complete hands domain migration - Add Valtio store for hands state - Add React hooks for hands access - Export all from domain index Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ## Chunk 4: Shared Module 提取 ### Task 4.1: 创建共享错误处理 **Files:** - Create: `desktop/src/shared/error-handling.ts` - Create: `desktop/src/shared/types.ts` - Create: `desktop/src/shared/index.ts` - [ ] **Step 1: 创建错误处理工具** Create `desktop/src/shared/error-handling.ts`: ```typescript /** * Shared Error Handling * * Unified error handling utilities. */ export class AppError extends Error { constructor( message: string, public code: string, public cause?: Error ) { super(message); this.name = 'AppError'; } } export function isError(error: unknown): error is Error { return error instanceof Error; } export function getErrorMessage(error: unknown): string { if (isError(error)) { return error.message; } return String(error); } export function wrapError(error: unknown, code: string): AppError { if (error instanceof AppError) { return error; } return new AppError(getErrorMessage(error), code, isError(error) ? error : undefined); } ``` - [ ] **Step 2: 创建共享类型** Create `desktop/src/shared/types.ts`: ```typescript /** * Shared Types */ export type Result = | { ok: true; value: T } | { ok: false; error: E }; export type AsyncResult = Promise>; export interface PaginatedResponse { items: T[]; total: number; page: number; pageSize: number; } ``` - [ ] **Step 3: 创建 Index** Create `desktop/src/shared/index.ts`: ```typescript /** * Shared Module */ export * from './error-handling'; export * from './types'; ``` - [ ] **Step 4: 提交共享模块** ```bash cd g:/ZClaw_openfang && git add desktop/src/shared/ git commit -m "$(cat <<'EOF' refactor(shared): create shared module - Add AppError class for unified error handling - Add Result type for functional error handling - Add PaginatedResponse type Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ## Chunk 5: 集成和验证 ### Task 5.1: 创建向后兼容层 **Files:** - Modify: `desktop/src/store/chatStore.ts` - [ ] **Step 1: 更新旧 Store 重导出** Update `desktop/src/store/chatStore.ts` to re-export from domain: ```typescript /** * Chat Store - Backward Compatibility Layer * * This file re-exports from the new domains/chat module. * Import from '@/domains/chat' for new code. */ export * from '../domains/chat'; ``` - [ ] **Step 2: 提交兼容层** ```bash cd g:/ZClaw_openfang && git add desktop/src/store/chatStore.ts git commit -m "$(cat <<'EOF' refactor(chat): add backward compatibility layer - Re-export from domains/chat for backward compatibility - Maintains existing import paths Co-Authored-By: Claude Opus 4.6 EOF )" ``` --- ### Task 5.2: 运行测试验证 - [ ] **Step 1: 运行 Chat Store 测试** Run: ```bash cd g:/ZClaw_openfang/desktop && pnpm test tests/store/chatStore.test.ts ``` Expected: Tests pass with new Valtio store - [ ] **Step 2: 运行所有测试** Run: ```bash cd g:/ZClaw_openfang/desktop && pnpm test ``` Expected: No new test failures --- ### Task 5.3: 更新文档 - [ ] **Step 1: 创建 Phase 2 变更日志** Create `docs/changelogs/2026-03-21-phase2-domain-reorganization.md` --- ## Verification Checklist ### Domain Structure - [ ] domains/chat/ created with types, store, hooks - [ ] domains/hands/ created with types, machine, store, hooks - [ ] shared/ created with error-handling, types ### State Management - [ ] Valtio installed and configured - [ ] Chat store migrated to Valtio - [ ] Hands store migrated to Valtio - [ ] XState machine created for hands ### Compatibility - [ ] Backward compatibility layer in place - [ ] Existing imports still work - [ ] Tests passing --- ## Next Steps (Phase 3) - Valtio 性能优化 - XState 状态机完整集成 - Intelligence 缓存增强 - 组件迁移到新 Hooks