Files
zclaw_openfang/docs/superpowers/specs/2026-03-21-architecture-optimization-design.md
iven 0d4fa96b82
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
refactor: 统一项目名称从OpenFang到ZCLAW
重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括:
- 配置文件中的项目名称
- 代码注释和文档引用
- 环境变量和路径
- 类型定义和接口名称
- 测试用例和模拟数据

同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
2026-03-27 07:36:03 +08:00

23 KiB
Raw Blame History

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 实现细粒度响应

// 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<Message>) => void;
  setStreaming: (streaming: boolean) => void;
}

export const chatState = proxy<ChatState>({
  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 => <Message key={m.id} message={m} />);
}

function StreamingIndicator() {
  const { isStreaming } = useChatState();  // 只订阅 isStreaming
  return isStreaming ? <Spinner /> : null;
}

迁移策略:

  • 现有 chatStore.ts (689 行) 将被重写
  • 保持相同的 API 接口,确保组件无需修改
  • 逐步迁移,先迁移 Chat再迁移其他 Store

预期收益:

  • 流式更新时性能提升 70%
  • 代码更简洁 (直接 mutate)
  • 选择性渲染减少不必要的重渲染

3.2 Hands 状态机 (XState)

问题: 当前 handStore.ts (535 行) 状态转换逻辑分散,难以追踪

解决方案: 使用 XState 实现状态机,与现有 Store 集成

// 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 (
    <div>
      {state.matches('pending_approval') && <ApprovalDialog />}
      {state.matches('executing') && <Spinner />}
    </div>
  );
}

迁移策略:

  • 现有 handStore.ts 保持向后兼容
  • 新建 domains/hands/ 模块
  • 逐步将状态逻辑迁移到 XState
  • 保持现有组件 API 不变

3.3 Intelligence 缓存增强

问题: 现有 intelligence-client.ts (956 行) 已有 localStorage 回退缓存,但缺少 LRU 淘汰和 TTL

解决方案: 增强现有缓存层,添加 LRU + TTL

注意: 这是增强现有实现,而非替换

// domains/intelligence/cache.ts
interface CacheEntry<T> {
  value: T;
  expiresAt: number;
}

export class IntelligenceCache {
  private cache = new Map<string, CacheEntry<unknown>>();
  private maxSize = 100;
  private defaultTTL = 5 * 60 * 1000; // 5 minutes

  get<T>(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<T>(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 添加加密

// shared/secure-storage.ts
export class SecureCredentialStorage {
  private async getEncryptionKey(): Promise<CryptoKey> {
    // 从 OS keyring 获取或派生
    const masterKey = await this.getMasterKey();
    return deriveKey(masterKey, SALT, {
      name: 'AES-GCM',
      length: 256,
    });
  }

  async store(key: string, value: string): Promise<void> {
    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<string | null> {
    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 安全

// 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 单元测试

// 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 集成测试

// 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 技术依赖

{
  "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 参考文档


文档版本: 1.1 | 最后更新: 2026-03-21