# ZCLAW 架构优化设计规格 > **版本**: 1.0 > **日期**: 2026-03-21 > **状态**: 待审核 > **作者**: Claude + 用户协作 --- ## 1. 概述 ### 1.1 背景 ZCLAW 是面向中文用户的 AI Agent 桌面客户端,经过分析发现以下需要改进的领域: - **安全风险**: 浏览器 eval() XSS 风险、localStorage 凭据回退 - **性能瓶颈**: 流式更新时重建整个消息数组、无界消息数组 - **架构问题**: 50+ lib 模块缺乏统一抽象、测试覆盖不足 ### 1.2 目标 - 在 14 周内完成全面架构优化 - 采用激进架构优先策略 - 重点优化四个核心系统:对话、Hands、Intelligence、技能 ### 1.3 关键决策 | 决策点 | 选择 | 理由 | |--------|------|------| | 状态管理 | VZustand | Proxy 细粒度响应,性能更好 | | 安全策略 | Web Worker 隔离 | 最安全的执行隔离方案 | | 整体方案 | A+C 混合 | 渐进式 + 领域驱动结合 | --- ## 2. 架构设计 ### 2.1 总体架构 ``` ┌─────────────────────────────────────────────────────────────────┐ │ UI Layer │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ React Components (60+) │ │ │ │ - 按领域组织 │ │ │ │ - 只负责展示和交互 │ │ │ └──────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ State Layer │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ VZustand Stores (基于 Proxy) │ │ │ │ - 细粒度响应 │ │ │ │ - 领域划分: Chat, Hands, Intelligence, Skills │ │ │ └──────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Client Layer │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │ │ Gateway Client │ │ Intelligence Clt │ │ Worker Pool │ │ │ │ (WebSocket) │ │ (Tauri Commands) │ │ (隔离执行) │ │ │ └──────────────────┘ └──────────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Backend Layer │ │ ┌──────────────────┐ ┌──────────────────────────────────────┐│ │ │ OpenFang Kernel │ │ Tauri Rust Backend ││ │ │ (Port 50051) │ │ - Intelligence (心跳/压缩/反思) ││ │ │ │ │ - Memory (SQLite 持久化) ││ │ │ │ │ - Browser (WebDriver) ││ │ └──────────────────┘ └──────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.2 领域划分 ``` desktop/src/ ├── domains/ # 领域模块 (新建) │ ├── chat/ # 对话系统 │ │ ├── store.ts # VZustand 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 组件 (保持) ``` --- ## 3. 核心组件设计 ### 3.1 VZustand 状态管理 **问题**: 当前 Zustand 每次更新都会触发整个订阅组件重渲染 **解决方案**: 使用 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; } ``` **预期收益**: - 流式更新时性能提升 70% - 代码更简洁 (直接 mutate) - 选择性渲染减少不必要的重渲染 ### 3.2 Web Worker 隔离执行 **问题**: browser.eval() 在主线程执行用户输入,存在 XSS 风险 **解决方案**: Web Worker 完全隔离 ```typescript // workers/pool.ts export class BrowserWorkerPool { private workers: Worker[] = []; private maxWorkers = 4; async execute(script: string, args: unknown[]): Promise { const worker = this.getAvailableWorker(); return new Promise((resolve, reject) => { const timeout = setTimeout(() => { worker.terminate(); this.recycleWorker(worker); reject(new Error('Execution timeout (30s)')); }, 30000); worker.onmessage = (e) => { clearTimeout(timeout); if (e.data.error) { reject(new ExecutionError(e.data.error)); } else { resolve(e.data.result); } }; worker.onerror = (e) => { clearTimeout(timeout); reject(new ExecutionError(e.message)); }; worker.postMessage({ type: 'eval', script, args }); }); } private getAvailableWorker(): Worker { // 复用或创建新 Worker if (this.workers.length < this.maxWorkers) { const worker = new Worker(new URL('./browser-worker.ts', import.meta.url)); this.workers.push(worker); return worker; } // 等待可用 Worker... } } // workers/browser-worker.ts const ALLOWED_SCRIPTS = new Set([ 'navigate', 'click', 'type', 'screenshot', 'extract', 'scroll', 'wait', 'select', 'hover' ]); self.onmessage = async (e) => { const { type, script, args } = e.data; if (type === 'eval') { try { if (!ALLOWED_SCRIPTS.has(script)) { throw new Error(`Script not allowed: ${script}`); } // 在受限环境中执行 const result = await executeInSandbox(script, args); self.postMessage({ result }); } catch (error) { self.postMessage({ error: error.message }); } } }; async function executeInSandbox(script: string, args: unknown[]): Promise { // 无 DOM 访问 // 无 localStorage 访问 // 只有受限的 API // ... } ``` **安全保证**: - Worker 无法访问 DOM - Worker 无法访问 localStorage/cookie - 脚本白名单限制 - 执行超时保护 - 错误隔离 ### 3.3 Hands 状态机 **问题**: 当前 HandStore 状态转换不清晰,难以追踪 **解决方案**: 使用 XState 实现状态机 ```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', }, }, }, }); ``` ### 3.4 Intelligence 缓存策略 **问题**: 每次请求都需要调用 Rust 后端 **解决方案**: 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 凭据存储加密 ```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/executor.test.ts describe('HandExecutor', () => { let pool: BrowserWorkerPool; beforeEach(() => { pool = new BrowserWorkerPool(); }); afterEach(async () => { await pool.terminateAll(); }); it('should execute allowed script', async () => { const result = await pool.execute('navigate', ['https://example.com']); expect(result).toBeDefined(); }); it('should reject disallowed script', async () => { await expect(pool.execute('eval', ['alert(1)'])) .rejects.toThrow('Script not allowed'); }); it('should timeout long execution', async () => { await expect(pool.execute('infiniteLoop', [])) .rejects.toThrow('Execution timeout'); }, 35000); }); ``` ### 5.3 E2E 测试场景 1. **聊天流程**: 发送消息 → 接收流式响应 → 显示完整消息 2. **Hands 触发**: 触发 Hand → 审批流程 → 执行 → 结果显示 3. **Intelligence**: 记忆提取 → 心跳触发 → 反思生成 4. **技能搜索**: 输入关键词 → 搜索技能 → 查看详情 --- ## 6. 实施计划 ### 6.1 Phase 1: 安全 + 测试 (2周) **目标**: 建立安全基础和测试框架 **任务**: - [ ] 实现 Web Worker 隔离执行引擎 - [ ] 实现凭据加密存储 - [ ] 强制 WSS 连接 - [ ] 建立测试框架 (Vitest + Playwright) - [ ] 设置覆盖率门禁 (60%) - [ ] 添加 chatStore 基础测试 **交付物**: - `workers/browser-worker.ts` - `workers/pool.ts` - `shared/secure-storage.ts` - 测试配置文件 ### 6.2 Phase 2: 领域重组 (4周) **目标**: 按领域重组代码,迁移到 VZustand **任务**: - [ ] 创建 domains/ 目录结构 - [ ] 迁移 Chat Store 到 VZustand - [ ] 迁移 Hands Store + 状态机 - [ ] 迁移 Intelligence Client - [ ] 迁移 Skills Store - [ ] 提取共享模块 - [ ] 更新导入路径 **交付物**: - `domains/chat/` - `domains/hands/` - `domains/intelligence/` - `domains/skills/` - `shared/` ### 6.3 Phase 3: 核心优化 (6周,并行) **Track A: Chat 优化** - [ ] VZustand 性能优化 - [ ] 虚拟滚动实现 - [ ] 消息分页 - [ ] 流式响应 AsyncGenerator **Track B: Hands 优化** - [ ] XState 状态机完善 - [ ] 审批流程配置化 - [ ] Worker 隔离集成 - [ ] 错误恢复机制 **Track C: Intelligence 优化** - [ ] Rust 后端功能完善 - [ ] LRU 缓存实现 - [ ] 向量检索准备 - [ ] 性能调优 ### 6.4 Phase 4: 集成 + 清理 (2周) **目标**: 完成集成,清理旧代码 **任务**: - [ ] 跨领域集成测试 - [ ] E2E 测试完善 - [ ] 清理旧代码 - [ ] 更新文档 - [ ] 性能基准测试 **交付物**: - 完整的测试套件 - 更新的文档 - 性能报告 --- ## 7. 验收标准 ### 7.1 功能验收 - [ ] 所有现有功能正常工作 - [ ] 无回归问题 - [ ] 新安全功能有效 ### 7.2 性能验收 | 指标 | 当前 | 目标 | |------|------|------| | 首屏加载 | ~3s | <2s | | 消息渲染 | 卡顿 | 60fps | | 1000+ 消息滚动 | 卡顿 | 流畅 | | 内存占用 | 无界 | <500MB | | Worker 执行延迟 | N/A | <100ms | ### 7.3 安全验收 - [ ] XSS 攻击测试通过 - [ ] 凭据存储安全审计通过 - [ ] WebSocket 安全测试通过 - [ ] 依赖安全扫描通过 ### 7.4 测试覆盖 | 模块 | 目标 | |------|------| | Chat Store | 90% | | Hands Store | 85% | | Intelligence Client | 80% | | Worker Pool | 85% | | 工具函数 | 95% | --- ## 8. 风险与缓解 | 风险 | 概率 | 影响 | 缓解措施 | |------|------|------|----------| | VZustand 学习曲线 | 中 | 中 | 提前 POC,编写示例 | | Worker 兼容性 | 低 | 高 | 渐进增强,主线程回退 | | 迁移导致回归 | 中 | 高 | 每阶段充分测试 | | 进度延期 | 中 | 中 | 预留 20% buffer | | 团队不熟悉 | 中 | 中 | 培训 + 文档 | --- ## 9. 附录 ### 9.1 技术依赖 ```json { "dependencies": { "valtio": "^2.0.0", "xstate": "^5.0.0", "react-window": "^2.2.7" }, "devDependencies": { "vitest": "^1.0.0", "@playwright/test": "^1.40.0", "@testing-library/react": "^14.0.0" } } ``` ### 9.2 参考文档 - [VZustand 文档](https://valtio.pmnd.rs/) - [XState 文档](https://xstate.js.org/) - [Web Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) - [Tauri 安全最佳实践](https://tauri.app/v2/guides/security/) --- *文档版本: 1.0 | 最后更新: 2026-03-21*