# ZCLAW 架构优化设计规格 > **版本**: 1.1 > **日期**: 2026-03-21 > **状态**: 待审核 > **作者**: Claude + 用户协作 --- ## 1. 概述 ### 1.1 背景 ZCLAW 是面向中文用户的 AI Agent 桌面客户端,经过分析发现以下需要改进的领域: - **安全风险**: localStorage 凭据回退明文存储、WebSocket 非 localhost 允许 ws:// - **性能瓶颈**: 流式更新时重建整个消息数组、无界消息数组 - **架构问题**: 50+ lib 模块缺乏统一抽象、测试覆盖不足 ### 1.2 目标 - 在 14 周内完成全面架构优化 (含 2 周缓冲) - 采用激进架构优先策略 - 重点优化四个核心系统:对话、Hands、Intelligence、技能 ### 1.3 术语表 | 术语 | 含义 | 备注 | |------|------|------| | **Valtio** | Proxy-based 状态管理库 | pmnd.rs/valtio,将替代 Zustand | | Zustand | 当前使用的状态管理库 | 将被 Valtio 替代 | | XState | 状态机库 | 用于 Hands 状态管理 | ### 1.4 关键决策 | 决策点 | 选择 | 理由 | |--------|------|------| | 状态管理 | **Valtio** (替代 Zustand) | Proxy 细粒度响应,性能更好 | | 安全策略 | 凭据加密存储 + WSS 强制 | 解决实际存在的安全风险 | | 整体方案 | A+C 混合 | 渐进式 + 领域驱动结合 | --- ## 2. 架构设计 ### 2.1 总体架构 ``` ┌─────────────────────────────────────────────────────────────────┐ │ UI Layer │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ React Components (60+) │ │ │ │ - 按领域组织 │ │ │ │ - 只负责展示和交互 │ │ │ └──────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ State Layer │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Valtio Stores (基于 Proxy) │ │ │ │ - 细粒度响应 │ │ │ │ - 领域划分: Chat, Hands, Intelligence, Skills │ │ │ └──────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Client Layer │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │ │ Gateway Client │ │ Intelligence Clt │ │ Worker Pool │ │ │ │ (WebSocket) │ │ (Tauri Commands) │ │ (隔离执行) │ │ │ └──────────────────┘ └──────────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Backend Layer │ │ ┌──────────────────┐ ┌──────────────────────────────────────┐│ │ │ ZCLAW Kernel │ │ Tauri Rust Backend ││ │ │ (Port 50051) │ │ - Intelligence (心跳/压缩/反思) ││ │ │ │ │ - Memory (SQLite 持久化) ││ │ │ │ │ - Browser (WebDriver) ││ │ └──────────────────┘ └──────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.2 领域划分 ``` desktop/src/ ├── domains/ # 领域模块 (新建) │ ├── chat/ # 对话系统 │ │ ├── store.ts # Valtio store │ │ ├── types.ts # 类型定义 │ │ ├── api.ts # API 调用 │ │ └── hooks.ts # React hooks │ ├── hands/ # 自动化系统 │ │ ├── store.ts # 状态机 store │ │ ├── machine.ts # XState 状态机 │ │ ├── executor.ts # 执行器 │ │ └── types.ts │ ├── intelligence/ # 智能层 │ │ ├── client.ts # 统一客户端 │ │ ├── cache.ts # 缓存策略 │ │ └── types.ts │ └── skills/ # 技能系统 │ ├── store.ts │ ├── loader.ts # 技能加载器 │ └── types.ts ├── workers/ # Web Workers (新建) │ ├── browser-worker.ts # 浏览器隔离执行 │ └── pool.ts # Worker 池管理 ├── shared/ # 共享模块 (新建) │ ├── error-handling.ts # 统一错误处理 │ ├── logging.ts # 统一日志 │ └── types.ts # 共享类型 └── components/ # UI 组件 (保持) ``` ### 2.3 现有代码分析 | 模块 | 现有文件 | 行数 | 迁移策略 | |------|----------|------|----------| | Chat | `store/chatStore.ts` | 689 | 迁移到 Valtio,保留 API | | Hands | `store/handStore.ts` | 535 | 迁移到 XState 状态机 | | Intelligence | `lib/intelligence-client.ts` | 956 | 保留,增强缓存层 | | Browser | `lib/browser-client.ts` | 461 | 保留,无需 Worker | | Secure Storage | `lib/secure-storage.ts` | 350 | 增强,添加加密回退 | | Gateway | `lib/gateway-client.ts` | 1100 | 保留,强制 WSS | ### 2.4 迁移映射表 | 现有文件 | 新位置 | 操作 | |----------|--------|------| | `store/chatStore.ts` | `domains/chat/store.ts` | 迁移 + 重写 | | `store/handStore.ts` | `domains/hands/store.ts` | 迁移 + 状态机 | | `store/skillStore.ts` | `domains/skills/store.ts` | 迁移 | | `lib/intelligence-client.ts` | `domains/intelligence/client.ts` | 迁移 + 增强 | | `lib/error-handling.ts` | `shared/error-handling.ts` | 迁移 | | `lib/secure-storage.ts` | `shared/secure-storage.ts` | 增强 | | `store/agentStore.ts` | 保留 | 不变 | | `store/connectionStore.ts` | 保留 | 不变 | | `lib/gateway-client.ts` | 保留 | 不变 | --- ## 3. 核心组件设计 ### 3.1 Valtio 状态管理 (替代 Zustand) **问题**: 当前 Zustand 每次更新都会触发整个订阅组件重渲染 **解决方案**: 使用 Valtio 的 Proxy 实现细粒度响应 ```typescript // domains/chat/store.ts import { proxy, useSnapshot } from 'valtio'; interface ChatState { messages: Message[]; conversations: Conversation[]; currentConversationId: string | null; isStreaming: boolean; // Actions addMessage: (message: Message) => void; updateMessage: (id: string, update: Partial) => void; setStreaming: (streaming: boolean) => void; } export const chatState = proxy({ messages: [], conversations: [], currentConversationId: null, isStreaming: false, addMessage: (message) => { chatState.messages.push(message); // 直接 mutate }, updateMessage: (id, update) => { const msg = chatState.messages.find(m => m.id === id); if (msg) Object.assign(msg, update); // 细粒度更新 }, setStreaming: (streaming) => { chatState.isStreaming = streaming; } }); // Hook export function useChatState() { return useSnapshot(chatState); // 只在访问的字段变化时重渲染 } // 组件使用 function MessageList() { const { messages } = useChatState(); // 只订阅 messages return messages.map(m => ); } function StreamingIndicator() { const { isStreaming } = useChatState(); // 只订阅 isStreaming return isStreaming ? : null; } ``` **迁移策略**: - 现有 `chatStore.ts` (689 行) 将被重写 - 保持相同的 API 接口,确保组件无需修改 - 逐步迁移,先迁移 Chat,再迁移其他 Store **预期收益**: - 流式更新时性能提升 70% - 代码更简洁 (直接 mutate) - 选择性渲染减少不必要的重渲染 ### 3.2 Hands 状态机 (XState) **问题**: 当前 `handStore.ts` (535 行) 状态转换逻辑分散,难以追踪 **解决方案**: 使用 XState 实现状态机,与现有 Store 集成 ```typescript // domains/hands/machine.ts import { createMachine, assign } from 'xstate'; export const handMachine = createMachine({ id: 'hand', initial: 'idle', states: { idle: { on: { TRIGGER: 'validating' }, }, validating: { on: { VALID: 'checking_approval', INVALID: 'error', }, }, checking_approval: { on: { NEEDS_APPROVAL: 'pending_approval', AUTO_APPROVE: 'executing', }, }, pending_approval: { on: { APPROVE: 'executing', REJECT: 'cancelled', TIMEOUT: 'error', }, }, executing: { on: { SUCCESS: 'completed', ERROR: 'error', TIMEOUT: 'error', }, }, completed: { on: { RESET: 'idle' } }, error: { on: { RETRY: 'validating', RESET: 'idle' } }, cancelled: { on: { RESET: 'idle' } }, }, }); // domains/hands/store.ts - 与现有 API 集成 import { createActorContext } from '@xstate/react'; export const HandContext = createActorContext(handMachine); // 使用方式 function HandPanel() { const state = HandContext.useSelector(s => s); const send = HandContext.useActorRef(); return (
{state.matches('pending_approval') && } {state.matches('executing') && }
); } ``` **迁移策略**: - 现有 `handStore.ts` 保持向后兼容 - 新建 `domains/hands/` 模块 - 逐步将状态逻辑迁移到 XState - 保持现有组件 API 不变 ### 3.3 Intelligence 缓存增强 **问题**: 现有 `intelligence-client.ts` (956 行) 已有 localStorage 回退缓存,但缺少 LRU 淘汰和 TTL **解决方案**: 增强现有缓存层,添加 LRU + TTL > **注意**: 这是增强现有实现,而非替换 ```typescript // domains/intelligence/cache.ts interface CacheEntry { value: T; expiresAt: number; } export class IntelligenceCache { private cache = new Map>(); private maxSize = 100; private defaultTTL = 5 * 60 * 1000; // 5 minutes get(key: string): T | null { const entry = this.cache.get(key); if (!entry) return null; if (Date.now() > entry.expiresAt) { this.cache.delete(key); return null; } return entry.value as T; } set(key: string, value: T, ttl = this.defaultTTL): void { // LRU 淘汰 if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(key, { value, expiresAt: Date.now() + ttl, }); } invalidate(pattern: string): void { for (const key of this.cache.keys()) { if (key.includes(pattern)) { this.cache.delete(key); } } } } ``` --- ## 4. 安全设计 ### 4.1 凭据存储加密增强 > **现有实现**: `lib/secure-storage.ts` (350 行) 已使用 OS keyring + localStorage fallback > **问题**: localStorage fallback 存储明文密钥,存在安全风险 > **策略**: 增强现有实现,为 localStorage fallback 添加加密 ```typescript // shared/secure-storage.ts export class SecureCredentialStorage { private async getEncryptionKey(): Promise { // 从 OS keyring 获取或派生 const masterKey = await this.getMasterKey(); return deriveKey(masterKey, SALT, { name: 'AES-GCM', length: 256, }); } async store(key: string, value: string): Promise { const encKey = await this.getEncryptionKey(); const iv = crypto.getRandomValues(new Uint8Array(12)); const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, encKey, new TextEncoder().encode(value) ); // 存储加密后的数据 const stored = { iv: arrayToBase64(iv), data: arrayToBase64(new Uint8Array(encrypted)), }; if (isTauriEnv()) { await invoke('secure_store', { key, value: JSON.stringify(stored) }); } else { localStorage.setItem(`enc_${key}`, JSON.stringify(stored)); } } async retrieve(key: string): Promise { let stored: { iv: string; data: string }; if (isTauriEnv()) { stored = await invoke('secure_retrieve', { key }); } else { const raw = localStorage.getItem(`enc_${key}`); if (!raw) return null; stored = JSON.parse(raw); } const encKey = await this.getEncryptionKey(); const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: base64ToArray(stored.iv) }, encKey, base64ToArray(stored.data) ); return new TextDecoder().decode(decrypted); } } ``` ### 4.2 WebSocket 安全 ```typescript // shared/websocket-security.ts export function createSecureWebSocket(url: string): WebSocket { // 强制 WSS for non-localhost if (!url.startsWith('wss://') && !isLocalhost(url)) { throw new SecurityError( 'Non-localhost connections must use WSS protocol' ); } const ws = new WebSocket(url); // 添加消息验证 ws.addEventListener('message', (event) => { const message = validateMessage(event.data); if (!message) { console.error('Invalid message format'); ws.close(1008, 'Policy Violation'); } }); return ws; } function isLocalhost(url: string): boolean { const parsed = new URL(url); return ['localhost', '127.0.0.1', '::1'].includes(parsed.hostname); } ``` --- ## 5. 测试策略 ### 5.1 单元测试 ```typescript // domains/chat/store.test.ts describe('ChatState', () => { beforeEach(() => { chatState.messages = []; chatState.isStreaming = false; }); it('should add message', () => { const msg = createMockMessage(); chatState.addMessage(msg); expect(chatState.messages).toHaveLength(1); expect(chatState.messages[0]).toEqual(msg); }); it('should update message content', () => { chatState.messages = [createMockMessage({ id: '1', content: 'old' })]; chatState.updateMessage('1', { content: 'new' }); expect(chatState.messages[0].content).toBe('new'); }); it('should not trigger re-render for unrelated updates', () => { const { result, rerender } = renderHook(() => useChatState()); act(() => { chatState.isStreaming = true; // 不会触发 MessageList 重渲染 }); // 验证只有订阅 isStreaming 的组件重渲染 }); }); ``` ### 5.2 集成测试 ```typescript // domains/hands/machine.test.ts describe('HandMachine', () => { it('should transition from idle to validating on TRIGGER', () => { const service = interpret(handMachine).start(); service.send('TRIGGER'); expect(service.state.value).toBe('validating'); }); it('should reach pending_approval for hands requiring approval', () => { const service = interpret(handMachine).start(); service.send('TRIGGER'); service.send('VALID'); service.send('NEEDS_APPROVAL'); expect(service.state.value).toBe('pending_approval'); }); it('should cancel on REJECT', () => { const service = interpret(handMachine).start(); // ... navigate to pending_approval service.send('REJECT'); expect(service.state.value).toBe('cancelled'); }); }); ``` ### 5.3 E2E 测试场景 1. **聊天流程**: 发送消息 → 接收流式响应 → 显示完整消息 2. **Hands 触发**: 触发 Hand → 审批流程 → 执行 → 结果显示 3. **Intelligence**: 记忆提取 → 心跳触发 → 反思生成 4. **技能搜索**: 输入关键词 → 搜索技能 → 查看详情 --- ## 6. 实施计划 > **总周期**: 14 周 (含 2 周缓冲) ### 6.1 Phase 1: 安全 + 测试 (2周) **目标**: 建立安全基础和测试框架 #### TASK-001: 凭据加密存储增强 - **输入条件**: 现有 `secure-storage.ts` 可用 - **输出产物**: 增强的 `shared/secure-storage.ts` - **验收标准**: - localStorage fallback 使用 AES-GCM 加密 - 所有单元测试通过 - 无回归问题 - **预估工时**: 3 人日 - **依赖**: 无 #### TASK-002: 强制 WSS 连接 - **输入条件**: 现有 `gateway-client.ts` 可用 - **输出产物**: 更新的 `gateway-client.ts` - **验收标准**: - 非 localhost 连接必须使用 wss:// - 测试覆盖边界情况 - **预估工时**: 1 人日 - **依赖**: 无 #### TASK-003: 测试框架建立 - **输入条件**: 无 - **输出产物**: Vitest + Playwright 配置 - **验收标准**: - 覆盖率门禁 60% 生效 - CI 集成完成 - **预估工时**: 2 人日 - **依赖**: 无 #### TASK-004: chatStore 基础测试 - **输入条件**: 测试框架就绪 - **输出产物**: `store/chatStore.test.ts` - **验收标准**: - 核心功能测试覆盖 - 覆盖率 > 80% - **预估工时**: 2 人日 - **依赖**: TASK-003 ### 6.2 Phase 2: 领域重组 (4周) **目标**: 按领域重组代码,迁移到 Valtio #### TASK-101: 创建 domains/ 目录结构 - **预估工时**: 1 人日 #### TASK-102: 迁移 Chat Store 到 Valtio - **输入条件**: 现有 `chatStore.ts` (689 行) - **输出产物**: `domains/chat/store.ts` - **验收标准**: - API 向后兼容 - 性能提升 > 50% - 测试覆盖率 > 90% - **预估工时**: 5 人日 #### TASK-103: 迁移 Hands Store + XState - **输入条件**: 现有 `handStore.ts` (535 行) - **输出产物**: `domains/hands/store.ts` + `machine.ts` - **验收标准**: - 状态机完整实现 - 审批流程正常 - 测试覆盖率 > 85% - **预估工时**: 5 人日 #### TASK-104: 增强 Intelligence 缓存 - **输入条件**: 现有 `intelligence-client.ts` (956 行) - **输出产物**: 增强的缓存层 - **预估工时**: 3 人日 #### TASK-105: 提取共享模块 - **预估工时**: 2 人日 ### 6.3 Phase 3: 核心优化 (6周,并行) **Track A: Chat 优化 (2人)** - [ ] Valtio 性能优化 - [ ] 虚拟滚动实现 (react-window) - [ ] 消息分页 + 惰性加载 - [ ] 流式响应 AsyncGenerator **Track B: Hands 优化 (1人)** - [ ] XState 状态机完善 - [ ] 审批流程配置化 - [ ] 错误恢复机制 **Track C: Intelligence 优化 (1人)** - [ ] Rust 后端功能完善 - [ ] LRU 缓存集成 - [ ] 性能调优 ### 6.4 Phase 4: 集成 + 清理 (2周 = 1周实施 + 1周缓冲) **目标**: 完成集成,清理旧代码 **任务**: - [ ] 跨领域集成测试 - [ ] E2E 测试完善 - [ ] 清理旧代码 - [ ] 更新文档 - [ ] 性能基准测试 **交付物**: - 完整的测试套件 - 更新的文档 - 性能报告 --- ## 7. 验收标准 ### 7.1 功能验收 - [ ] 所有现有功能正常工作 - [ ] 无回归问题 - [ ] 新安全功能有效 ### 7.2 性能验收 | 指标 | 当前 | 目标 | 测量方法 | |------|------|------|----------| | 首屏加载 | ~3s | <2s | Chrome DevTools Performance → navigationStart 到 firstContentfulPaint | | 消息渲染 | 卡顿 | 60fps | React DevTools Profiler → MessageList 组件渲染时间 | | 1000+ 消息滚动 | 卡顿 | 流畅 | Chrome DevTools Performance → 滚动时 frame rate | | 内存占用 | 无界 | <500MB | Chrome Memory Profiler → 堆快照对比 | | Store 更新延迟 | ~50ms | <10ms | Performance.mark() 测量状态更新到渲染完成 | ### 7.3 安全验收 - [ ] localStorage 凭据已加密 (使用 AES-GCM) - [ ] 非 localhost 连接强制 WSS - [ ] 依赖安全扫描通过 (npm audit) - [ ] 密钥不在日志中输出 - [ ] WebSocket 安全测试通过 - [ ] 依赖安全扫描通过 ### 7.4 测试覆盖 | 模块 | 目标 | |------|------| | Chat Store | 90% | | Hands Store | 85% | | Intelligence Client | 80% | | Worker Pool | 85% | | 工具函数 | 95% | --- ## 8. 风险与缓解 | 风险 | 概率 | 影响 | 缓解措施 | |------|------|------|----------| | Valtio 学习曲线 | 中 | 中 | 提前 POC,编写示例 | | XState 状态机复杂度 | 中 | 中 | 从简单场景开始,逐步完善 | | 迁移导致回归 | 中 | 高 | 每阶段充分测试 | | 进度延期 | 中 | 中 | 预留 2 周缓冲 | | 团队不熟悉 | 中 | 中 | 培训 + 文档 | --- ## 9. 附录 ### 9.1 技术依赖 ```json { "dependencies": { "valtio": "1.11.2", "xstate": "5.18.2", "@xstate/react": "4.1.3", "react-window": "1.8.10" }, "devDependencies": { "vitest": "2.1.8", "@playwright/test": "1.49.1", "@testing-library/react": "16.1.0" } } ``` ### 9.2 参考文档 - [Valtio 文档](https://valtio.pmnd.rs/) - [XState 文档](https://xstate.js.org/) - [React Window 文档](https://react-window.vercel.app/) - [Tauri 安全最佳实践](https://tauri.app/v2/guides/security/) --- *文档版本: 1.1 | 最后更新: 2026-03-21*