mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Docs/i18n batch c1 developing foundation (#2504)
Co-authored-by: agent:skill-master <skill-master@openclaw>
This commit is contained in:
co-authored by
agent:skill-master <skill-master@openclaw>
parent
788087cc0a
commit
3299c16933
@@ -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,基于根本的性能和安全原因:
|
||||
|
||||
| 指标 | OpenHuman(Tauri + Rust) | 典型 Electron 应用 |
|
||||
| ------------------------- | -------------------------------------------------------- | ---------------------------- |
|
||||
| 二进制体积 | 取决于功能(CEF 运行时 + 技能包占主导) | ~150 MB+ |
|
||||
| 每技能上下文内存 | ~1-2 MB(QuickJS) | ~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 VM;JavaScript 行为通过共享运行时桥接流动。
|
||||
|
||||
**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 token,64 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
|
||||
桥接 API:net.rs 通过 reqwest 发起 HTTP 请求(无 CORS,rustls 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** | MCP(JSON-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 流程中的位置。
|
||||
@@ -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` 表示 Linux(tauri-driver session)
|
||||
- `isMac2()`,`true` 表示 macOS(Appium 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 缓存。
|
||||
|
||||
## Spec:Notifications
|
||||
|
||||
**文件**:`app/test/e2e/specs/notifications.spec.ts`
|
||||
|
||||
通过实时 core sidecar 和 Notifications UI 页面测试 notification RPC 方法:
|
||||
|
||||
- `notification_ingest`,通过 core RPC 创建新通知
|
||||
- `notification_list`,验证摄入的通知被返回
|
||||
- `notification_mark_read`,将通知标记为已读
|
||||
- `notification_stats`,检查聚合统计形状
|
||||
- UI:Notifications 页面渲染集成通知部分(`[data-testid="integration-notifications-section"]`)
|
||||
- UI:Notifications 页面显示 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
|
||||
|
||||
# 通过 Docker(Linux,与 CI 相同镜像):
|
||||
docker compose -f e2e/docker-compose.yml run --rm inference-e2e
|
||||
```
|
||||
@@ -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"
|
||||
```
|
||||
|
||||
**原因**
|
||||
|
||||
CEF(Chromium 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 = 签发了 token;edge = 过期刷新 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。
|
||||
Reference in New Issue
Block a user