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
重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括: - 配置文件中的项目名称 - 代码注释和文档引用 - 环境变量和路径 - 类型定义和接口名称 - 测试用例和模拟数据 同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
23 KiB
23 KiB
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 测试场景
- 聊天流程: 发送消息 → 接收流式响应 → 显示完整消息
- Hands 触发: 触发 Hand → 审批流程 → 执行 → 结果显示
- Intelligence: 记忆提取 → 心跳触发 → 反思生成
- 技能搜索: 输入关键词 → 搜索技能 → 查看详情
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