Docs/i18n batch c1 developing foundation (#2504)

Co-authored-by: agent:skill-master <skill-master@openclaw>
This commit is contained in:
JAYcodr
2026-05-22 15:44:59 -07:00
committed by GitHub
co-authored by agent:skill-master <skill-master@openclaw>
parent 788087cc0a
commit 3299c16933
6 changed files with 1285 additions and 0 deletions
+353
View File
@@ -0,0 +1,353 @@
---
description: OpenHuman 代码库的深度架构参考 —— 仓库布局、运行时范围、双 socket 同步、RPC 流程。
icon: code-branch
lang: zh-CN
---
# OpenHuman 架构
**基于 Rust 构建的加密社区 AI 超级助手。**
OpenHuman 是一款为加密货币生态系统量身打造的跨平台通信与自动化平台。单一的 React + Rust(Tauri)代码库可以面向多个平台;**我们目前为用户文档和发布的仅是桌面端** —— **Windows、macOS 和 Linux**。Android、iOS 和 Web **尚未**在当前文档或发布中支持。技术栈包括一个托管的 Node.js 运行时,用于支持工具能力的技能;持久化的 Rust 原生 WebSocket 基础设施;以及一个 AI 工具协议,让语言模型实时调用任何已连接的服务。
---
## 仓库布局(monorepo
| 路径 | 内容 |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`app/`** | Yarn workspace **`openhuman-app`**Vite/React UI`app/src/`)、Tauri 壳层(`app/src-tauri/`)、Vitest 测试 |
| **仓库根目录 `src/`** | Rust **`openhuman_core`** 库 + **`openhuman-core`** CLI 二进制文件 —— 核心服务器、JSON-RPC、一等 JavaScript 运行时(`src/openhuman/javascript/`),由托管的 Node.js 实现驱动、频道、内存等 |
| **`Cargo.toml`**(根目录) | 构建 `openhuman-core` 二进制文件(`cargo build --bin openhuman-core`),staging 到 `app/src-tauri/binaries/` 以供桌面打包 |
| **`skills/`** | 运行时消耗的技能包 |
| **`docs/`** | 本书 + 每棵树指南(`docs/src/``docs/src-tauri/` |
桌面应用 **WebView**`app/` 加载 UI;繁重的 RPC 和技能在 **`openhuman-core`** 进程中运行,可通过 HTTP 从 Tauri 主机访问(`core_rpc_relay`)。
---
## 平台覆盖范围
**今天支持的(终端用户):** 桌面端。Windows、macOS、Linux(原生安装包)。
**尚未支持:** Android、iOS、独立 Web 客户端(仓库中可能以实验性目标存在;不要视为产品就绪)。
```text
OpenHuman(已发布)
|
Desktop
/ | \
Windows macOS Linux
x64 x64 x64
ARM64 ARM64 ARM64
```
Tauri v2 将 Rust 核心编译为每个平台的原生二进制文件,将 React 前端作为轻量级 WebView 嵌入。桌面构建产出 `.dmg``.msi``.AppImage``.deb` 安装包。额外目标(移动端、Web)在明确文档化支持之前均超出范围。
---
## 高层架构
```text
+------------------------------------------------------------------+
| React 前端 |
| Redux Toolkit | Socket.io 客户端 | MCP 传输层 | UI |
+------------------------------------------------------------------+
| Tauri IPC 桥接 |
+------------------------------------------------------------------+
| Rust 核心引擎 |
| |
| +------------------+ +------------------+ +-----------------+ |
| | QuickJS 技能 | | Socket 管理器 | | AI 加密 | |
| | 运行时引擎 | | (持久化 WS) | | & 内存存储 | |
| +------------------+ +------------------+ +-----------------+ |
| |
| +------------------+ +------------------+ +-----------------+ |
| | 技能注册表 | | Cron 调度器 | | 会话 & 认证 | |
| | & 桥接 API | | (5s tick 循环) | | 管理 | |
| +------------------+ +------------------+ +-----------------+ |
| |
| +------------------+ +------------------+ +-----------------+ |
| | Telegram | | SQLite 存储 | | OS 钥匙串 | |
| | 集成 | | (rusqlite) | | 集成 | |
| +------------------+ +------------------+ +-----------------+ |
+------------------------------------------------------------------+
|
+-----------+-----------+
| |
后端服务 外部 API
(Socket.io 服务器) (Telegram 等)
```
前端通过两种方式与 **openhuman** Rust 核心通信:用于一小部分壳层命令的 **Tauri IPC**(窗口、AI 文件辅助函数、**`core_rpc_relay`**),以及用于业务逻辑和技能的 **HTTP JSON-RPC**。核心拥有持久连接(如适用)、内存/功能的加密工作,以及 **QuickJS** 沙盒化技能执行。
---
## Rust 驱动的性能
OpenHuman 选择 Tauri + Rust 而非 Electron,基于根本的性能和安全原因:
| 指标 | OpenHumanTauri + Rust | 典型 Electron 应用 |
| ------------------------- | -------------------------------------------------------- | ---------------------------- |
| 二进制体积 | 取决于功能(CEF 运行时 + 技能包占主导) | ~150 MB+ |
| 每技能上下文内存 | ~1-2 MBQuickJS | ~150 MB+Chromium 渲染器) |
| 冷启动 | 亚 500ms | 2-5 秒 |
| 垃圾回收暂停 | 无(Rust 所有权模型) | V8 GC 暂停 |
| 内存安全 | 编译期保证 | 运行时异常 |
| TLS 实现 | rustls(无 OpenSSL 依赖) | Chromium 的 BoringSSL |
**这对加密平台为何重要**:交易员和分析师在运行 OpenHuman 的同时,还会运行资源密集型工具、图表软件、多个浏览器标签、交易终端。原生二进制文件加上亚 500ms 启动意味着应用感觉像原生应用,不会碍事。零 GC 暂停意味着实时价格推送和警报永远不会因内存管理而延迟。
**Tokio 异步运行时**驱动所有 I/O。WebSocket 连接、HTTP 请求、文件操作和技能间通信,都是线程池上的非阻塞任务。数千个并发操作(技能执行、cron job、socket 事件)共享一小套固定的 OS 线程。
---
## 实时 Socket 基础设施
OpenHuman 实现了**双 socket 架构**:桌面端使用 Rust 原生 WebSocket 客户端,Web 端使用 JavaScript Socket.io 客户端。Rust 实现能在应用后台存活,独立于 WebView 运行,并通过 rustls 处理 TLS。
```text
桌面模式: Web 模式:
+-------------+ +-------------+
| React UI | | React UI |
+------+------+ +------+------+
| Tauri IPC | Direct
+------+------+ +------+------+
| Rust Socket | | JS Socket |
| Manager | | .io Client |
+------+------+ +------+------+
| tokio-tungstenite | Socket.io
| + rustls TLS | (websocket/polling)
+------+------+ +------+------+
| Backend | | Backend |
+-------------+ +-------------+
```
**Rust Socket 管理器**通过原始 WebSocket 实现 Engine.IO v4 + Socket.IO v4 帧:
- **握手**WebSocket 连接、Engine.IO OPEN(提取 `sid``pingInterval``pingTimeout`)、带 JWT 认证的 Socket.IO CONNECT、CONNECT ACK
- **保活**:响应 Engine.IO PING 以 PONG;超时阈值 = `pingInterval + pingTimeout + 5s`(默认:50 秒)
- **重连**:指数退避,从 1 秒到最大 30 秒。成功连接丢失后重置为 1s;如果连接从未建立则持续增长
- **CORS 绕过**Rust `reqwest` HTTP 客户端直接发起外部 API 调用,不受浏览器 CORS 限制
socket 连接在所有技能间**共享**。当事件到达时,socket 管理器通过异步消息通道将它们路由到相应的技能。这完全消除了每个技能的连接开销。
**`tool:sync` 协议**:每次 socket 连接和技能生命周期变化时,客户端都会发出一个 `tool:sync` 事件,包含可用工具的完整列表及其连接状态。这使后端 AI 系统能实时感知所有能力。
---
## 技能运行时引擎
OpenHuman 的决定性能力是其运行在 Rust 进程内部的**沙盒化 JavaScript 执行引擎**。技能是轻量级自动化脚本,通过自定义工具、集成和定时任务扩展平台。
```text
+---------------------------------------------------------------+
| RuntimeEngine |
| |
| +-------------------+ +-------------------+ |
| | SkillRegistry | | CronScheduler | |
| | (HashMap + MPSC) | | (5s tick loop) | |
| +--------+----------+ +--------+----------+ |
| | | |
| +--------v----------+ +--------v----------+ +----------+ |
| | JavaScript Layer | | runtime_node | | Bridge | |
| | skill metadata | | managed Node.js | | APIs | |
| | + prompt context | | system/bundled | +----+-----+ |
| | + tool discovery | | tool execution | | |
| +-------------------+ +-------------------+ | |
| | |
| +---------------------------------------------------v-----+ |
| | net | db | store | cron | log | tauri | | |
| | HTTP SQLite KV Schedule Log Platform| | |
| +------------------------------------------------------+ | |
+---------------------------------------------------------------+
```
**Node.js 运行时**:核心尽可能解析兼容的系统 `node`,否则将托管发行版安装到 OpenHuman 缓存中。技能主要暴露工具元数据,并使用运行时桥接来列出和执行工具,而非在核心内运行隔离的 QuickJS VM。
| 参数 | 值 |
| ---------------------- | ----- |
| 公共语言槽位 | `javascript` |
| 当前 JS 后端 | `runtime_node` |
| 托管 Node 版本 | 默认 `v22.11.0` |
| 运行时来源 | 系统 `node` 或托管安装 |
| 完整性验证 | 针对 `SHASUMS256.txt` 的 SHA-256 |
**工具桥架构**`SKILL.md` 包提供元数据、指令和可选的捆绑 JS 辅助函数。Rust 核心拥有权威的工具注册表,JavaScript 运行时桥接列出工具并将具名工具调用分派到核心或 Node-backed 辅助函数中。
**桥接 API** 向运行时桥接和 Node-backed 辅助函数暴露平台能力:
| 桥接 | 能力 |
| --------- | ----------------------------------------------------------- |
| **net** | 通过 `reqwest` 的 HTTP fetch(默认 30s 超时,所有方法) |
| **db** | 通过 `rusqlite` 的每个技能 SQLite 数据库 |
| **store** | 键值持久化 |
| **cron** | 定时注册(6 字段 cron 表达式) |
| **log** | 通过 Rust `log` crate 的结构化日志 |
| **tauri** | 平台检测、通知、白名单环境变量 |
**技能发现** 使用 `SKILL.md` 加上可选的捆绑资源:
| 字段 | 用途 |
| ------------------ | ------- |
| `name` | 人类可读的显示名称 |
| `description` | 触发/选择摘要 |
| `metadata.id` | 存在时的稳定技能 slug |
| `allowed-tools` | 工具允许列表指引 |
| 捆绑资源 | 脚本、参考、资源 |
技能从 GitHub 仓库同步并在运行时发现。执行不再建模为每个技能一个嵌入式 QuickJS VMJavaScript 行为通过共享运行时桥接流动。
**Cron 调度器**:一个 5 秒 tick 循环对照 UTC 时间检查所有已注册的调度,使用 `cron` crate 进行表达式解析。当调度触发时,调度器向技能的通道发送 `CronTrigger` 消息,调用技能的 `onCronTrigger()` 处理程序。
---
## AI & 工具协议(MCP
OpenHuman 实现了**模型上下文协议**,一个基于 Socket.io 的 JSON-RPC 2.0 层,让 AI 模型发现并由技能暴露的工具。
```text
用户提示
|
v
AI 模型(后端)
|
| 1. mcp:listTools --> 前端/Rust 聚合所有技能工具
| <-- 工具目录
|
| 2. 决定调用哪个工具
|
| 3. mcp:toolCall { skillId__toolName, arguments }
| |
| v
| Socket 管理器路由到技能注册表
| |
| v
| QuickJS 技能实例执行工具
| |
| v
| 桥接 API 调用(HTTP、DB 等)
| |
| <-- mcp:toolCallResponse { result }
|
v
AI 对用户的响应
```
**传输**:每次请求 30 秒超时,`mcp:` 事件前缀,请求 ID 在待处理响应映射中跟踪。工具名称以 `skillId__toolName` 命名空间化,以实现明确路由。
**工具同步**`tool:sync` 事件在每次 socket 连接和技能状态变化时广播完整的工具清单、技能 ID、名称、连接状态和工具列表。后端 AI 系统始终拥有可用能力的最新视图。
**AI 记忆系统**
| 功能 | 实现 |
| ------------------ | ------------------------------------------------------ |
| 静态加密 | 带 Argon2id 密钥派生的 AES-256-GCM |
| 分块 | 每块 512 token64 token 重叠 |
| 搜索 | 混合:70% 向量相似度 + 30% FTS5 全文 |
| 嵌入 | OpenAI `text-embedding-3-small` |
| 知识图谱 | 通过 REST API 的 Neo4j,用于实体关系 |
| 会话 | 带压缩和工具压缩的 JSONL 转录 |
记忆加密密钥通过 Argon2id 从用户凭证派生,确保记忆文件在未经认证的情况下不可读。混合搜索结合语义理解(向量相似度)和关键词精确度(SQLite FTS5)以实现可靠的召回。
---
## 安全架构
```text
+-------------------------------------------------------------------+
| 安全层 |
| |
| +------------------+ +------------------+ +------------------+ |
| | OS 钥匙串 | | AES-256-GCM | | 沙盒化 | |
| | (macOS/Win/Lin) | | 内存加密 | | QuickJS 每 | |
| | 用于凭证 | | + Argon2id KDF | | 技能 (64 MB) | |
| +------------------+ +------------------+ +------------------+ |
| |
| +------------------+ +------------------+ +------------------+ |
| | 一次性 | | rustls TLS | | 无 localStorage | |
| | 登录 token | | 用于所有网络 | | 存储敏感数据 | |
| | (5-min TTL) | | 连接 | | | |
| +------------------+ +------------------+ +------------------+ |
+-------------------------------------------------------------------+
```
- **凭证存储**:通过 `keyring` crate 的 OS 钥匙串集成(macOS Keychain、Windows Credential Manager、Linux Secret Service),仅限桌面端
- **内存加密**:带 Argon2id 密钥派生的 AES-256-GCM。所有 AI 内存静态加密
- **技能沙盒化**:每个 QuickJS 实例都有强制内存限制(默认 64 MB)和栈限制(512 KB)。禁止跨技能内存访问
- **认证交接**:Web 到桌面认证使用 5 分钟 TTL 的一次性登录 token,通过 Rust HTTP 客户端交换(绕过 CORS
- **网络 TLS**:所有 WebSocket 和 HTTP 连接使用 rustls,不依赖平台 OpenSSL
- **状态管理**:敏感数据保存在 Redux(内存)和 OS 钥匙串(持久化)中。凭证或 token 不使用 localStorage
- **提示注入防护**:用户提示在模型/工具执行前经过规范化/评分,并在服务器端强制执行(`allow | review | block`)。详见 [`docs/PROMPT_INJECTION_GUARD.md`](../../docs/PROMPT_INJECTION_GUARD.md)
---
## 端到端数据流
从用户操作到外部服务再返回的完整流程:
```text
用户在聊天 UI 中输入命令
|
v
React 前端分派到 AI 提供商
|
v
AI 模型接收提示 + 工具目录(通过 tool:sync
|
v
AI 决定调用技能工具(例如,发送 Telegram 消息)
|
v
通过 Socket.io 发送 mcp:toolCall 事件
|
v
Socket 管理器(Rust)接收事件,解析 skillId__toolName
|
v
技能注册表通过 MPSC 通道将消息路由到正确的 QuickJS 实例
|
v
QuickJS 技能执行工具处理程序
|
v
桥接 APInet.rs 通过 reqwest 发起 HTTP 请求(无 CORSrustls TLS
|
v
外部服务响应(例如,Telegram API
|
v
结果回流:桥接 -> QuickJS -> 注册表 -> Socket -> MCP -> AI -> UI
|
v
用户在聊天界面中看到结果
```
每一层都是异步且非阻塞的。Rust 核心在固定的 Tokio 线程池上处理数千个并发的技能执行、cron 触发和 socket 事件。
---
## 技术栈
| 层 | 技术 | 原因 |
| -------------- | ------------------------------- | -------------------------------------------------------- |
| **前端** | React 19, TypeScript 5.8 | 现代组件模型,类型安全 |
| **状态** | Redux Toolkit + Persist | 可预测状态,支持离线持久化 |
| **构建** | Vite 7 | 亚秒级 HMR,优化的生产构建 |
| **样式** | Tailwind CSS | 工具优先,一致的设计系统 |
| **框架** | Tauri v2 | 原生跨平台,开销最小 |
| **语言** | Rust (2021 edition) | 内存安全,零成本抽象 |
| **异步** | Tokio | 高性能异步 I/O 运行时 |
| **JS 运行时** | Node.js | 用于工具辅助函数和技能相关 JS 的托管 V8 运行时 |
| **数据库** | SQLite (rusqlite) | 嵌入式,零配置,每技能隔离 |
| **WebSocket** | tokio-tungstenite + rustls | 持久连接,原生 TLS |
| **HTTP** | reqwest | 异步 HTTP,支持 rustls + native-tLS 双栈 |
| **加密** | aes-gcm + argon2 | AES-256-GCM 加密,Argon2id 密钥派生 |
| **调度** | cron crate + 自定义调度器 | 标准 cron 表达式,5 秒精度 |
| **Telegram** | 已移除 | Telegram 集成已移除 |
| **实时** | Socket.io(客户端) | 双向基于事件的通信 |
| **AI** | MCPJSON-RPC 2.0 | LLM 集成的标准化工具协议 |
| **搜索** | OpenAI 嵌入 + SQLite FTS5 | 混合语义 + 关键词搜索 |
| **图谱** | Neo4j | 实体关系知识图谱 |
@@ -0,0 +1,81 @@
---
description: >-
OpenHuman 系统的高层轮廓(桌面壳层、Rust 核心、Memory Tree、Agent 循环)。指向仓库中的深度开发者架构文档。
icon: code-branch
lang: zh-CN
---
# 架构
OpenHuman 基于 GNU GPL3 开源。本页是系统的高层轮廓;深度开发者架构参考位于仓库中的 [深度架构文档](../architecture.zh-CN.md)。
## 系统形态
OpenHuman 是一款 **React + Tauri v2 桌面应用**,搭配一个承担重活的 **Rust 核心**
```text
┌──────────────────────────────────────────────────┐
│ Tauri 壳层 (app/src-tauri/) │
│ • 窗口管理、OS 集成、sidecar 生命周期 │
│ • 用于集成提供商的 CEF 子 WebView │
└──────────────────────────────────────────────────┘
│ JSON-RPC (HTTP) ↕
┌──────────────────────────────────────────────────┐
│ Rust 核心 (openhuman 二进制, src/) │
│ • Memory Tree 流水线 │
│ • 集成适配器 + 自动获取调度器 │
│ • 提供商路由器(模型路由) │
│ • TokenJuice 压缩 │
│ • 原生工具(搜索、获取、文件系统、git…) │
│ • 语音(STT 输入、TTS 输出、Meet Agent
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ React 前端 (app/src/) │
│ • 页面、导航 │
│ • 通过 coreRpcClient 与核心通信 │
│ • 无业务逻辑 —— 仅负责展示 │
└──────────────────────────────────────────────────┘
```
**逻辑归属:**
* **Rust 核心**。所有业务逻辑。Memory Tree、集成、模型路由、工具、语音。具有权威性。
* **Tauri 壳层**。窗口管理、进程生命周期、IPC。是交付载体,不是功能的栖身之所。
* **React 前端**。UI 与编排。通过 JSON-RPC 调用核心。
## 数据流
1. **连接**。通过 OAuth 接入[集成](../../features/integrations/README.zh-CN.md)。后端保存 token;核心永远不会以明文形式看到它。
2. **自动获取**。每二十分钟,[调度器](../../features/obsidian-wiki/auto-fetch.zh-CN.md)会遍历每个活跃连接,并要求每个原生提供商进行同步。
3. **规范化**。提供商输出(邮件页面、GitHub diff、Slack 频道转储)被归一化为带来源标签的 Markdown。
4. **分块**。Markdown 被拆分为 ≤3k token 的确定性块。
5. **存储**。块存入 SQLite (`<workspace>/memory_tree/chunks.db`),并以 `.md` 文件形式存入 `<workspace>/wiki/`
6. **评分**。后台工作线程运行嵌入、实体提取、热度评分。
7. **摘要**。从块池中构建并刷新来源 / 主题 / 全局摘要树。
8. **检索**。当你提问时,Agent 查询 Memory Tree(搜索 / 钻取 / 主题 / 全局 / 获取)。
9. **压缩**。工具输出和大型源数据在进入 LLM 上下文前经过 [TokenJuice](../../features/token-compression.zh-CN.md) 处理。
10. **路由**。[路由器](../../features/model-routing/) 根据任务提示选择合适的提供商 + 模型。
## 隐私边界
留在你机器上的数据:
* Memory Tree SQLite 数据库。
* Obsidian Markdown 仓库。
* 音频捕获缓冲区和任何本地模型状态。
经过 OpenHuman 后端的数据(在一个订阅下):
* LLM 调用(模型提供商)。
* 网页搜索智能体。
* 集成 OAuth 和工具智能体。
* TTS 流。
完整图景请参阅 [隐私与安全](../../features/privacy-and-security.zh-CN.md)。
## 开源
* **仓库:** [github.com/tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman)。GNU GPL3。
* 欢迎提交 **Issue 和 PR**。项目处于早期测试阶段。
* 对于贡献者,权威开发者指南是[深度架构文档](../architecture.zh-CN.md)。
@@ -0,0 +1,190 @@
---
description: 在全新机器上从头构建 Rust 核心。
icon: terminal
lang: zh-CN
---
# 构建 Rust 核心
本页面向贡献者,是在全新机器上编译 Rust 核心的参考文档。
它仅涵盖**仓库根目录的 crate**:
- Cargo 包:`openhuman`
- 二进制文件:`openhuman-core`
- 库:`openhuman_core`
如果你需要完整的桌面应用(`pnpm dev`、Tauri、CEF、前端工具链),请使用[环境搭建](getting-set-up.zh-CN.md)。该路径有额外的 JavaScript、子模块和桌面运行时依赖,**不**需要用于纯核心的 `cargo` 工作流。
## 1. 安装指定版本的 Rust 工具链
仓库在 [`rust-toolchain.toml`](../../rust-toolchain.toml) 中固定了 Rust 版本:
- Channel`1.93.0`
- Components`rustfmt``clippy`
推荐安装方式:
```bash
rustup toolchain install 1.93.0 --component rustfmt --component clippy
rustup default 1.93.0
```
你也可以在安装 `rustup` 后,让 `cargo``rust-toolchain.toml` 自动安装。
## 2. 克隆仓库
仅核心开发:
```bash
git clone https://github.com/tinyhumansai/openhuman.git
cd openhuman
```
这对根目录 crate 来说已足够。
桌面/Tauri 开发则不同:
- 只有在构建桌面壳层或 CEF 感知的 Tauri 工具链时,才需要 `app/src-tauri/vendor/` 子模块。
- 该流程请遵循[环境搭建](getting-set-up.zh-CN.md)并运行 `git submodule update --init --recursive`
## 3. 构建命令
从仓库根目录运行:
```bash
# 快速依赖 + 类型检查
cargo check --manifest-path Cargo.toml
# 实际 CLI / RPC 二进制文件的 Debug 构建
cargo build --manifest-path Cargo.toml --bin openhuman-core
# Release 构建
cargo build --manifest-path Cargo.toml --release --bin openhuman-core
# Rust 测试
cargo test --manifest-path Cargo.toml
```
注意:
- **包**名是 `openhuman`,但可运行的二进制文件是 **`openhuman-core`**。
- 如果你更喜欢面向包的 cargo 命令用于打包脚本,请使用 `-p openhuman`
- 构建好的二进制文件位于 `target/debug/openhuman-core``target/release/openhuman-core`
## 4. macOS 前置条件
安装:
- Xcode Command Line Tools`xcode-select --install`
原因:
- `whisper-rs` 在构建期间编译原生代码。
- 在 macOS 上,该 crate 在 [`Cargo.toml`](../../Cargo.toml) 中以 `metal` 特性启用构建,因此需要 Apple 工具链和 SDK 头文件。
安装 Xcode CLT 后,核心应该能用上述 cargo 命令构建。
## 5. Linux 前置条件
### 仅核心包集合
在全新 Linux 机器上运行 `cargo` 前,先安装这些包。
**Ubuntu / Debian**
```bash
sudo apt-get update
sudo apt-get install -y \
build-essential cmake pkg-config clang libssl-dev libclang-dev \
libasound2-dev libxi-dev libxtst-dev libxdo-dev libudev-dev \
libstdc++-14-dev
```
**Arch Linux**
```bash
sudo pacman -S --needed base-devel cmake pkgconf clang openssl \
alsa-lib libxi libxtst xdotool libevdev
```
> 在 Arch 上,`clang` 包含 `libclang``base-devel` 包含 `gcc`(提供 `libstdc++`),因此不需要单独的 `-dev` 包。
这些包的重要性:
- `build-essential` / `base-devel``cmake``pkg-config` / `pkgconf`:传递性 Rust 依赖使用的原生构建。
- `clang``libclang-dev`bindgen / C 和 C++ 编译路径,被原生 crate 使用。
- `libssl-dev` / `openssl`:某些网络依赖需要的 OpenSSL 头文件。
- `libasound2-dev` / `alsa-lib``libxi-dev` / `libxi``libxtst-dev` / `libxtst``libxdo-dev` / `xdotool``libudev-dev`Arch 中已包含在 `systemd-libs` 内)、`libevdev`:被核心构建引入的音频/输入/设备 crate 所需。
### `whisper-rs` + `clang` 注意事项
`whisper-rs-sys``clang` 下可能会失败并提示:
```text
fatal error: 'array' file not found
```
这就是为什么文档特别指出 `libstdc++-14-dev``clang` 在 Ubuntu runner 上可能会选择 GCC 14 的 C++ 头文件。
如果你的发行版布局仍然导致构建无法解析 `libstdc++.so`,请使用 [`AGENTS.md`](../../AGENTS.md) 中记录的相同变通方案:
```bash
# Ubuntu/Debian —— 按需调整 GCC 版本
sudo ln -sf /usr/lib/gcc/x86_64-linux-gnu/13/libstdc++.so /usr/lib/x86_64-linux-gnu/libstdc++.so
```
Arch Linux 通常不需要此变通方案,因为 `gcc-libs``libstdc++.so` 放在了默认库搜索路径上。
### Linux 桌面/Tauri 包集合
如果你构建的是桌面壳层而非仅核心 crate,请安装更广泛的依赖集合。
**Ubuntu / Debian**(镜像自 [`.github/workflows/build-desktop.yml`](../../.github/workflows/build-desktop.yml)):
```bash
sudo apt-get update
sudo apt-get install -y \
libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev \
patchelf cmake libasound2-dev libxdo-dev libxtst-dev libx11-dev libxi-dev \
libevdev-dev libssl-dev libclang-dev \
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
libgbm1 libpango-1.0-0 libcairo2 libatspi2.0-0 libxshmfence1 libu2f-udev
```
**Arch Linux**
```bash
sudo pacman -S --needed gtk3 webkit2gtk-4.1 libayatana-appindicator \
librsvg patchelf nss nspr at-spi2-core libcups libdrm \
libxkbcommon libxcomposite libxdamage libxfixes libxrandr \
mesa pango cairo libxshmfence
```
仅在需要 `app/src-tauri/` 时使用桌面列表;对于根 crate 工作,上面较小的仅核心列表是相关的基线。
## 6. Windows 前置条件
安装:
- 通过 `rustup` 安装 Rust
- Visual Studio Build Tools 2022 或带 **使用 C++ 的桌面开发** 工作负载的 Visual Studio
- CI 和发布构建使用的 MSVC 目标:`x86_64-pc-windows-msvc`
安装 Microsoft 工具链后推荐的命令:
```powershell
rustup toolchain install 1.93.0 --component rustfmt --component clippy
rustup target add x86_64-pc-windows-msvc
cargo build --manifest-path Cargo.toml --bin openhuman-core
```
Windows 注意事项:
- 仓库对 `whisper-rs-sys` 打补丁以强制使用静态 MSVC CRT,并避免 [`Cargo.toml`](../../Cargo.toml) 中提到的 `LNK2038` / `LNK1169` 不匹配。请使用 MSVC 工具链,而非 MinGW。
## 7. 相关路径
- [环境搭建](getting-set-up.zh-CN.md):完整的桌面贡献者设置,含 `pnpm`、Tauri、子模块和 sidecar staging。
- [OpenHuman 架构](architecture/README.zh-CN.md):核心在桌面应用和 RPC 流程中的位置。
+256
View File
@@ -0,0 +1,256 @@
---
description: 使用 WDIO + tauri-driver / Appium 进行端到端测试。CI 和本地设置。
icon: vials
lang: zh-CN
---
# E2E 测试指南
## 概述
桌面 E2E 测试使用 **WebDriverIO (WDIO)** 通过两个自动化后端驱动 Tauri 应用:
| 平台 | 驱动 | 端口 | 应用格式 | 选择器 |
|----------|--------|------|------------|-----------|
| **Linux / CEF 状态** | `tauri-driver` | 4444 | Debug 二进制文件 | CSS / DOM |
| **macOS / Appium** | Appium Mac2 | 4723 | `.app` 包 | XPath / 辅助功能 |
OpenHuman 桌面应用目前使用 CEF 运行时(`tauri-runtime-cef`)。Linux `tauri-driver` 与 WebKitWebDriver / webkit2gtk 通信,无法驱动 CEF -backed WebView,因此 Linux CEF E2E 在 CI 中被禁用,直到存在 CEF 兼容的驱动或替代 harness。目前支持的路径是 macOS/Appium 用于本地运行,以及在该工作流启用时手动触发 macOS/Appium 工作流运行。
---
## 快速开始
### Linux / CEF 状态
```bash
# 安装 tauri-driver(一次性)
cargo install tauri-driver
# 构建 E2E 应用
pnpm --filter openhuman-app test:e2e:build
# 运行所有流程
pnpm --filter openhuman-app test:e2e:all:flows
# 运行单个 spec
bash app/scripts/e2e-run-spec.sh test/e2e/specs/smoke.spec.ts smoke
```
在无头 Linux 上,harness 在 **Xvfb** 虚拟显示下运行。此路径目前仅对非 CEF / WebKit 兼容调试有用;默认 CEF 应用无法被 WebKitWebDriver 自动化。
### macOS / Appium
```bash
# 安装 Appium + Mac2 驱动(一次性,需要 Node 24+)
npm install -g appium
appium driver install mac2
# 构建 .app 包
pnpm --filter openhuman-app test:e2e:build
# 运行所有流程
pnpm --filter openhuman-app test:e2e:all:flows
```
### macOS 上的 Docker(本地运行 Linux harness
使用 Docker 从 macOS 运行相同的基于 Linux 的 harness。同样的 CEF 限制适用:在存在 CEF 兼容驱动之前,这不是默认 CEF 运行时的支持路径。
```bash
# 构建 + 运行所有 E2E 流程
docker compose -f e2e/docker-compose.yml run --rm e2e
# 先构建应用(如需要)
docker compose -f e2e/docker-compose.yml run --rm e2e \
pnpm --filter openhuman-app test:e2e:build
# 运行单个 spec
docker compose -f e2e/docker-compose.yml run --rm e2e \
bash app/scripts/e2e-run-spec.sh test/e2e/specs/smoke.spec.ts smoke
```
需要 Docker Desktop 或 Colima。仓库通过 bind mount 挂载,因此构建在运行之间持久化。
---
## 架构
### 平台检测
`app/test/e2e/helpers/platform.ts` 导出:
- `isTauriDriver()``true` 表示 Linuxtauri-driver session
- `isMac2()``true` 表示 macOSAppium Mac2 session
- `supportsExecuteScript()``true``browser.execute()` 可用时(仅 tauri-driver
### 元素辅助函数
`app/test/e2e/helpers/element-helpers.ts` 提供统一 API
| 辅助函数 | Mac2 (macOS) | tauri-driver (Linux) |
|--------|-------------|---------------------|
| `waitForText(text)` | @label/@value/@title 上的 XPath | DOM 文本内容上的 XPath |
| `waitForButton(text)` | XCUIElementTypeButton XPath | `button` / `[role="button"]` XPath |
| `clickText(text)` | W3C 指针动作 | 标准 `el.click()` |
| `clickNativeButton(text)` | XCUIElementTypeButton 上的 W3C 指针动作 | button 上的标准 `el.click()` |
| `clickToggle()` | XCUIElementTypeSwitch / XCUIElementTypeCheckBox | `[role="switch"]` / `input[type="checkbox"]` |
| `waitForWindowVisible()` | XCUIElementTypeWindow | 窗口句柄检查 |
| `waitForWebView()` | XCUIElementTypeWebView | `document.readyState` 检查 |
| `hasAppChrome()` | XCUIElementTypeMenuBar | 窗口句柄检查 |
| `dumpAccessibilityTree()` | 辅助功能 XML | HTML 页面源码 |
### 稳定的测试 ID
优先为 E2E spec 点击或轮询的 UI affordance 使用稳定的 `data-testid` hook。使用分类法 `<surface>-<element>-<id?>`,例如:
- `cron-jobs-panel``cron-refresh`
- `cron-job-row-<jobId>``cron-job-toggle-<jobId>``cron-job-run-<jobId>``cron-job-view-runs-<jobId>``cron-job-remove-<jobId>`
- `settings-nav-<routeId>`
- `skill-row-<skillId>``skill-install-<skillId>``skill-uninstall-<skillId>`
- `thread-row-<threadId>``new-thread-button``send-message-button`
- `onboarding-next-button`
当 spec 瞄准这些 hook 之一时,使用 `element-helpers.ts` 中的 `waitForTestId(testId)``clickTestId(testId)`。对行/动作发现保留文本选择器,对用户可见文案断言也保留文本选择器。
### 深度链接辅助函数
`app/test/e2e/helpers/deep-link-helpers.ts` 处理 auth 深度链接:
- **tauri-driver**`browser.execute(window.__simulateDeepLink(url))`(主要),`xdg-open`(备用)
- **Appium Mac2**`macos: deepLink` 扩展命令(主要),`open -a ...`(备用)
对于发布候选版,在触碰 CEF preflight、单实例或深度链接启动代码时,还要在 Linux 或 macOS 上运行一次手动 secondary-instance 冒烟测试:
1. 正常启动 OpenHuman 并保持运行。
2. 通过 OS opener 触发 `openhuman://auth?token=e2e-token&key=auth`
3. 确认已运行的窗口接收到回调,且不会启动第二个完整的 CEF 实例。
4. 确认 secondary 进程干净退出,没有 CEF 缓存锁错误。
这捕捉了一类回归:secondary 进程在 Tauri 的深度链接转发路径安装之前,于 CEF 缓存 preflight 期间退出。
### 编写跨平台 spec
1. 在 spec 中使用 `element-helpers.ts` 中的**辅助函数**,永远不要使用原始的 `XCUIElementType*` 选择器
2. 使用 **`clickNativeButton(text)`** 代替内联 button-clicking 代码
3. 使用 **`hasAppChrome()`** 代替检查 `XCUIElementTypeMenuBar`
4. 使用 **`waitForWebView()`** 代替检查 `XCUIElementTypeWebView`
5. 对于仅 macOS 的测试,使用 `process.platform` 守卫或单独的 spec 文件
6. 对 hash 路由使用 `navigateViaHash(route)`;它等待 hash、`document.readyState` 和挂载的 React root 后返回。在 onboarding 之后,`walkOnboarding()` 也等待 `#/home` 加上 Home 页面标记,然后 spec 才会导航到别处。
---
## 环境变量
| 变量 | 默认值 | 说明 |
|----------|---------|-------------|
| `TAURI_DRIVER_PORT` | `4444` | tauri-driver WebDriver 端口 |
| `APPIUM_PORT` | `4723` | Appium 服务器端口 |
| `E2E_MOCK_PORT` | `18473` | Mock 后端服务器端口 |
| `OPENHUMAN_WORKSPACE` | (临时目录) | 应用工作区目录 |
| `OPENHUMAN_SERVICE_MOCK` | `0` | 启用服务 mock 模式 |
| `OPENHUMAN_E2E_MODE` | 未设置 | 启用破坏性测试支持 RPC;E2E runner 将其设为 `1` |
| `OPENHUMAN_E2E_AUTH_BYPASS` | 未设置 | 启用 JWT 绕过认证 |
| `DEBUG_E2E_DEEPLINK` | (verbose) | 设为 `0` 以静默深度链接日志 |
| `E2E_FORCE_CARGO_CLEAN` | 未设置 | E2E 构建前强制 cargo clean |
---
## CI 工作流
### Push / PR 检查
默认的 `test.yml` 工作流运行前端单元测试和 Rust 检查。其 Linux `tauri-driver` E2E job 被注释掉了,因为 WebKitWebDriver 无法驱动 CEF-backed WebView。
被禁用的 Linux E2E job 过去会:
1. 安装系统依赖(webkit2gtk、Xvfb、dbus
2. 通过 cargo 安装 `tauri-driver`
3. 用 mock 服务器 URL 构建应用
4. 在 Xvfb 下运行所有 E2E 流程
### macOS / Appium
macOS/Appium 是当前 CEF 桌面应用支持的自动化后端。在本地运行,或在该工作流启用时通过手动触发的 macOS 工作流运行:
1. 安装 Appium + Mac2 驱动
2. 构建 `.app`
3. 运行所有 E2E 流程
---
## 故障排除
### Linux"WebView not ready" 超时
对于默认 CEF 运行时,这通常意味着不支持的 Linux `tauri-driver` 路径正试图通过 WebKitWebDriver 驱动 CEF-backed WebView。请使用 macOS/Appium,或等待 CEF 兼容的 Linux 驱动。
确保 `DISPLAY` 已设置且 Xvfb 正在运行:
```bash
export DISPLAY=:99
Xvfb :99 -screen 0 1280x1024x24 &
```
还要确保 dbus 已启动(webkit2gtk 需要):
```bash
eval $(dbus-launch --sh-syntax)
```
### Linux:找不到 tauri-driver
```bash
cargo install tauri-driver
```
### macOS:深度链接在 `tauri dev` 中不工作
深度链接需要 `.app` 包。请改用 `pnpm tauri build --debug --bundles app`
### Docker:首次运行构建很慢
首次 Docker 构建从源码编译 Rust + tauri-driver。后续运行使用缓存层。Cargo registry 和 git 源通过 Docker volume 缓存。
## SpecNotifications
**文件**`app/test/e2e/specs/notifications.spec.ts`
通过实时 core sidecar 和 Notifications UI 页面测试 notification RPC 方法:
- `notification_ingest`,通过 core RPC 创建新通知
- `notification_list`,验证摄入的通知被返回
- `notification_mark_read`,将通知标记为已读
- `notification_stats`,检查聚合统计形状
- UINotifications 页面渲染集成通知部分(`[data-testid="integration-notifications-section"]`
- UINotifications 页面显示 System Events 部分(`[data-testid="system-events-section"]`
**运行**
```bash
bash app/scripts/e2e-run-spec.sh test/e2e/specs/notifications.spec.ts notifications
```
**平台说明**RPC 测试(`notification_ingest``notification_list``notification_mark_read``notification_stats`)为 Linux/tauri-driver 和 macOS/Appium Mac2 编写,但默认 CEF 运行时的 Linux 执行被禁用,直到存在 CEF 兼容驱动。UI 断言(Notifications 页面部分)需要 `browser.execute()` 支持,因此当 `supportsExecuteScript()` 返回 `false` 时,它们在 Mac2 上自动跳过。
---
## Agent 可观测的工件流
对于一种规范的、可检查的 run,将截图、页面源码 dump 和 mock 请求日志写入磁盘:
```bash
bash app/scripts/e2e-agent-review.sh
```
工件落在 `app/test/e2e/artifacts/<timestamp>-agent-review/`。完整详情 + 辅助 API[`AGENT-OBSERVABILITY.md`](agent-observability.md)。任何失败的测试都会触发 `wdio.conf.ts``afterTest` hook,将 `failure-*.png` + `failure-*.source.xml` 写入同一运行目录。
---
## Rust 推理提供商 E2E
这些测试(`tests/inference_provider_e2e.rs`)使用 **wiremock** 模拟 HTTP upstream,不需要实时 LLM API 调用。它们覆盖 OpenAI 兼容聊天、Anthropic 认证风格、每模型温度抑制、Ollama 本地提供商和 `/v1` HTTP 端点认证层。
```bash
# 本地:
bash scripts/test-rust-inference-e2e.sh
# 通过 DockerLinux,与 CI 相同镜像):
docker compose -f e2e/docker-compose.yml run --rm inference-e2e
```
+248
View File
@@ -0,0 +1,248 @@
---
description: 如何从源码构建 OpenHuman —— 工具链、 vendored Tauri CLI、sidecar staging。
icon: wrench
lang: zh-CN
---
# 构建与安装 OpenHuman
本指南涵盖完整的桌面/源码安装路径和发布安装包。
如果你只需要在新机器上运行仓库根目录的 Rust crate,请使用[构建 Rust 核心](building-rust-core.zh-CN.md)。该页面记录了固定的 Rust 工具链、OS 包前置条件以及 `openhuman-core` 的精确 `cargo` 命令。
本指南涵盖两条路径:
1. 从源码构建并编译 OpenHuman
2. 安装最新的稳定发布二进制文件
## 前置条件
- `git`
- Node.js 24 或更高版本(见 `app/package.json`
- `pnpm@10.10.0`(见根目录 `package.json``packageManager` 字段)
- 通过 `rustup` 安装的 Rust 1.93.0,含 `rustfmt``clippy`(见 `rust-toolchain.toml`
- CMake,原生 Rust 依赖所需
- `app/src-tauri/vendor/` 下的 Git 子模块,vendored CEF-aware Tauri CLI 所需
- 平台桌面构建工具:macOS 上的 Xcode Command Line Tools,或 Linux 上的 Tauri GTK/WebKit/AppIndicator 包集合
macOS Homebrew 快速开始:
```bash
brew install node@24 pnpm rustup-init cmake
rustup toolchain install 1.93.0 --profile minimal
rustup component add rustfmt clippy --toolchain 1.93.0
```
Arch Linux 快速开始:
```bash
sudo pacman -S --needed nodejs npm rustup cmake base-devel clang openssl \
alsa-lib xdotool libxtst libxi libevdev gtk3 webkit2gtk-4.1 \
libayatana-appindicator librsvg patchelf nss nspr at-spi2-core \
libcups libdrm libxkbcommon libxcomposite libxdamage libxfixes \
libxrandr mesa pango cairo libxshmfence
npm install -g pnpm@10.10.0
rustup toolchain install 1.93.0 --profile minimal
rustup component add rustfmt clippy --toolchain 1.93.0
```
## 从源码构建(本地编译)
从仓库根目录运行:
```bash
# 1) 克隆并进入仓库
git clone https://github.com/tinyhumansai/openhuman.git
cd openhuman
# 2) 获取 vendored Tauri/CEF 源码
git submodule update --init --recursive
# 3) 安装 JS 依赖(workspace
pnpm install
# 4) 构建 Rust 核心二进制文件
cargo build --manifest-path Cargo.toml --bin openhuman-core
# 5) 运行桌面 staging hook(当前为 no-op;为脚本兼容性保留)
cd app
pnpm core:stage
# 6) 构建桌面应用产物
pnpm build
```
本地开发(而非生产构建):
```bash
# 仅 Web UI 开发:在上述 cd app 步骤后,在 app/ 内运行
pnpm dev
# 使用 vendored Tauri/CEF CLI 的桌面应用开发:从 workspace 根目录运行
cd ..
pnpm --filter openhuman-app dev:app
```
## 安装最新稳定版(macOS/Linux x64
主要安装命令:
```bash
curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash
```
安装器行为:
- 解析你平台的最新稳定 OpenHuman 发布版本
- 可用时验证产物摘要
- 本地安装(默认不需要 sudo
- macOS:将 `OpenHuman.app` 安装到 `~/Applications`
- Linux x64:将 AppImage 安装为 `~/.local/bin/openhuman` 并写入桌面入口
实用 flag
```bash
# 预览操作而不写入文件
curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash -s -- --dry-run
```
## Windows(最新稳定版)
使用 PowerShell
```powershell
irm https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.ps1 | iex
```
Windows 安装器行为:
- 解析最新稳定版
- 下载 x64 的 MSI/EXE
- 可用时验证摘要
- 在安装包支持的情况下执行按用户安装
## ARM Linux 构建(aarch64
ARM Linux 构建由于 CEF 和 GTK 依赖需要特殊处理。
### 前置条件
```bash
# 安装 xvfb 用于 headless 构建/测试
sudo apt install xvfb
```
### 构建
```bash
cd app
pnpm tauri build --target aarch64-unknown-linux-gnu
```
### 运行 ARM 二进制文件
该二进制文件需要设置 CEF 库路径:
### 选项 1 —— 直接调用
```bash
REL_DIR=app/src-tauri/target/aarch64-unknown-linux-gnu/release
CEF_DIR=$(ls -d "$REL_DIR"/build/cef-dll-sys-*/out/cef_linux_aarch64 2>/dev/null | head -n1)
export LD_LIBRARY_PATH="$CEF_DIR:$REL_DIR/deps:$REL_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
"$REL_DIR/OpenHuman" --no-sandbox
```
### 选项 2 —— Wrapper 脚本(推荐)
保存到 `~/bin/openhuman` 并赋予可执行权限(`chmod +x ~/bin/openhuman`):
```bash
#!/bin/bash
REL_DIR=/path/to/app/src-tauri/target/aarch64-unknown-linux-gnu/release
CEF_DIR=$(ls -d "$REL_DIR"/build/cef-dll-sys-*/out/cef_linux_aarch64 2>/dev/null | head -n1)
export LD_LIBRARY_PATH="$CEF_DIR:$REL_DIR/deps:$REL_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
exec "$REL_DIR/OpenHuman" --no-sandbox "$@"
```
### DEB 包安装
```bash
DEB_FILE=$(ls app/src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/deb/OpenHuman_*_arm64.deb | head -n1)
sudo dpkg -i "$DEB_FILE"
```
### GTK 初始化修复
ARM 构建需要 GTK 在 Tauri 创建系统托盘之前初始化。这在 `vendor/tauri-cef/crates/tauri-runtime-cef/src/lib.rs` 中处理:
```rust
// CEF 初始化后,添加:
#[cfg(target_os = "linux")]
{
gtk::init().ok();
}
```
如果托盘初始化失败并提示 "GTK has not been initialized",请确保此修复已到位后重新构建。
全平台手动下载链接:
- 网站:https://tinyhuman.ai/openhuman
- 最新发布:https://github.com/tinyhumansai/openhuman/releases/latest
## 故障排除
### macOS`pnpm dev:app` 退出并提示 "CEF cache is held by another OpenHuman instance"
**症状**
`pnpm dev:app`(或 Tauri 壳层的任何 debug 构建)在窗口出现前退出,提示类似:
```text
[openhuman] CEF cache at /Users/<you>/Library/Caches/com.openhuman.app/cef is held by another OpenHuman instance (host <hostname>, pid 12345).
Quit the running instance and try again.
Workaround:
pkill -f "OpenHuman.app/Contents"
pkill -f "openhuman-core"
```
**原因**
CEFChromium Embedded Framework)通过 `~/Library/Caches/com.openhuman.app/cef` 下的 `SingletonLock` 符号链接对其用户数据目录持有独占锁。已安装的 `.app` 包和开发二进制文件使用相同的标识符(`com.openhuman.app`),因此它们无法并排运行。如果没有 preflight,`cef::initialize` 会返回失败,而 vendored `tauri-runtime-cef` 会以 Rust 回溯和无可操作消息的方式 panic(这是 preflight 落地前的 issue #864)。
**修复**
退出另一个 OpenHuman 实例并重新运行。最快路径:
```bash
pkill -f "OpenHuman.app/Contents"
pkill -f "openhuman-core"
pnpm dev:app
```
如果锁是由崩溃进程留下的(PID 已不存在),preflight 会自动移除陈旧的 `SingletonLock`,开发启动将继续,无需手动清理。
**已知限制**
开发和发布构建仍然共享 `com.openhuman.app` 作为缓存标识符。将开发隔离到单独的 `com.openhuman.app.dev` 缓存需要修改 vendored `tauri-runtime-cef`(缓存路径在运行时内部从 bundle 标识符构建,未暴露给 openhuman 壳层)。作为 #864 的后续跟踪。
### 核心端口上的陈旧 `openhuman` RPC 进程
**症状**
之前的 Tauri 构建或 `openhuman-core run` harness 在 `OPENHUMAN_CORE_PORT`(默认 `7788`)上留下了一个监听进程。在 issue #1130 之前,新的 Tauri 构建会静默附加到该监听器,导致版本漂移,以及新构建的 `OPENHUMAN_CORE_TOKEN` 不匹配时出现 401。
**当前行为(issue #1130**
`core_process::ensure_running` 现在在启动时探测端口:
- 如果 `GET /` 将监听器识别为 OpenHuman 核心(JSON body 含 `"name": "openhuman"`),则将其视为之前运行的陈旧进程并主动终止(Unix 上 `SIGTERM`750ms 后 `SIGKILL`Windows 上 `taskkill /F /T /PID`)。Tauri 主机随后会生成自己的全新嵌入式核心。
- 如果监听器是其他东西(或不讲 HTTP),启动会大声失败,并在日志中显示冲突,而非静默附加。
- 设置 `OPENHUMAN_CORE_REUSE_EXISTING=1` 以选择回到遗留的 attach-to-anything 行为,在将 `openhuman-core run` 作为手动调试 harness 运行时很有用。
**手动清理(仍然有效)**
```bash
pkill -f "OpenHuman.app/Contents"
pkill -f "openhuman-core"
```
@@ -0,0 +1,157 @@
---
description: OpenHuman 如何测试其产品 —— Vitest、cargo test、WDIO E2E。每种测试该放哪里。
icon: vial
lang: zh-CN
---
# 测试策略
OpenHuman 如何测试其产品。"我的测试该放哪里?"的权威答案。 companion 文档为 [`TEST-COVERAGE-MATRIX.md`](../../docs/TEST-COVERAGE-MATRIX.md)。
---
## 测试层级
| 层级 | 存放位置 | 测试内容 | 驱动方式 |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Rust 单元测试** | 同一 `*.rs` 文件内的 `#[cfg(test)] mod tests`,或同级 `tests.rs`,或域名下的 `tests/` 子目录(例如 `src/openhuman/channels/tests/` | 纯领域逻辑、schema、RPC handler 形态、内存状态机 | `cargo test` |
| **Rust 集成测试** | 仓库根目录的 `tests/*.rs` | 完整领域接线,含真实 Tokio 运行时、模拟外部服务、JSON-RPC 端到端(`tests/json_rpc_e2e.rs`)、领域 × 领域交互 | `pnpm test:rust`(调用 `bash scripts/test-rust-with-mock.sh` |
| **Vitest 单元测试** | 与源码共存于 `app/src/**` 下的 `*.test.ts(x)`,或 `app/src/**/__tests__/` 下 | React 组件、hook、store slice、纯工具函数、service 层适配器 | `pnpm test:unit` |
| **WDIO E2E** | `app/test/e2e/specs/*.spec.ts` | 完整桌面流程:UI → Tauri → core sidecar → JSON-RPC;用户可见行为 | Linux CI: `tauri-driver`(端口 4444)。macOS 本地: Appium Mac2(端口 4723)。详见 [E2E 测试](e2e-testing.zh-CN.md)。 |
| **手动冒烟测试** | [`docs/RELEASE-MANUAL-SMOKE.md`](../../docs/RELEASE-MANUAL-SMOKE.md) | 驱动程序无法断言的 OS 级表面:TCC 权限弹窗、Gatekeeper、代码签名、DMG 安装、OS 原生通知 | 发布切割时由人工执行,在发布 PR 中签字确认 |
---
## 决策树 —— 我的测试该放哪里?
```text
变更是否在 JSON-RPC 边界之后(在 src/ 中)?
├─ 是 —— 是否跨领域或与外部服务通信?
│ ├─ 是 → Rust 集成测试 (tests/*.rs)
│ └─ 否 → Rust 单元测试(源码旁)
└─ 否 —— 变更在 app/ 中
├─ 是纯函数、hook、slice 或独立组件?
│ └─ 是 → Vitest 单元测试 (*.test.tsx 与源码共存)
└─ 是否用户可见 且 跨越 UI ⇄ Tauri ⇄ sidecar ⇄ JSON-RPC
├─ 是 → WDIO E2E (app/test/e2e/specs/*.spec.ts)
└─ 是否 OS 级(TCC、Gatekeeper、安装、OS 通知)?
└─ 是 → 手动冒烟清单
```
如果一项变更触及多个层级,在**每个**触及的层级都写测试。不要用一层替代另一层。
---
## 失败路径要求
覆盖矩阵中的每个功能叶子节点,除了 happy path 外,**至少**还要有一个**失败 / 边界**断言。例如:
- 文件写入工具:happy = 写入了字节;failure = 路径限制拒绝。
- OAuth 流程:happy = 签发了 tokenedge = 过期刷新 token 恢复。
- 记忆存储:happy = 存储并召回;edge = 遗忘后再召回返回空。
只断言 happy path 的 spec 是不完整的。
---
## Mock 策略
- **单元 / 集成 / E2E 中禁止真实网络。** 使用共享 mock 后端(`scripts/mock-api-core.mjs``scripts/mock-api-server.mjs``app/test/e2e/mock-server.ts`)。
- 测试用 admin 端点:`GET /__admin/health``POST /__admin/reset``POST /__admin/behavior``GET /__admin/requests`
- **外部服务**Telegram、Slack、Gmail、Notion、Ollama、OpenAI 等)在 mock 后端层面被 stub;测试通过 `getRequestLog()` 断言请求形态。
- 唯一可接受的例外是记录在案的发布切割手动冒烟步骤。
---
## 确定性规则
- 禁止 wall-clock 等待,使用 `waitForApp``waitForAppReady``waitForWebView` 辅助函数,或显式的元素就绪谓词。
- 禁止共享文件系统状态,每个 E2E spec 在隔离的 `OPENHUMAN_WORKSPACE` 中运行(由 `app/scripts/e2e-run-spec.sh` 创建/清理)。
- 禁止顺序依赖的 spec,每个 spec 必须能独立通过。
- 禁止依赖绝对坐标或动画时序。
- 禁止在 tauri-driver 上通过 `browser.keys()` 使用真实键盘,通过 `browser.execute(...)` 合成(参见 `command-palette.spec.ts` 中的模式)。
---
## 现有 harness 提供的能力
- **Mock 后端引导**`app/test/e2e/mock-server.ts` 中的 `startMockServer` / `stopMockServer`
- **Auth 捷径**`helpers/deep-link-helpers.ts` 中的 `triggerAuthDeepLink` / `triggerAuthDeepLinkBypass` 跳过真实 OAuth。
- **元素辅助函数**`helpers/element-helpers.ts` 中的 `clickNativeButton``waitForWebView``clickToggle`,在 spec 中使用这些代替原始的 `XCUIElementType*` 选择器。
- **共享流程**`helpers/shared-flows.ts` 中的 `completeOnboardingIfVisible``navigateViaHash``navigateToSkills``walkOnboarding`
- **从 spec 调用 Core RPC**`helpers/core-rpc.ts` 中的 `callOpenhumanRpc`,当 UI 步骤可能脆弱时直接驱动 sidecar。
- **平台守卫**`helpers/platform.ts` 中的 `isTauriDriver``isMac2``supportsExecuteScript`
- **失败时捕获工件**`captureFailureArtifacts``wdio.conf.ts` 运行,截图 + DOM dump 输出到 `app/test/e2e/artifacts/`
---
## 命名与结构规范
- WDIO spec:端到端产品流用 `<feature-area>-flow.spec.ts`;更窄的表面用 `<feature>.spec.ts`
- Vitest 同位置:优先 `Component.tsx` + `Component.test.tsx` 同级;仅在组合多个相关测试时使用 `__tests__/`
- Rust 集成测试:文件名用 snake_case 匹配表面,JSON-RPC 驱动流用 `<feature>_e2e.rs`,跨领域用 `<feature>_integration.rs`
- 每个 `describe` / `mod tests` 块对应一个功能列表 ID 范围,如果映射不明显,在注释中链接矩阵行。
---
## 合并前门禁
开 PR 前运行。CI 会跑同一套,但本地更快:
```bash
# Rust 核心
cargo fmt --check
cargo check --manifest-path Cargo.toml
cargo clippy --manifest-path Cargo.toml -- -D warnings
cargo test --manifest-path Cargo.toml
# Tauri 壳层
cargo check --manifest-path app/src-tauri/Cargo.toml
# 前端
pnpm typecheck
pnpm lint
pnpm format:check
pnpm test:unit
# 带 mock 后端的 Rust 集成测试
pnpm test:rust
# E2E(慢 —— 仅在行为用户可见变更时运行)
pnpm test:e2e:build
bash app/scripts/e2e-run-spec.sh test/e2e/specs/<your-spec>.spec.ts <id>
```
---
## 无法被驱动程序自动化的 —— 需要手动冒烟
某些表面无法被 WDIO / Appium 驱动,因为它们跨越 OS 级信任边界或硬件路径。完整的清单 + 签字块位于 [`docs/RELEASE-MANUAL-SMOKE.md`](../../docs/RELEASE-MANUAL-SMOKE.md),该文件是每次发布必须验证内容的权威来源。涵盖示例:
- macOS TCC 权限弹窗(辅助功能、输入监控、屏幕录制、麦克风)
- Gatekeeper 首次启动签名验证
- 代码签名完整性(`codesign --verify --deep --strict`
- DMG 安装 / 拖入 Applications 流程
- 自动更新下载 + 重启
- Linux OS 原生通知 toast(无显示服务器的 driver 无法看见 Xvfb 之外的 Linux
如果一项功能没有自动化覆盖,也不在手动冒烟清单上,视为未测试,开一个覆盖缺口。
---
## 覆盖矩阵即契约
[覆盖矩阵](../../docs/TEST-COVERAGE-MATRIX.md) 中的每个功能叶子节点映射到:
1. 一个或多个测试路径,**或**
2. 一个合理的 `🚫` 并附手动冒烟条目。
当你添加 / 删除 / 重命名功能时,**在同一 PR 中更新矩阵行**。CI 将在 #965 落地后守卫此契约。
---
## 不确定时
- 尽可能把测试推到层级栈的**底层**(Rust 单元 > Rust 集成 > Vitest > WDIO)。更低层级更快、更确定、运行成本更低。
- WDIO 用于真正跨越 UI ⇄ Tauri ⇄ sidecar ⇄ JSON-RPC 的行为。不要仅仅因为 UI 存在就通过 WDIO 驱动一个可单元测试的关注点。
- 失败的 happy path 是回归。缺失的失败路径测试是缺口。两者都是 bug。