Files
zclaw_openfang/docs/superpowers/specs/2026-03-21-architecture-optimization-design.md
iven 52c5e8a732 docs(spec): add architecture optimization design spec
Comprehensive design for 14-week architecture overhaul:
- VZustand for fine-grained reactivity
- Web Worker isolation for security
- XState for Hands state machine
- Domain-driven directory structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 16:16:24 +08:00

721 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<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;
}
```
**预期收益**:
- 流式更新时性能提升 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<unknown> {
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<unknown> {
// 无 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<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 凭据存储加密
```typescript
// 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 安全
```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*