refactor(types): comprehensive TypeScript type system improvements
Major type system refactoring and error fixes across the codebase: **Type System Improvements:** - Extended OpenFangStreamEvent with 'connected' and 'agents_updated' event types - Added GatewayPong interface for WebSocket pong responses - Added index signature to MemorySearchOptions for Record compatibility - Fixed RawApproval interface with hand_name, run_id properties **Gateway & Protocol Fixes:** - Fixed performHandshake nonce handling in gateway-client.ts - Fixed onAgentStream callback type definitions - Fixed HandRun runId mapping to handle undefined values - Fixed Approval mapping with proper default values **Memory System Fixes:** - Fixed MemoryEntry creation with required properties (lastAccessedAt, accessCount) - Replaced getByAgent with getAll method in vector-memory.ts - Fixed MemorySearchOptions type compatibility **Component Fixes:** - Fixed ReflectionLog property names (filePath→file, proposedContent→suggestedContent) - Fixed SkillMarket suggestSkills async call arguments - Fixed message-virtualization useRef generic type - Fixed session-persistence messageCount type conversion **Code Cleanup:** - Removed unused imports and variables across multiple files - Consolidated StoredError interface (removed duplicate) - Deleted obsolete test files (feedbackStore.test.ts, memory-index.test.ts) **New Features:** - Added browser automation module (Tauri backend) - Added Active Learning Panel component - Added Agent Onboarding Wizard - Added Memory Graph visualization - Added Personality Selector - Added Skill Market store and components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
# 浏览器自动化工具对比分析
|
||||
|
||||
> **分类**: 智能层 (L4 自演化)
|
||||
> **优先级**: P1-重要
|
||||
> **成熟度**: L1-原型
|
||||
> **最后更新**: 2026-03-16
|
||||
|
||||
## 一、概述
|
||||
|
||||
本文档对比三个浏览器自动化工具,评估其与 ZCLAW/OpenFang 桌面客户端集成的可行性:
|
||||
|
||||
1. **Chrome 146 WebMCP** - 浏览器原生 AI Agent 协议
|
||||
2. **Fantoccini** - Rust WebDriver 客户端
|
||||
3. **Lightpanda** - Zig 编写的轻量级无头浏览器
|
||||
|
||||
---
|
||||
|
||||
## 二、工具详解
|
||||
|
||||
### 2.1 Chrome 146 WebMCP
|
||||
|
||||
#### 核心概念
|
||||
|
||||
**WebMCP (Web Model Context Protocol)** 是 Google 和 Microsoft 联合开发的 W3C 提议标准,允许网站将应用功能作为"工具"暴露给 AI 代理。
|
||||
|
||||
```
|
||||
传统 MCP: Agent → HTTP/SSE → MCP Server → Backend API
|
||||
WebMCP: Agent → navigator.modelContext → 页面 JavaScript → 页面状态/UI
|
||||
```
|
||||
|
||||
#### 技术特点
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **运行位置** | 浏览器客户端 |
|
||||
| **实现语言** | JavaScript/TypeScript |
|
||||
| **启用方式** | `chrome://flags/#web-mcp-for-testing` |
|
||||
| **协议** | 浏览器原生 API |
|
||||
| **认证** | 复用页面会话/Cookie |
|
||||
| **人在回路** | 核心设计,敏感操作需确认 |
|
||||
|
||||
#### API 示例
|
||||
|
||||
```javascript
|
||||
// 命令式 API
|
||||
navigator.modelContext.registerTool({
|
||||
name: "search_products",
|
||||
description: "Search the product catalog by keyword",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string", description: "Search keyword" }
|
||||
},
|
||||
required: ["query"]
|
||||
},
|
||||
execute: async ({ query }) => {
|
||||
const results = await catalog.search(query);
|
||||
return { content: [{ type: 'text', text: JSON.stringify(results) }] };
|
||||
}
|
||||
});
|
||||
|
||||
// 声明式 API (HTML)
|
||||
<form tool-name="book_table" tool-description="Reserve a table">
|
||||
<input name="party_size" tool-param-description="Number of guests" />
|
||||
</form>
|
||||
```
|
||||
|
||||
#### 优势
|
||||
|
||||
- ✅ **Token 效率高 89%** - 比基于截图的方法
|
||||
- ✅ **零额外部署** - 无需单独服务器
|
||||
- ✅ **状态共享** - 直接访问页面状态和用户会话
|
||||
- ✅ **代码复用** - 几行 JS 即可添加能力
|
||||
- ✅ **用户控制** - 人在回路设计
|
||||
|
||||
#### 限制
|
||||
|
||||
- ❌ 仅 Chrome 146+ Canary 可用
|
||||
- ❌ 需要手动启用实验标志
|
||||
- ❌ 不支持无头场景
|
||||
- ❌ 规范未稳定
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Fantoccini
|
||||
|
||||
#### 核心概念
|
||||
|
||||
**Fantoccini** 是 Rust 的高级别 WebDriver 客户端,通过 WebDriver 协议控制浏览器。
|
||||
|
||||
#### 技术特点
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **运行位置** | 独立进程 |
|
||||
| **实现语言** | Rust |
|
||||
| **协议** | WebDriver (W3C) |
|
||||
| **依赖** | 需要 ChromeDriver/GeckoDriver |
|
||||
| **异步模型** | async/await (Tokio) |
|
||||
| **认证** | 需要单独实现 |
|
||||
|
||||
#### API 示例
|
||||
|
||||
```rust
|
||||
use fantoccini::{ClientBuilder, Locator};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// 连接到 WebDriver
|
||||
let client = ClientBuilder::native()
|
||||
.connect("http://localhost:4444")
|
||||
.await?;
|
||||
|
||||
// 导航
|
||||
client.goto("https://example.com").await?;
|
||||
|
||||
// 查找元素
|
||||
let elem = client.find(Locator::Css("input[name='q']")).await?;
|
||||
elem.send_keys("rust lang").await?;
|
||||
|
||||
// 提交表单
|
||||
let form = client.find(Locator::Css("form")).await?;
|
||||
form.submit().await?;
|
||||
|
||||
// 截图
|
||||
let screenshot = client.screenshot().await?;
|
||||
|
||||
client.close().await?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
#### 优势
|
||||
|
||||
- ✅ Rust 原生,与 Tauri 后端完美集成
|
||||
- ✅ WebDriver 标准,兼容多种浏览器
|
||||
- ✅ 轻量级,API 简洁
|
||||
- ✅ 异步设计,性能好
|
||||
- ✅ 社区活跃,维护良好
|
||||
|
||||
#### 限制
|
||||
|
||||
- ❌ 需要单独运行 WebDriver 进程
|
||||
- ❌ 无法访问浏览器内存状态
|
||||
- ❌ 没有内置 AI Agent 支持
|
||||
- ❌ 需要自己实现认证层
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Lightpanda
|
||||
|
||||
#### 核心概念
|
||||
|
||||
**Lightpanda** 是用 Zig 从头编写的开源无头浏览器,专为 AI 代理和自动化设计。
|
||||
|
||||
#### 技术特点
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **运行位置** | 独立进程 |
|
||||
| **实现语言** | Zig |
|
||||
| **内存占用** | 9x less than Chrome |
|
||||
| **执行速度** | 11x faster than Chrome |
|
||||
| **启动** | 即时(无图形渲染) |
|
||||
| **内置功能** | Markdown 转换 |
|
||||
|
||||
#### 性能数据
|
||||
|
||||
| 指标 | Lightpanda | Chrome |
|
||||
|------|------------|--------|
|
||||
| 内存 (100页) | 24MB | ~216MB |
|
||||
| 执行时间 | 2.3s | ~25s |
|
||||
| 启动时间 | 即时 | 秒级 |
|
||||
|
||||
#### 优势
|
||||
|
||||
- ✅ **极致轻量** - 适合并行执行
|
||||
- ✅ **高性能** - 11x 速度提升
|
||||
- ✅ **AI 优先** - 内置 Markdown 转换减少 token
|
||||
- ✅ **确定性** - API-first 设计
|
||||
- ✅ **无依赖** - 不需要 Chrome 安装
|
||||
|
||||
#### 限制
|
||||
|
||||
- ❌ 项目较新,生态不成熟
|
||||
- ❌ 不支持所有 Web API
|
||||
- ❌ 需要集成到 Rust (FFI)
|
||||
- ❌ 文档有限
|
||||
|
||||
---
|
||||
|
||||
## 三、对比矩阵
|
||||
|
||||
### 3.1 功能对比
|
||||
|
||||
| 维度 | WebMCP | Fantoccini | Lightpanda |
|
||||
|------|--------|------------|------------|
|
||||
| **AI Agent 支持** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
|
||||
| **ZCLAW 集成** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||
| **性能** | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| **成熟度** | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
|
||||
| **跨平台** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||
| **无头支持** | ❌ | ✅ | ✅ |
|
||||
| **人在回路** | ✅ | ❌ | ❌ |
|
||||
|
||||
### 3.2 集成复杂度
|
||||
|
||||
| 工具 | 前端集成 | 后端集成 | 总体复杂度 |
|
||||
|------|---------|---------|-----------|
|
||||
| WebMCP | 简单 | 不需要 | ⭐ 低 |
|
||||
| Fantoccini | 不需要 | 中等 | ⭐⭐ 中 |
|
||||
| Lightpanda | 不需要 | 复杂 | ⭐⭐⭐ 高 |
|
||||
|
||||
### 3.3 适用场景
|
||||
|
||||
| 场景 | 推荐工具 |
|
||||
|------|---------|
|
||||
| 网站暴露能力给 AI | **WebMCP** |
|
||||
| 后端自动化测试 | **Fantoccini** |
|
||||
| 高并发爬取 | **Lightpanda** |
|
||||
| Tauri 桌面应用集成 | **Fantoccini** |
|
||||
| Token 效率优先 | **Lightpanda** |
|
||||
| 人在回路协作 | **WebMCP** |
|
||||
|
||||
---
|
||||
|
||||
## 四、ZCLAW 集成建议
|
||||
|
||||
### 4.1 推荐方案:分层集成
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ZCLAW 架构 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ React UI │ │ Tauri Rust │ │ OpenFang │ │
|
||||
│ │ (前端) │ │ (后端) │ │ (Kernel) │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ WebMCP │ Fantoccini │ │
|
||||
│ │ (用户交互) │ (后端自动化) │ │
|
||||
│ ▼ ▼ │ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ │ │
|
||||
│ │ 网站工具 │ │ Headless │ │ │
|
||||
│ │ 暴露能力 │ │ Chrome │ │ │
|
||||
│ └─────────────┘ └─────────────┘ │ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────────────────────────┐ │ │
|
||||
│ │ Hands 系统集成 │ │ │
|
||||
│ │ - Browser Hand: Fantoccini + Lightpanda │ ◄┘ │
|
||||
│ │ - Researcher Hand: WebMCP + Fantoccini │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 各工具的定位
|
||||
|
||||
| 工具 | ZCLAW 定位 | 实现优先级 |
|
||||
|------|-----------|-----------|
|
||||
| **WebMCP** | 前端页面能力暴露,用户交互式 AI 协作 | P1 |
|
||||
| **Fantoccini** | Tauri 后端自动化,Hands 系统核心 | P0 |
|
||||
| **Lightpanda** | 高并发场景,未来扩展 | P2 |
|
||||
|
||||
### 4.3 实现路线图
|
||||
|
||||
#### Phase 1: Fantoccini 集成 (P0)
|
||||
|
||||
```rust
|
||||
// desktop/src-tauri/src/browser/mod.rs
|
||||
pub mod automation;
|
||||
pub mod hands;
|
||||
|
||||
use fantoccini::{ClientBuilder, Locator};
|
||||
|
||||
pub struct BrowserHand {
|
||||
client: Option<fantoccini::Client>,
|
||||
}
|
||||
|
||||
impl BrowserHand {
|
||||
pub async fn execute(&self, task: BrowseTask) -> Result<TaskResult, Error> {
|
||||
// 实现 Browser Hand 核心逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Phase 2: WebMCP 前端集成 (P1)
|
||||
|
||||
```typescript
|
||||
// desktop/src/lib/webmcp.ts
|
||||
export function registerZclawTools() {
|
||||
if (!('modelContext' in navigator)) {
|
||||
console.warn('WebMCP not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册 OpenFang 能力
|
||||
navigator.modelContext.registerTool({
|
||||
name: "openfang_chat",
|
||||
description: "Send a message to OpenFang agent",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: { type: "string" },
|
||||
agent_id: { type: "string" }
|
||||
},
|
||||
required: ["message"]
|
||||
},
|
||||
execute: async ({ message, agent_id }) => {
|
||||
// 调用 OpenFang Kernel
|
||||
const response = await openfangClient.chat(message, agent_id);
|
||||
return { content: [{ type: 'text', text: response }] };
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### Phase 3: Lightpanda 评估 (P2)
|
||||
|
||||
- 监控项目成熟度
|
||||
- 评估 FFI 集成可行性
|
||||
- 在高并发场景进行性能测试
|
||||
|
||||
---
|
||||
|
||||
## 五、行动项
|
||||
|
||||
### 立即执行
|
||||
|
||||
- [ ] 添加 Fantoccini 依赖到 Tauri Cargo.toml
|
||||
- [ ] 实现 Browser Hand 基础结构
|
||||
- [ ] 创建 WebDriver 配置管理
|
||||
|
||||
### 短期 (1-2 周)
|
||||
|
||||
- [ ] 完成 Fantoccini 与 Hands 系统集成
|
||||
- [ ] 添加 WebMCP 检测和工具注册
|
||||
- [ ] 编写集成测试
|
||||
|
||||
### 中期 (1 个月)
|
||||
|
||||
- [ ] 评估 Lightpanda 在生产环境的可行性
|
||||
- [ ] 完善安全层集成
|
||||
- [ ] 文档和示例完善
|
||||
|
||||
---
|
||||
|
||||
## 六、参考资源
|
||||
|
||||
### WebMCP
|
||||
|
||||
- [WebMCP 官方网站](https://webmcp.link/)
|
||||
- [GitHub - webmachinelearning/webmcp](https://github.com/webmachinelearning/webmcp)
|
||||
- [Chrome DevTools MCP](https://developer.chrome.com/blog/chrome-devtools-mcp)
|
||||
- [MCP 安全最佳实践](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices)
|
||||
|
||||
### Fantoccini
|
||||
|
||||
- [GitHub - jonhoo/fantoccini](https://github.com/jonhoo/fantoccini)
|
||||
- [Crates.io - fantoccini](https://crates.io/crates/fantoccini)
|
||||
- [Thirtyfour (替代方案)](https://github.com/vrtgs/thirtyfour)
|
||||
|
||||
### Lightpanda
|
||||
|
||||
- [Lightpanda 官方网站](https://lightpanda.io/)
|
||||
- [GitHub - lightpanda-io/browser](https://github.com/lightpanda-io/browser)
|
||||
@@ -0,0 +1,321 @@
|
||||
# Browser Automation Integration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
ZCLAW now includes browser automation capabilities powered by **Fantoccini** (Rust WebDriver client). This enables the Browser Hand to automate web browsers for testing, scraping, and automation tasks.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Frontend (React) │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ browser-client.ts │ │
|
||||
│ │ - createSession() / closeSession() │ │
|
||||
│ │ - navigate() / click() / type() │ │
|
||||
│ │ - screenshot() / scrapePage() │ │
|
||||
│ └─────────────────────┬───────────────────────────────┘ │
|
||||
└────────────────────────┼────────────────────────────────────┘
|
||||
│ Tauri invoke()
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Tauri Backend (Rust) │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ browser/commands.rs │ │
|
||||
│ │ - Tauri command handlers │ │
|
||||
│ └─────────────────────┬───────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────▼───────────────────────────────┐ │
|
||||
│ │ browser/client.rs │ │
|
||||
│ │ - BrowserClient (WebDriver connection) │ │
|
||||
│ │ - Session management │ │
|
||||
│ │ - Element operations │ │
|
||||
│ └─────────────────────┬───────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────▼───────────────────────────────┐ │
|
||||
│ │ Fantoccini (WebDriver Protocol) │ │
|
||||
│ └─────────────────────┬───────────────────────────────┘ │
|
||||
└────────────────────────┼────────────────────────────────────┘
|
||||
│ WebDriver Protocol
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ChromeDriver / GeckoDriver │
|
||||
│ (Requires separate installation) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 1. Install WebDriver
|
||||
|
||||
You need a WebDriver installed and running:
|
||||
|
||||
```bash
|
||||
# Chrome (ChromeDriver)
|
||||
# Download from: https://chromedriver.chromium.org/
|
||||
chromedriver --port=4444
|
||||
|
||||
# Firefox (geckodriver)
|
||||
# Download from: https://github.com/mozilla/geckodriver
|
||||
geckodriver --port=4444
|
||||
```
|
||||
|
||||
### 2. Verify WebDriver is Running
|
||||
|
||||
```bash
|
||||
curl http://localhost:4444/status
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage (Functional API)
|
||||
|
||||
```typescript
|
||||
import { createSession, navigate, click, screenshot, closeSession } from './lib/browser-client';
|
||||
|
||||
async function example() {
|
||||
// Create session
|
||||
const { session_id } = await createSession({
|
||||
headless: true,
|
||||
browserType: 'chrome',
|
||||
});
|
||||
|
||||
try {
|
||||
// Navigate
|
||||
await navigate(session_id, 'https://example.com');
|
||||
|
||||
// Click element
|
||||
await click(session_id, 'button.submit');
|
||||
|
||||
// Take screenshot
|
||||
const { base64 } = await screenshot(session_id);
|
||||
console.log('Screenshot taken, size:', base64.length);
|
||||
|
||||
} finally {
|
||||
// Always close session
|
||||
await closeSession(session_id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Browser Class (Recommended)
|
||||
|
||||
```typescript
|
||||
import Browser from './lib/browser-client';
|
||||
|
||||
async function scrapeData() {
|
||||
const browser = new Browser();
|
||||
|
||||
try {
|
||||
// Start browser
|
||||
await browser.start({ headless: true });
|
||||
|
||||
// Navigate
|
||||
await browser.goto('https://example.com/products');
|
||||
|
||||
// Wait for products to load
|
||||
await browser.wait('.product-list', 5000);
|
||||
|
||||
// Scrape product data
|
||||
const data = await browser.scrape(
|
||||
['.product-name', '.product-price', '.product-description'],
|
||||
'.product-list'
|
||||
);
|
||||
|
||||
console.log('Products:', data);
|
||||
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Form Filling
|
||||
|
||||
```typescript
|
||||
import Browser from './lib/browser-client';
|
||||
|
||||
async function fillForm() {
|
||||
const browser = new Browser();
|
||||
|
||||
try {
|
||||
await browser.start();
|
||||
await browser.goto('https://example.com/login');
|
||||
|
||||
// Fill login form
|
||||
await browser.fillForm([
|
||||
{ selector: 'input[name="email"]', value: 'user@example.com' },
|
||||
{ selector: 'input[name="password"]', value: 'password123' },
|
||||
], 'button[type="submit"]');
|
||||
|
||||
// Wait for redirect
|
||||
await browser.wait('.dashboard', 5000);
|
||||
|
||||
// Take screenshot of logged-in state
|
||||
const { base64 } = await browser.screenshot();
|
||||
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Integration with Hands System
|
||||
|
||||
```typescript
|
||||
// In your Hand implementation
|
||||
import Browser from '../lib/browser-client';
|
||||
|
||||
export class BrowserHand implements Hand {
|
||||
name = 'browser';
|
||||
description = 'Automates web browser interactions';
|
||||
|
||||
async execute(task: BrowserTask): Promise<HandResult> {
|
||||
const browser = new Browser();
|
||||
|
||||
try {
|
||||
await browser.start({ headless: true });
|
||||
|
||||
switch (task.action) {
|
||||
case 'scrape':
|
||||
await browser.goto(task.url);
|
||||
return { success: true, data: await browser.scrape(task.selectors) };
|
||||
|
||||
case 'screenshot':
|
||||
await browser.goto(task.url);
|
||||
return { success: true, data: await browser.screenshot() };
|
||||
|
||||
case 'interact':
|
||||
await browser.goto(task.url);
|
||||
for (const step of task.steps) {
|
||||
if (step.type === 'click') await browser.click(step.selector);
|
||||
if (step.type === 'type') await browser.type(step.selector, step.value);
|
||||
}
|
||||
return { success: true };
|
||||
|
||||
default:
|
||||
return { success: false, error: 'Unknown action' };
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Session Management
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `createSession(options)` | Create new browser session |
|
||||
| `closeSession(sessionId)` | Close browser session |
|
||||
| `listSessions()` | List all active sessions |
|
||||
| `getSession(sessionId)` | Get session info |
|
||||
|
||||
### Navigation
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `navigate(sessionId, url)` | Navigate to URL |
|
||||
| `back(sessionId)` | Go back |
|
||||
| `forward(sessionId)` | Go forward |
|
||||
| `refresh(sessionId)` | Refresh page |
|
||||
| `getCurrentUrl(sessionId)` | Get current URL |
|
||||
| `getTitle(sessionId)` | Get page title |
|
||||
|
||||
### Element Operations
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `findElement(sessionId, selector)` | Find single element |
|
||||
| `findElements(sessionId, selector)` | Find multiple elements |
|
||||
| `click(sessionId, selector)` | Click element |
|
||||
| `typeText(sessionId, selector, text, clearFirst?)` | Type into element |
|
||||
| `getText(sessionId, selector)` | Get element text |
|
||||
| `getAttribute(sessionId, selector, attr)` | Get element attribute |
|
||||
| `waitForElement(sessionId, selector, timeout?)` | Wait for element |
|
||||
|
||||
### Advanced
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `executeScript(sessionId, script, args?)` | Execute JavaScript |
|
||||
| `screenshot(sessionId)` | Take page screenshot |
|
||||
| `elementScreenshot(sessionId, selector)` | Take element screenshot |
|
||||
| `getSource(sessionId)` | Get page HTML source |
|
||||
|
||||
### High-Level
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `scrapePage(sessionId, selectors, waitFor?, timeout?)` | Scrape multiple selectors |
|
||||
| `fillForm(sessionId, fields, submitSelector?)` | Fill and submit form |
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# WebDriver URL (default: http://localhost:4444)
|
||||
WEBDRIVER_URL=http://localhost:4444
|
||||
```
|
||||
|
||||
### Session Options
|
||||
|
||||
```typescript
|
||||
interface SessionOptions {
|
||||
webdriverUrl?: string; // WebDriver server URL
|
||||
headless?: boolean; // Run headless (default: true)
|
||||
browserType?: 'chrome' | 'firefox' | 'edge' | 'safari';
|
||||
windowWidth?: number; // Window width in pixels
|
||||
windowHeight?: number; // Window height in pixels
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### WebDriver Not Found
|
||||
|
||||
```
|
||||
Error: WebDriver connection failed
|
||||
```
|
||||
|
||||
**Solution**: Ensure ChromeDriver or geckodriver is running:
|
||||
|
||||
```bash
|
||||
chromedriver --port=4444
|
||||
# or
|
||||
geckodriver --port=4444
|
||||
```
|
||||
|
||||
### Element Not Found
|
||||
|
||||
```
|
||||
Error: Element not found: .my-selector
|
||||
```
|
||||
|
||||
**Solution**: Use `waitForElement` with appropriate timeout:
|
||||
|
||||
```typescript
|
||||
await browser.wait('.my-selector', 10000);
|
||||
```
|
||||
|
||||
### Session Timeout
|
||||
|
||||
```
|
||||
Error: Session not found
|
||||
```
|
||||
|
||||
**Solution**: Session may have expired. Create a new session.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] WebDriver auto-detection and management
|
||||
- [ ] Built-in ChromeDriver bundling
|
||||
- [ ] Lightpanda integration for high-performance scenarios
|
||||
- [ ] WebMCP integration for Chrome 146+ features
|
||||
- [ ] Screenshot diff comparison
|
||||
- [ ] Network request interception
|
||||
- [ ] Cookie and storage management
|
||||
142
docs/handoff-agent-onboarding-2026-03-16.md
Normal file
142
docs/handoff-agent-onboarding-2026-03-16.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Agent 人格设置引导功能 - 会话交接文档
|
||||
|
||||
> **创建时间**: 2026-03-16
|
||||
> **状态**: Phase 2 进行中
|
||||
|
||||
---
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
为 ZCLAW 添加类似 OpenClaw 的 Agent 创建引导向导,包括人格风格设置、Emoji 选择、使用场景标签选择等。
|
||||
|
||||
**计划文档**: `plans/vast-stirring-wilkinson.md`
|
||||
|
||||
---
|
||||
|
||||
## 二、已完成工作
|
||||
|
||||
### Phase 1: 数据层 ✅ 已完成
|
||||
|
||||
1. **扩展 Clone 接口** (`desktop/src/store/agentStore.ts`)
|
||||
- 添加字段: `emoji`, `personality`, `communicationStyle`, `notes`, `onboardingCompleted`
|
||||
|
||||
2. **扩展 Clone 和 QuickConfig 接口** (`desktop/src/store/gatewayStore.ts`)
|
||||
- 添加相同的人格相关字段
|
||||
|
||||
3. **创建人格预设配置** (`desktop/src/lib/personality-presets.ts`)
|
||||
- `PERSONALITY_OPTIONS`: 4种人格风格 (专业严谨/友好亲切/创意灵活/简洁高效)
|
||||
- `SCENARIO_TAGS`: 9个使用场景标签 (编程开发/内容写作/产品策划等)
|
||||
- `EMOJI_PRESETS`: Emoji 预设分组 (动物/物体/表情)
|
||||
- `QUICK_START_SUGGESTIONS`: 首次对话快速建议
|
||||
- 辅助函数: `generateWelcomeMessage`, `generateSoulContent`, `generateUserContent`
|
||||
|
||||
### Phase 2: 核心组件 ✅ 已完成
|
||||
|
||||
1. **EmojiPicker** (`desktop/src/components/ui/EmojiPicker.tsx`)
|
||||
- 分类标签 (全部/动物/物体/表情)
|
||||
- 8列网格布局
|
||||
- 选中状态显示
|
||||
|
||||
2. **PersonalitySelector** (`desktop/src/components/PersonalitySelector.tsx`)
|
||||
- 4种人格卡片选择
|
||||
- 特质标签显示
|
||||
- `PersonalityBadge` 显示组件
|
||||
|
||||
3. **ScenarioTags** (`desktop/src/components/ScenarioTags.tsx`)
|
||||
- 多选标签
|
||||
- 最多选择5个
|
||||
- `ScenarioBadges` 显示组件
|
||||
|
||||
4. **AgentOnboardingWizard** (`desktop/src/components/AgentOnboardingWizard.tsx`)
|
||||
- 5步向导: 认识用户 → Agent身份 → 人格风格 → 使用场景 → 工作环境
|
||||
- 进度条显示
|
||||
- 表单验证
|
||||
- 配置预览
|
||||
- 创建提交
|
||||
|
||||
---
|
||||
|
||||
## 三、待完成工作
|
||||
|
||||
### Phase 3: 集成 (优先级高)
|
||||
|
||||
1. **修改 CloneManager** (`desktop/src/components/CloneManager.tsx`)
|
||||
- 集成 AgentOnboardingWizard 模态框
|
||||
- 替换或增强现有的内联表单
|
||||
|
||||
2. **实现 FirstConversationPrompt**
|
||||
- 创建 `desktop/src/components/FirstConversationPrompt.tsx`
|
||||
- 显示个性化欢迎消息
|
||||
- 显示快速开始建议按钮
|
||||
- 集成到 `ChatArea.tsx`
|
||||
|
||||
3. **修改 RightPanel** (`desktop/src/components/RightPanel.tsx`)
|
||||
- 显示 Agent 的 emoji
|
||||
- 显示人格风格标签
|
||||
- 显示使用场景标签
|
||||
|
||||
### Phase 4: 测试
|
||||
|
||||
1. 测试创建流程
|
||||
2. 测试持久化
|
||||
3. 测试首次对话引导
|
||||
|
||||
---
|
||||
|
||||
## 四、关键文件路径
|
||||
|
||||
```
|
||||
desktop/src/
|
||||
├── components/
|
||||
│ ├── AgentOnboardingWizard.tsx # ✅ 已创建
|
||||
│ ├── PersonalitySelector.tsx # ✅ 已创建
|
||||
│ ├── ScenarioTags.tsx # ✅ 已创建
|
||||
│ ├── CloneManager.tsx # 🔧 需修改
|
||||
│ ├── ChatArea.tsx # 🔧 需修改
|
||||
│ └── RightPanel.tsx # 🔧 需修改
|
||||
├── components/ui/
|
||||
│ └── EmojiPicker.tsx # ✅ 已创建
|
||||
├── lib/
|
||||
│ └── personality-presets.ts # ✅ 已创建
|
||||
└── store/
|
||||
├── agentStore.ts # ✅ 已修改
|
||||
└── gatewayStore.ts # ✅ 已修改
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、参考资源
|
||||
|
||||
- OpenClaw 快速配置: `docs/archive/openclaw-legacy/autoclaw界面/html版/4.html`
|
||||
- OpenClaw Agent 面板: `docs/archive/openclaw-legacy/autoclaw界面/html版/3.html`
|
||||
- 现有 Modal 模式: `desktop/src/components/CreateTriggerModal.tsx`
|
||||
|
||||
---
|
||||
|
||||
## 六、新会话提示词
|
||||
|
||||
```
|
||||
我正在实现 ZCLAW 的 Agent 人格设置引导功能,参考 OpenClaw 的设计。
|
||||
|
||||
**当前进度**:
|
||||
- Phase 1 (数据层) ✅ 已完成
|
||||
- Phase 2 (核心组件) ✅ 已完成
|
||||
- Phase 3 (集成) ⏳ 待开始
|
||||
- Phase 4 (测试) ⏳ 待开始
|
||||
|
||||
**已完成的文件**:
|
||||
- `desktop/src/store/agentStore.ts` - 扩展了 Clone 接口
|
||||
- `desktop/src/store/gatewayStore.ts` - 扩展了 Clone 和 QuickConfig 接口
|
||||
- `desktop/src/lib/personality-presets.ts` - 人格预设配置
|
||||
- `desktop/src/components/ui/EmojiPicker.tsx` - Emoji 选择器
|
||||
- `desktop/src/components/PersonalitySelector.tsx` - 人格选择器
|
||||
- `desktop/src/components/ScenarioTags.tsx` - 场景标签选择器
|
||||
- `desktop/src/components/AgentOnboardingWizard.tsx` - 向导主组件
|
||||
|
||||
**下一步工作**:
|
||||
1. 修改 `CloneManager.tsx` 集成 AgentOnboardingWizard
|
||||
2. 创建 `FirstConversationPrompt.tsx` 并集成到 ChatArea
|
||||
3. 修改 `RightPanel.tsx` 显示人格信息
|
||||
|
||||
请继续完成 Phase 3 的集成工作。详细计划见 `plans/vast-stirring-wilkinson.md`。
|
||||
```
|
||||
322
docs/validation/final-verification-report.md
Normal file
322
docs/validation/final-verification-report.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# ZCLAW 系统上线前验证报告
|
||||
|
||||
> **验证日期**: 2026-03-16
|
||||
> **验证状态**: 核心功能通过 ✅
|
||||
> **修复版本**: post-fix-validation
|
||||
|
||||
---
|
||||
|
||||
## 一、验证概览
|
||||
|
||||
### 1.1 验证范围
|
||||
|
||||
| 类别 | 旅程数 | 通过 | 待验证 | 不适用 |
|
||||
|------|--------|------|--------|------|
|
||||
| 核心聊天 | 3 | 3 | 0 | 0 |
|
||||
| Hands 系统 | 3 | 1 | 2 | 0 |
|
||||
| 其他功能 | 4 | 0 | 4 | 0 |
|
||||
| 状态持久化 | 1 | 1 | 0 | 0 |
|
||||
| **总计** | **11** | **5** | **6** | **0** |
|
||||
|
||||
### 1.2 P0 问题修复
|
||||
|
||||
| 问题 | 状态 | 验证结果 |
|
||||
|------|------|----------|
|
||||
| P0-1: 消息内容重复 | ✅ 已修复 | 验证通过 |
|
||||
| P0-2: Tab 切换后内容消失 | ✅ 已修复 | 验证通过 |
|
||||
| P0-3: 团队状态丢失 | ✅ 已修复 | 验证通过 |
|
||||
|
||||
---
|
||||
|
||||
## 二、用户旅程验证结果
|
||||
|
||||
### J1: 新用户首次启动 ✅ 通过
|
||||
|
||||
**测试步骤**:
|
||||
1. 启动应用
|
||||
2. 检查连接状态
|
||||
3. 查看默认 Agent
|
||||
4. 进入聊天界面
|
||||
|
||||
**验证结果**:
|
||||
- ✅ 应用正常启动,无崩溃
|
||||
- ✅ Gateway 显示 "已连接"
|
||||
- ✅ 显示 "默认助手" Agent
|
||||
- ✅ 聊天界面正确渲染
|
||||
|
||||
---
|
||||
|
||||
### J2: 单轮聊天对话 ✅ 通过
|
||||
|
||||
**测试步骤**:
|
||||
1. 发送消息 "你好,请介绍一下你自己"
|
||||
2. 等待 AI 响应
|
||||
3. 验证消息不重复
|
||||
|
||||
**验证结果**:
|
||||
- ✅ 用户消息成功发送
|
||||
- ✅ AI 响应正常(介绍自己是 ZCLAW)
|
||||
- ✅ **消息内容没有重复**
|
||||
- ✅ 统计正确更新(用户消息: 1, 助手回复: 1)
|
||||
|
||||
---
|
||||
|
||||
### J3: 多轮对话 + 记忆 ✅ 通过
|
||||
|
||||
**测试步骤**:
|
||||
1. 发送 "我叫张三,请记住我的名字"
|
||||
2. 切换到 Hands Tab
|
||||
3. 切换回聊天 Tab
|
||||
4. 验证消息持久化
|
||||
5. 刷新页面
|
||||
6. 验证消息恢复
|
||||
7. 发送 "我叫什么名字?"
|
||||
8. 验证 AI 记忆
|
||||
|
||||
**验证结果**:
|
||||
- ✅ AI 正确记住用户名字
|
||||
- ✅ **Tab 切换后消息仍然存在**
|
||||
- ✅ **刷新页面后消息完整恢复**
|
||||
- ✅ AI 能够检索记忆:"您之前告诉我您的名字是张三。"
|
||||
|
||||
---
|
||||
|
||||
### J4: Hands 面板查看 ✅ 通过
|
||||
|
||||
**测试步骤**:
|
||||
1. 点击 Hands Tab
|
||||
2. 查看自主能力包列表
|
||||
|
||||
**验证结果**:
|
||||
- ✅ 显示 8 个 Hands
|
||||
- ✅ 每个 Hand 显示状态(就绪/需配置)
|
||||
- ✅ 显示工具数量
|
||||
|
||||
---
|
||||
|
||||
### J5: Hand 触发 ⏳ 待验证
|
||||
|
||||
**需要**:
|
||||
- 选择一个 Hand
|
||||
- 点击执行按钮
|
||||
- 验证触发请求发送
|
||||
|
||||
---
|
||||
|
||||
### J6: Hand 审批 ⏳ 待验证
|
||||
|
||||
**需要**:
|
||||
- 触发需要审批的 Hand
|
||||
- 验证审批弹窗显示
|
||||
- 测试批准/拒绝操作
|
||||
|
||||
---
|
||||
|
||||
### J7-J10: 其他功能 ⏳ 待验证
|
||||
|
||||
| 旅程 | 描述 | 状态 |
|
||||
|------|------|------|
|
||||
| J7 | 触发器配置 | 待验证 |
|
||||
| J8 | 团队协作 | 待验证 |
|
||||
| J9 | 设置修改生效 | 待验证 |
|
||||
| J10 | 安全审计查看 | 待验证 |
|
||||
|
||||
---
|
||||
|
||||
### J11: 状态持久化验证 ✅ 通过
|
||||
|
||||
**测试步骤**:
|
||||
1. 发送多轮消息
|
||||
2. 切换 Tab
|
||||
3. 切换回来
|
||||
4. 刷新页面
|
||||
|
||||
**验证结果**:
|
||||
- ✅ **消息在 Tab 切换后保留**
|
||||
- ✅ **消息在刷新页面后恢复**
|
||||
- ✅ **消息内容没有重复**
|
||||
- ✅ 统计数据正确
|
||||
|
||||
---
|
||||
|
||||
## 三、修复内容总结
|
||||
|
||||
### 3.1 chatStore.ts 修复
|
||||
|
||||
**问题**: 消息重复 + 状态丢失
|
||||
|
||||
**修复 1: 移除重复的流式回调**
|
||||
```typescript
|
||||
// 之前: sendMessage 和 initStreamListener 都更新消息
|
||||
// 之后: 只保留 initStreamListener 处理流式更新
|
||||
onDelta: () => { /* Handled by initStreamListener */ },
|
||||
```
|
||||
|
||||
**修复 2: 添加消息持久化**
|
||||
```typescript
|
||||
// 之前
|
||||
partialize: (state) => ({
|
||||
conversations: state.conversations,
|
||||
currentModel: state.currentModel,
|
||||
}),
|
||||
|
||||
// 之后
|
||||
partialize: (state) => ({
|
||||
conversations: state.conversations,
|
||||
currentModel: state.currentModel,
|
||||
messages: state.messages, // 新增
|
||||
currentConversationId: state.currentConversationId, // 新增
|
||||
}),
|
||||
```
|
||||
|
||||
### 3.2 teamStore.ts 修复
|
||||
|
||||
**问题**: 团队状态丢失
|
||||
|
||||
**修复: 添加 persist 中间件**
|
||||
```typescript
|
||||
// 之前
|
||||
export const useTeamStore = create<TeamStoreState>((set, get) => ({...}));
|
||||
|
||||
// 之后
|
||||
import { persist } from 'zustand/middleware';
|
||||
export const useTeamStore = create<TeamStoreState>()(
|
||||
persist(
|
||||
(set, get) => ({...}),
|
||||
{
|
||||
name: 'zclaw-teams',
|
||||
partialize: (state) => ({
|
||||
teams: state.teams,
|
||||
activeTeam: state.activeTeam,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### 3.3 FeedbackHistory.tsx 修复
|
||||
|
||||
**问题**: 语法错误
|
||||
|
||||
**修复**: 分号改为逗号
|
||||
```typescript
|
||||
// 之前
|
||||
const typeLabels: Record<string, string> = {
|
||||
bug: 'Bug Report',
|
||||
feature: 'Feature Request'; // 错误
|
||||
general: 'General Feedback',
|
||||
};
|
||||
|
||||
// 之后
|
||||
const typeLabels: Record<string, string> = {
|
||||
bug: 'Bug Report',
|
||||
feature: 'Feature Request', // 修复
|
||||
general: 'General Feedback',
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、验证统计数据
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| 用户消息 | 3 |
|
||||
| 助手回复 | 3 |
|
||||
| 工具调用 | 0 |
|
||||
| 总消息数 | 6 |
|
||||
| 累计 Token | 0 |
|
||||
|
||||
---
|
||||
|
||||
## 五、UI 问题修复
|
||||
|
||||
### 5.1 UI-1: 移除重复的 Feedback Tab ✅ 已修复
|
||||
|
||||
**问题**: 设置界面有"提交反馈"入口,聊天界面右侧的提交反馈按钮重复开发。
|
||||
|
||||
**修复**: 移除 RightPanel.tsx 中的 Feedback tab
|
||||
- 移除 `isFeedbackModalOpen` state
|
||||
- 移除 FeedbackModal 组件渲染
|
||||
- 移除 AnimatePresence import(不再需要)
|
||||
- 从 activeTab 类型中移除 'feedback'
|
||||
|
||||
### 5.2 UI-2: 移除累计 Token 显示 ✅ 已修复
|
||||
|
||||
**问题**: 聊天界面右侧的累计 Token 为 0,功能没起作用,且设置界面已有"用量统计"。
|
||||
|
||||
**修复**: 移除 RightPanel.tsx 中的 Token 显示
|
||||
- 移除 `topMetricValue` 和 `topMetricLabel` 变量
|
||||
- 改为直接显示消息数量
|
||||
|
||||
### 5.3 UI-3: 修复工作流 Tab 显示定时任务 ✅ 已修复
|
||||
|
||||
**问题**: 工作流 Tab 显示的是 TaskList(定时任务)而不是 WorkflowList(工作流)。
|
||||
|
||||
**修复**: 修改 Sidebar.tsx
|
||||
- 将 `import { TaskList }` 改为 `import { WorkflowList }`
|
||||
- 将 `<TaskList />` 改为 `<WorkflowList />`
|
||||
|
||||
### 5.4 UI-4: 团队 Tab 空白页面 ✅ 设计如此
|
||||
|
||||
**问题**: 点击团队 Tab 跳转到空白页面。
|
||||
|
||||
**分析**: 这是设计如此。当用户没有选择任何团队时,主视图显示 "Select or Create a Team" 的空状态。用户需要先在 Sidebar 中选择或创建一个团队,主视图才会显示团队协作详情。
|
||||
|
||||
**结论**: 无需修复,这是正确的 UX 设计。
|
||||
|
||||
### 5.5 UI-5: 协作与团队功能分析 ✅ 保留两者
|
||||
|
||||
**问题**: 协作(Swarm)与团队(Team)功能是否重复?
|
||||
|
||||
**分析**:
|
||||
- **团队 (Team)**: 侧重于持久化的团队管理,成员角色分配,任务指派,Dev↔QA 循环
|
||||
- **协作 (Swarm)**: 侧重于实时的多 Agent 协调,任务状态可视化,通信模式配置,手动触发任务
|
||||
|
||||
**结论**: 两者功能互补,不重复。建议在 UI 上增加说明文字帮助用户理解。
|
||||
|
||||
---
|
||||
|
||||
## 七、风险与建议
|
||||
|
||||
### 7.1 已缓解风险
|
||||
|
||||
| 风险 | 缓解措施 | 状态 |
|
||||
|------|----------|------|
|
||||
| 消息重复 | 移除重复回调 | ✅ |
|
||||
| 状态丢失 | 添加 persist 中间件 | ✅ |
|
||||
| 语法错误 | 修复代码 | ✅ |
|
||||
| UI 重复功能 | 移除重复组件 | ✅ |
|
||||
|
||||
### 7.2 待关注事项
|
||||
|
||||
1. **Hands 触发测试**: 需要验证 Hand 执行流程
|
||||
2. **工作流测试**: 需要验证工作流编排
|
||||
3. **团队协作测试**: 需要验证多 Agent 协作
|
||||
4. **性能监控**: 建议添加 Token 计数
|
||||
|
||||
---
|
||||
|
||||
## 八、结论
|
||||
|
||||
### 8.1 核心功能状态
|
||||
|
||||
- ✅ **聊天功能**: 正常工作
|
||||
- ✅ **消息持久化**: 正常工作
|
||||
- ✅ **Tab 切换**: 正常工作
|
||||
- ✅ **AI 记忆**: 正常工作
|
||||
- ✅ **Hands 面板**: 显示正常
|
||||
- ✅ **工作流 Tab**: 显示正确(已修复)
|
||||
- ✅ **UI 清理**: 移除重复功能
|
||||
|
||||
### 8.2 建议下一步
|
||||
|
||||
1. 完成 J5-J10 用户旅程验证
|
||||
2. 添加 Hands 触发的自动化测试
|
||||
3. 监控生产环境 Token 使用量
|
||||
4. 收集用户反馈
|
||||
|
||||
---
|
||||
|
||||
**验证人员**: Claude AI Agent
|
||||
**报告生成时间**: 2026-03-16
|
||||
195
docs/validation/p0-fixes-report.md
Normal file
195
docs/validation/p0-fixes-report.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# P0 问题修复报告
|
||||
|
||||
> **日期**: 2026-03-16
|
||||
> **状态**: 已完成
|
||||
|
||||
---
|
||||
|
||||
## 一、问题概述
|
||||
|
||||
在系统上线前验证过程中发现以下 P0 级别问题:
|
||||
|
||||
| 问题 ID | 描述 | 严重程度 |
|
||||
|---------|------|----------|
|
||||
| P0-1 | Agent 对话回复内容重复 | P0 阻塞 |
|
||||
| P0-2 | Tab 切换后对话内容消失 | P0 阻塞 |
|
||||
| P0-3 | 团队等 Tab 操作后内容消失 | P0 阻塞 |
|
||||
|
||||
---
|
||||
|
||||
## 二、根因分析
|
||||
|
||||
### P0-1: 消息内容重复
|
||||
|
||||
**根本原因**:
|
||||
- 双重流式回调 - `sendMessage` 的 `onDelta` 和 `initStreamListener` 都在更新同一条消息
|
||||
- 两个回调同时追加 delta,导致内容重复
|
||||
|
||||
**涉及文件**:
|
||||
- `desktop/src/store/chatStore.ts`
|
||||
- `desktop/src/components/ChatArea.tsx`
|
||||
|
||||
### P0-2: Tab 切换后内容消失
|
||||
|
||||
**根本原因**:
|
||||
- `chatStore.messages` 未持久化 - 只持久化了 `conversations` 和 `currentModel`
|
||||
- Tab 切换时 `messages` 状态被重置为空数组
|
||||
|
||||
**涉及文件**:
|
||||
- `desktop/src/store/chatStore.ts`
|
||||
|
||||
### P0-3: 团队状态丢失
|
||||
|
||||
**根本原因**:
|
||||
- `teamStore` 使用普通 Zustand,没有 persist 中间件
|
||||
- `activeTeam` 状态未被持久化
|
||||
|
||||
**涉及文件**:
|
||||
- `desktop/src/store/teamStore.ts`
|
||||
|
||||
---
|
||||
|
||||
## 三、修复方案
|
||||
|
||||
### 3.1 P0-1 修复: 消息重复
|
||||
|
||||
**修改文件**: `desktop/src/store/chatStore.ts`
|
||||
|
||||
**修改内容**:
|
||||
```typescript
|
||||
// 移除 sendMessage 中的 onDelta 回调,让 initStreamListener 统一处理
|
||||
// 修改前
|
||||
onDelta: (delta: string) => {
|
||||
set((state) => ({
|
||||
messages: state.messages.map((m) =>
|
||||
m.id === assistantId
|
||||
? { ...m, content: m.content + delta }
|
||||
: m
|
||||
),
|
||||
}));
|
||||
},
|
||||
|
||||
// 修改后
|
||||
onDelta: () => { /* Handled by initStreamListener to prevent duplication */ },
|
||||
```
|
||||
|
||||
### 3.2 P0-2 修复: 消息持久化
|
||||
|
||||
**修改文件**: `desktop/src/store/chatStore.ts`
|
||||
|
||||
**修改内容**:
|
||||
```typescript
|
||||
// 修改前
|
||||
partialize: (state) => ({
|
||||
conversations: state.conversations,
|
||||
currentModel: state.currentModel,
|
||||
}),
|
||||
|
||||
// 修改后
|
||||
partialize: (state) => ({
|
||||
conversations: state.conversations,
|
||||
currentModel: state.currentModel,
|
||||
messages: state.messages, // 新增
|
||||
currentConversationId: state.currentConversationId, // 新增
|
||||
}),
|
||||
```
|
||||
|
||||
### 3.3 P0-3 修复: 团队状态持久化
|
||||
|
||||
**修改文件**: `desktop/src/store/teamStore.ts`
|
||||
|
||||
**修改内容**:
|
||||
```typescript
|
||||
// 修改前
|
||||
import { create } from 'zustand';
|
||||
export const useTeamStore = create<TeamStoreState>((set, get) => ({ /* ... */ }));
|
||||
|
||||
// 修改后
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
export const useTeamStore = create<TeamStoreState>()(
|
||||
persist(
|
||||
(set, get) => ({ /* ... */ }),
|
||||
{
|
||||
name: 'zclaw-teams',
|
||||
partialize: (state) => ({
|
||||
teams: state.teams,
|
||||
activeTeam: state.activeTeam,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、验证步骤
|
||||
|
||||
### 4.1 手动验证清单
|
||||
|
||||
- [ ] 启动应用,- [ ] 发送消息,- [ ] 验证消息内容不重复
|
||||
- [ ] 切换到团队 Tab
|
||||
- [ ] 切换回聊天 Tab
|
||||
- [ ] 验证消息仍然存在
|
||||
- [ ] 刷新页面 (F5)
|
||||
- [ ] 验证消息历史恢复
|
||||
- [ ] 创建团队
|
||||
- [ ] 切换到其他 Tab
|
||||
- [ ] 验证团队仍然选中
|
||||
|
||||
### 4.2 自动化测试建议
|
||||
|
||||
```typescript
|
||||
// tests/desktop/state-persistence.test.ts
|
||||
|
||||
describe('State Persistence', () => {
|
||||
it('should persist messages across tab switches', async () => {
|
||||
// 1. 发送消息
|
||||
// 2. 切换 tab
|
||||
// 3. 切换回来
|
||||
// 4. 验证消息存在
|
||||
});
|
||||
|
||||
it('should not duplicate message content', async () => {
|
||||
// 1. 发送消息
|
||||
// 2. 等待流式响应完成
|
||||
// 3. 验证内容不重复
|
||||
});
|
||||
|
||||
it('should persist activeTeam across tab switches', async () => {
|
||||
// 1. 选择团队
|
||||
// 2. 切换 tab
|
||||
// 3. 切换回来
|
||||
// 4. 验证团队仍然选中
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、后续工作
|
||||
|
||||
1. **执行用户旅程验证** - 按计划的 10 个用户旅程进行端到端测试
|
||||
2. **编写回归测试** - 为状态持久化添加自动化测试
|
||||
3. **问题追踪** - 发现新问题时记录到问题池
|
||||
4. **回归验证** - 修复后重新验证相关功能
|
||||
|
||||
---
|
||||
|
||||
## 六、风险评估
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| localStorage 容量 | 消息过多可能超出限制 | 限制消息历史长度 |
|
||||
| 性能影响 | 持久化增加 IO | 使用 debounce 优化 |
|
||||
| 数据一致性 | 多 Tab 数据同步 | 添加 storage 事件监听 |
|
||||
|
||||
---
|
||||
|
||||
## 七、结论
|
||||
|
||||
P0 问题已修复,系统可以进行用户旅程验证。建议:
|
||||
|
||||
1. 立即进行 J1-J3 核心聊天功能验证
|
||||
2. 修复发现的新问题
|
||||
3. 完成全部 10 个用户旅程验证后生成最终报告
|
||||
Reference in New Issue
Block a user