docs(spec): revise architecture optimization spec based on review

Fixes from code review:
- Fix terminology: VZustand → Valtio
- Add terminology table for clarity
- Add existing code analysis section
- Add migration mapping table
- Remove incorrect Web Worker claims
- Update Hands section to focus on XState
- Update Intelligence cache to acknowledge existing impl
- Add detailed task breakdown with estimates
- Add performance measurement methods
- Update dependency versions to specific versions
- Add 2-week buffer to timeline

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-21 16:23:57 +08:00
parent 52c5e8a732
commit d1c200a243

View File

@@ -1,6 +1,6 @@
# ZCLAW 架构优化设计规格
> **版本**: 1.0
> **版本**: 1.1
> **日期**: 2026-03-21
> **状态**: 待审核
> **作者**: Claude + 用户协作
@@ -13,22 +13,30 @@
ZCLAW 是面向中文用户的 AI Agent 桌面客户端,经过分析发现以下需要改进的领域:
- **安全风险**: 浏览器 eval() XSS 风险、localStorage 凭据回退
- **安全风险**: localStorage 凭据回退明文存储、WebSocket 非 localhost 允许 ws://
- **性能瓶颈**: 流式更新时重建整个消息数组、无界消息数组
- **架构问题**: 50+ lib 模块缺乏统一抽象、测试覆盖不足
### 1.2 目标
- 在 14 周内完成全面架构优化
- 在 14 周内完成全面架构优化 (含 2 周缓冲)
- 采用激进架构优先策略
- 重点优化四个核心系统对话、Hands、Intelligence、技能
### 1.3 关键决策
### 1.3 术语表
| 术语 | 含义 | 备注 |
|------|------|------|
| **Valtio** | Proxy-based 状态管理库 | pmnd.rs/valtio将替代 Zustand |
| Zustand | 当前使用的状态管理库 | 将被 Valtio 替代 |
| XState | 状态机库 | 用于 Hands 状态管理 |
### 1.4 关键决策
| 决策点 | 选择 | 理由 |
|--------|------|------|
| 状态管理 | VZustand | Proxy 细粒度响应,性能更好 |
| 安全策略 | Web Worker 隔离 | 最安全的执行隔离方案 |
| 状态管理 | **Valtio** (替代 Zustand) | Proxy 细粒度响应,性能更好 |
| 安全策略 | 凭据加密存储 + WSS 强制 | 解决实际存在的安全风险 |
| 整体方案 | A+C 混合 | 渐进式 + 领域驱动结合 |
---
@@ -51,7 +59,7 @@ ZCLAW 是面向中文用户的 AI Agent 桌面客户端,经过分析发现以
┌─────────────────────────────────────────────────────────────────┐
│ State Layer │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ VZustand Stores (基于 Proxy) │ │
│ │ Valtio Stores (基于 Proxy) │ │
│ │ - 细粒度响应 │ │
│ │ - 领域划分: Chat, Hands, Intelligence, Skills │ │
│ └──────────────────────────────────────────────────────────┘ │
@@ -84,7 +92,7 @@ ZCLAW 是面向中文用户的 AI Agent 桌面客户端,经过分析发现以
desktop/src/
├── domains/ # 领域模块 (新建)
│ ├── chat/ # 对话系统
│ │ ├── store.ts # VZustand store
│ │ ├── store.ts # Valtio store
│ │ ├── types.ts # 类型定义
│ │ ├── api.ts # API 调用
│ │ └── hooks.ts # React hooks
@@ -111,15 +119,40 @@ desktop/src/
└── 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 VZustand 状态管理
### 3.1 Valtio 状态管理 (替代 Zustand)
**问题**: 当前 Zustand 每次更新都会触发整个订阅组件重渲染
**解决方案**: 使用 Proxy 实现细粒度响应
**解决方案**: 使用 Valtio 的 Proxy 实现细粒度响应
```typescript
// domains/chat/store.ts
@@ -174,106 +207,21 @@ function StreamingIndicator() {
}
```
**迁移策略**:
- 现有 `chatStore.ts` (689 行) 将被重写
- 保持相同的 API 接口,确保组件无需修改
- 逐步迁移,先迁移 Chat再迁移其他 Store
**预期收益**:
- 流式更新时性能提升 70%
- 代码更简洁 (直接 mutate)
- 选择性渲染减少不必要的重渲染
### 3.2 Web Worker 隔离执行
### 3.2 Hands 状态机 (XState)
**问题**: browser.eval() 在主线程执行用户输入,存在 XSS 风险
**问题**: 当前 `handStore.ts` (535 行) 状态转换逻辑分散,难以追踪
**解决方案**: 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 实现状态机
**解决方案**: 使用 XState 实现状态机,与现有 Store 集成
```typescript
// domains/hands/machine.ts
@@ -284,9 +232,7 @@ export const handMachine = createMachine({
initial: 'idle',
states: {
idle: {
on: {
TRIGGER: 'validating',
},
on: { TRIGGER: 'validating' },
},
validating: {
on: {
@@ -314,31 +260,44 @@ export const handMachine = createMachine({
TIMEOUT: 'error',
},
},
completed: {
on: {
RESET: 'idle',
},
},
error: {
on: {
RETRY: 'validating',
RESET: 'idle',
},
},
cancelled: {
on: {
RESET: 'idle',
},
},
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>
);
}
```
### 3.4 Intelligence 缓存策略
**迁移策略**:
- 现有 `handStore.ts` 保持向后兼容
- 新建 `domains/hands/` 模块
- 逐步将状态逻辑迁移到 XState
- 保持现有组件 API 不变
**问题**: 每次请求都需要调用 Rust 后端
### 3.3 Intelligence 缓存增强
**解决方案**: LRU 缓存 + TTL
**问题**: 现有 `intelligence-client.ts` (956 行) 已有 localStorage 回退缓存,但缺少 LRU 淘汰和 TTL
**解决方案**: 增强现有缓存层,添加 LRU + TTL
> **注意**: 这是增强现有实现,而非替换
```typescript
// domains/intelligence/cache.ts
@@ -391,7 +350,11 @@ export class IntelligenceCache {
## 4. 安全设计
### 4.1 凭据存储加密
### 4.1 凭据存储加密增强
> **现有实现**: `lib/secure-storage.ts` (350 行) 已使用 OS keyring + localStorage fallback
> **问题**: localStorage fallback 存储明文密钥,存在安全风险
> **策略**: 增强现有实现,为 localStorage fallback 添加加密
```typescript
// shared/secure-storage.ts
@@ -524,32 +487,28 @@ describe('ChatState', () => {
### 5.2 集成测试
```typescript
// domains/hands/executor.test.ts
describe('HandExecutor', () => {
let pool: BrowserWorkerPool;
beforeEach(() => {
pool = new BrowserWorkerPool();
// 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');
});
afterEach(async () => {
await pool.terminateAll();
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 execute allowed script', async () => {
const result = await pool.execute('navigate', ['https://example.com']);
expect(result).toBeDefined();
it('should cancel on REJECT', () => {
const service = interpret(handMachine).start();
// ... navigate to pending_approval
service.send('REJECT');
expect(service.state.value).toBe('cancelled');
});
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);
});
```
@@ -564,65 +523,101 @@ describe('HandExecutor', () => {
## 6. 实施计划
> **总周期**: 14 周 (含 2 周缓冲)
### 6.1 Phase 1: 安全 + 测试 (2周)
**目标**: 建立安全基础和测试框架
**任务**:
- [ ] 实现 Web Worker 隔离执行引擎
- [ ] 实现凭据加密存储
- [ ] 强制 WSS 连接
- [ ] 建立测试框架 (Vitest + Playwright)
- [ ] 设置覆盖率门禁 (60%)
- [ ] 添加 chatStore 基础测试
#### TASK-001: 凭据加密存储增强
- **输入条件**: 现有 `secure-storage.ts` 可用
- **输出产物**: 增强的 `shared/secure-storage.ts`
- **验收标准**:
- localStorage fallback 使用 AES-GCM 加密
- 所有单元测试通过
- 无回归问题
- **预估工时**: 3 人日
- **依赖**: 无
**交付物**:
- `workers/browser-worker.ts`
- `workers/pool.ts`
- `shared/secure-storage.ts`
- 测试配置文件
#### 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周)
**目标**: 按领域重组代码,迁移到 VZustand
**目标**: 按领域重组代码,迁移到 Valtio
**任务**:
- [ ] 创建 domains/ 目录结构
- [ ] 迁移 Chat Store 到 VZustand
- [ ] 迁移 Hands Store + 状态机
- [ ] 迁移 Intelligence Client
- [ ] 迁移 Skills Store
- [ ] 提取共享模块
- [ ] 更新导入路径
#### TASK-101: 创建 domains/ 目录结构
- **预估工时**: 1 人日
**交付物**:
- `domains/chat/`
- `domains/hands/`
- `domains/intelligence/`
- `domains/skills/`
- `shared/`
#### 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 优化**
- [ ] VZustand 性能优化
- [ ] 虚拟滚动实现
- [ ] 消息分页
**Track A: Chat 优化 (2人)**
- [ ] Valtio 性能优化
- [ ] 虚拟滚动实现 (react-window)
- [ ] 消息分页 + 惰性加载
- [ ] 流式响应 AsyncGenerator
**Track B: Hands 优化**
**Track B: Hands 优化 (1人)**
- [ ] XState 状态机完善
- [ ] 审批流程配置化
- [ ] Worker 隔离集成
- [ ] 错误恢复机制
**Track C: Intelligence 优化**
**Track C: Intelligence 优化 (1人)**
- [ ] Rust 后端功能完善
- [ ] LRU 缓存实现
- [ ] 向量检索准备
- [ ] LRU 缓存集成
- [ ] 性能调优
### 6.4 Phase 4: 集成 + 清理 (2周)
### 6.4 Phase 4: 集成 + 清理 (2周 = 1周实施 + 1周缓冲)
**目标**: 完成集成,清理旧代码
@@ -650,18 +645,20 @@ describe('HandExecutor', () => {
### 7.2 性能验收
| 指标 | 当前 | 目标 |
|------|------|------|
| 首屏加载 | ~3s | <2s |
| 消息渲染 | 卡顿 | 60fps |
| 1000+ 消息滚动 | 卡顿 | 流畅 |
| 内存占用 | 无界 | <500MB |
| Worker 执行延迟 | N/A | <100ms |
| 指标 | 当前 | 目标 | 测量方法 |
|------|------|------|----------|
| 首屏加载 | ~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 安全验收
- [ ] XSS 攻击测试通过
- [ ] 凭据存储安全审计通过
- [ ] localStorage 凭据已加密 (使用 AES-GCM)
- [ ] localhost 连接强制 WSS
- [ ] 依赖安全扫描通过 (npm audit)
- [ ] 密钥不在日志中输出
- [ ] WebSocket 安全测试通过
- [ ] 依赖安全扫描通过
@@ -681,10 +678,10 @@ describe('HandExecutor', () => {
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|----------|
| VZustand 学习曲线 | | | 提前 POC编写示例 |
| Worker 兼容性 | | | 渐进增强主线程回退 |
| Valtio 学习曲线 | | | 提前 POC编写示例 |
| XState 状态机复杂度 | | | 从简单场景开始逐步完善 |
| 迁移导致回归 | | | 每阶段充分测试 |
| 进度延期 | | | 预留 20% buffer |
| 进度延期 | | | 预留 2 周缓冲 |
| 团队不熟悉 | | | 培训 + 文档 |
---
@@ -696,25 +693,26 @@ describe('HandExecutor', () => {
```json
{
"dependencies": {
"valtio": "^2.0.0",
"xstate": "^5.0.0",
"react-window": "^2.2.7"
"valtio": "1.11.2",
"xstate": "5.18.2",
"@xstate/react": "4.1.3",
"react-window": "1.8.10"
},
"devDependencies": {
"vitest": "^1.0.0",
"@playwright/test": "^1.40.0",
"@testing-library/react": "^14.0.0"
"vitest": "2.1.8",
"@playwright/test": "1.49.1",
"@testing-library/react": "16.1.0"
}
}
```
### 9.2 参考文档
- [VZustand 文档](https://valtio.pmnd.rs/)
- [Valtio 文档](https://valtio.pmnd.rs/)
- [XState 文档](https://xstate.js.org/)
- [Web Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
- [React Window 文档](https://react-window.vercel.app/)
- [Tauri 安全最佳实践](https://tauri.app/v2/guides/security/)
---
*文档版本: 1.0 | 最后更新: 2026-03-21*
*文档版本: 1.1 | 最后更新: 2026-03-21*