3855 lines
196 KiB
Markdown
3855 lines
196 KiB
Markdown
# Agent Client 技术设计规格文档
|
||
|
||
> 版本:v1.1.0
|
||
> 最后更新:2026-01-29
|
||
> 状态:设计中
|
||
|
||
## 目录
|
||
|
||
1. [功能需求总览](#1-功能需求总览)
|
||
- [1.1 开发阶段划分](#11-开发阶段划分)
|
||
- [1.2 功能优先级分类](#12-功能优先级分类)
|
||
- [1.3 Feature 控制表](#13-feature-控制表)
|
||
- [1.4 功能与组件映射表](#14-功能与组件映射表)
|
||
2. [UI 界面详细设计](#2-ui-界面详细设计)
|
||
- [2.1 主窗口布局](#21-主窗口布局)
|
||
- [2.2 客户端信息 Tab](#22-客户端信息-tabid-和密码界面)
|
||
- [2.3 设置 Tab](#23-设置-tab)
|
||
- [2.4 依赖管理 Tab](#24-依赖管理-tab)
|
||
- [2.5 Agent 状态显示](#25-agent-状态显示)
|
||
- [2.6 权限设置 Tab](#26-权限设置-tab)
|
||
- [2.7 关于 Tab](#27-关于-tab)
|
||
- [2.8 远程桌面 Tab](#28-远程桌面-tab远程控制客户端)
|
||
- [2.9 Agent 聊天界面](#29-agent-聊天界面可选功能)
|
||
- [2.10 文件传输界面](#210-文件传输界面)
|
||
- [2.11 客户端升级](#211-客户端升级)
|
||
- [2.12 消息通知系统](#212-消息通知系统)
|
||
- [2.13 系统托盘菜单](#213-系统托盘菜单)
|
||
3. [组件选型与使用](#3-组件选型与使用)
|
||
4. [核心模块设计](#4-核心模块设计)
|
||
5. [通信协议设计](#5-通信协议设计)
|
||
- [5.1 Protobuf 消息类型](#51-protobuf-消息类型)
|
||
- [5.2 连接流程](#52-连接流程)
|
||
- [5.3 完整消息流转流程](#53-完整消息流转流程browser-→-agent-执行)
|
||
- [5.4 客户端 Agent 执行机制](#54-客户端-agent-执行机制)
|
||
- [5.5 业务数据通道设计](#55-业务数据通道设计)
|
||
- [5.6 远程桌面消息流程](#56-远程桌面消息流程)
|
||
- [5.7 文件传输消息流程](#57-文件传输消息流程)
|
||
- [5.8 Protobuf 消息定义示例](#58-protobuf-消息定义示例)
|
||
- [5.9 版本兼容性策略](#59-版本兼容性策略)
|
||
- [5.10 安全设计](#510-安全设计)
|
||
- [5.11 网络策略](#511-网络策略)
|
||
- [5.12 国际化方案](#512-国际化方案)
|
||
- [5.13 会话管理](#513-会话管理)
|
||
- [5.14 系统集成](#514-系统集成)
|
||
6. [系统架构](#6-系统架构)
|
||
7. [目录结构](#7-目录结构)
|
||
8. [阶段一验收测试用例](#8-阶段一验收测试用例)
|
||
- [8.1 测试类型说明](#81-测试类型说明)
|
||
- [8.2 单元测试用例](#82-单元测试用例)
|
||
- [8.3 集成测试用例](#83-集成测试用例)
|
||
- [8.4 端到端测试用例](#84-端到端测试用例)
|
||
- [8.5 手动验收测试清单](#85-手动验收测试清单)
|
||
- [8.6 测试覆盖率要求](#86-测试覆盖率要求)
|
||
- [8.7 性能验收指标](#87-性能验收指标)
|
||
|
||
---
|
||
|
||
## 1. 功能需求总览
|
||
|
||
### 1.1 开发阶段划分
|
||
|
||
本项目采用**分阶段开发**策略,明确各阶段目标和范围:
|
||
|
||
#### 阶段一:核心通信与 UI(当前优先级)
|
||
|
||
**目标**:验证 `agent-server-admin` 与 `agent-client` 之间的双向通信能力,确保 `data-server` 的 P2P/TCP/WebSocket 通信正常工作。
|
||
|
||
| 功能模块 | 说明 | 优先级 |
|
||
|----------|------|--------|
|
||
| 基础运行能力 | 跨平台运行、系统托盘、开机自启动 | P0 |
|
||
| 连接管理 | 连接 data-server、P2P/Relay 模式、心跳保活、重连 | P0 |
|
||
| 客户端信息 | 显示 ID、显示/隐藏密码、复制功能、修改密码 | P0 |
|
||
| 设置界面 | data-server 地址配置 | P0 |
|
||
| 权限管理 | 屏幕录制、辅助功能权限检测与引导 | P0 |
|
||
| 依赖管理 | Node.js 自动安装、npm 工具安装 | P0 |
|
||
| 跨平台打包 | Windows/macOS/Linux 安装包 | P0 |
|
||
|
||
**验证指标**:
|
||
- [ ] agent-client 可在 macOS/Windows/Linux 成功打包安装
|
||
- [ ] agent-client 能成功连接 data-server(hbbs/hbbr)
|
||
- [ ] agent-server-admin 能发现并连接 agent-client
|
||
- [ ] 双向通信正常(消息可发送/接收)
|
||
- [ ] 支持 P2P 直连(局域网)
|
||
- [ ] 支持 Relay 中继(跨公网)
|
||
- [ ] 支持 WebSocket 兼容模式(防火墙环境)
|
||
|
||
#### 阶段二:Agent 运行时集成(后续开发)
|
||
|
||
**目标**:集成 agent_runner,实现 Agent 任务执行、进度回传等功能。
|
||
|
||
| 功能模块 | 说明 | 优先级 |
|
||
|----------|------|--------|
|
||
| Agent 执行 | 任务执行、输出回传、任务取消 | P1 |
|
||
| Agent 状态 | 状态栏显示 agent 进程运行状态 | P1 |
|
||
| 远程桌面 | 屏幕捕获、输入模拟 | P1 |
|
||
| 文件传输 | 发送/接收文件 | P1 |
|
||
| 日志调试 | 开发者模式日志、导出日志 | P1 |
|
||
| 客户端升级 | 检查更新、下载安装 | P2 |
|
||
| 聊天界面 | Agent 聊天 UI | P2 |
|
||
|
||
### 1.2 功能优先级分类
|
||
|
||
| 优先级 | 功能模块 | 说明 |
|
||
|--------|----------|------|
|
||
| **P0 (高优先级)** | 基础运行能力 | 跨平台运行、系统托盘、开机自启动、Tab 导航 |
|
||
| **P0** | 连接管理 | 连接 data-server、P2P/Relay 模式、心跳保活 |
|
||
| **P0** | 客户端信息 | 显示 ID、显示/隐藏密码、复制功能、修改密码 |
|
||
| **P0** | 设置界面 | data-server 地址配置 |
|
||
| **P0** | 权限管理 | 屏幕录制、辅助功能权限检测与引导 |
|
||
| **P0** | 依赖管理 | Node.js 自动安装、npm 工具安装 |
|
||
| **P1 (中优先级)** | 增强功能 | 主题切换、语言设置、工作目录、文件传输 |
|
||
| **P1** | 远程桌面 | 屏幕捕获、输入模拟 |
|
||
| **P1** | Agent 状态 | 状态栏显示 agent 进程运行状态 |
|
||
| **P1** | Agent 执行 | 任务执行、输出回传 |
|
||
| **P1** | 日志调试 | 开发者模式日志、导出日志 |
|
||
| **P1** | 关于界面 | 版本信息、链接 |
|
||
| **P2 (低优先级)** | 进阶功能 | 客户端升级、聊天界面 |
|
||
|
||
### 1.2 Feature 控制表
|
||
|
||
| Feature | 默认启用 | 控制功能 |
|
||
|---------|----------|----------|
|
||
| `tray` | Yes | 系统托盘图标 |
|
||
| `auto-launch` | Yes | 开机自启动 |
|
||
| `chat-ui` | No | Agent 聊天界面 |
|
||
| `remote-desktop` | No | 远程桌面功能 |
|
||
| `dependency-management` | Yes | 依赖管理界面 |
|
||
| `file-transfer` | Yes | 文件传输功能 |
|
||
| `dev-mode` | No | 开发者模式日志 |
|
||
|
||
### 1.3 功能与组件映射表
|
||
|
||
| 功能需求 | 依赖组件 | 使用模块 |
|
||
|----------|----------|----------|
|
||
| UI 框架 | `gpui` + `gpui-component` | Root, TabPanel, Input, Button, Switch, DataTable 等 |
|
||
| 系统托盘 | `tray-icon` + `muda` | TrayIconBuilder, Menu, MenuItem |
|
||
| 开机自启动 | `auto-launch` | AutoLaunchBuilder |
|
||
| P2P/Relay 连接 | `qiming-rustdesk` | RendezvousMediator, ConnectionType |
|
||
| 屏幕捕获 | `qiming-rustdesk` (scrap) | ScrapManager |
|
||
| 输入模拟 | `enigo` | Enigo, Key, Button |
|
||
| Agent 运行时 | `rcoder` | AgentWorkerManager |
|
||
| 跨平台打包 | `cargo-packager` | Config, PackageFormat |
|
||
| 配置管理 | `serde` + `toml` | AppConfig |
|
||
|
||
---
|
||
|
||
## 2. UI 界面详细设计
|
||
|
||
### 2.1 主窗口布局
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
│ [qiming-agent ×] │
|
||
├─────────────────────────────────────────────────────────────────────────────┤
|
||
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ │
|
||
│ │ │ 客户端 │ 设置 │ 依赖管理 │ 权限 │ 关于 │ │
|
||
│ │ │ 信息 │ │ │ 设置 │ │ │
|
||
│ │ └─────────────┴─────────────┴─────────────┴─────────────┘ │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
||
│ │ │ │ │ │
|
||
│ │ │ Tab 内容区域 │ │ │
|
||
│ │ │ │ │ │
|
||
│ │ └─────────────────────────────────────────────────────────────┘ │ │
|
||
│ │ │ │
|
||
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||
├─────────────────────────────────────────────────────────────────────────────┤
|
||
│ [ID: 12345678] ● 已连接 (P2P) - 32ms │ Agent: 空闲 │ 依赖: ✓ 正常 │ 10:30:45│
|
||
└─────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**状态栏元素**:
|
||
- 左侧:客户端 ID(可点击复制)| 连接状态(图标 + 文字 + 延迟)
|
||
- 中左:Agent 状态(空闲/运行中/错误)
|
||
- 中右:依赖状态(绿色✓表示正常,红色⚠表示有问题)
|
||
- 右侧:当前时间
|
||
|
||
**状态栏布局**:
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────────────┐
|
||
│ [ID: 12345678] ● 已连接 (P2P) - 32ms │ Agent: 空闲 │ 依赖: ✓ 正常 │ 10:30:45 │
|
||
└─────────────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**状态图标定义**:
|
||
| 状态 | 图标 | 颜色 | 说明 |
|
||
|------|------|------|------|
|
||
| 连接成功 | ● | 绿色 | P2P 直连 |
|
||
| 连接成功 | ◑ | 黄色 | Relay 中继 |
|
||
| 连接中 | ○ | 蓝色(闪烁) | 正在连接 |
|
||
| 断开连接 | ● | 红色 | 连接断开 |
|
||
| Agent 空闲 | ✓ | 灰色 | 空闲状态 |
|
||
| Agent 运行中 | ⟳ | 蓝色(旋转) | 任务执行中 |
|
||
| Agent 错误 | ✗ | 红色 | 任务出错 |
|
||
| 依赖正常 | ✓ | 绿色 | 所有依赖就绪 |
|
||
| 依赖异常 | ⚠ | 红色 | 部分依赖缺失 |
|
||
|
||
---
|
||
|
||
### 2.2 客户端信息 Tab(ID 和密码界面)
|
||
|
||
#### 2.2.1 界面功能
|
||
|
||
**核心功能**:
|
||
1. 显示客户端唯一 ID(只读,服务器分配,不可修改)
|
||
2. 显示连接密码(可切换显示/隐藏)
|
||
3. 复制 ID 和密码到剪贴板
|
||
4. **密码修改入口**:点击按钮跳转到设置 Tab 的"安全"子页面
|
||
|
||
#### 2.2.2 详细布局
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 您的客户端 ID │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────────────────────────────────────┐ │ │
|
||
│ │ │ │ │ │
|
||
│ │ │ 12345678 │ │ │
|
||
│ │ │ │ │ │
|
||
│ │ │ 8 位数字 ID │ │ │
|
||
│ │ │ │ │ │
|
||
│ │ └─────────────────────────────────────────────┘ │ │
|
||
│ │ │ │
|
||
│ │ [📋 复制 ID] │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 连接密码 │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────────────────────────────────────┐ │ │
|
||
│ │ │ 🔒 **********●****** │ │ │
|
||
│ │ └─────────────────────────────────────────────┘ │ │
|
||
│ │ │ │
|
||
│ │ [📋 复制密码] [👁 显示/隐藏] │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ [🔐 修改密码] → 跳转至 设置 → 安全 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ ⚠️ 此密码用于远程连接验证,请妥善保管 │ │
|
||
│ │ 修改密码需要验证原密码 │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.2.3 交互逻辑
|
||
|
||
| 交互 | 触发动作 | 使用组件 |
|
||
|------|----------|----------|
|
||
| 点击 "复制 ID" | 将 ID 写入系统剪贴板 | `cx.write_to_clipboard()` |
|
||
| 点击 "复制密码" | 将密码写入系统剪贴板 | `cx.write_to_clipboard()` |
|
||
| 点击 "显示/隐藏" | 切换密码掩码状态 | `password_masked` 状态 |
|
||
| 点击 "修改密码" | 切换到设置 Tab,激活"安全"子页面 | `cx.set_active_tab(Settings)` + `cx.set_settings_subtab(Security)` |
|
||
|
||
#### 2.2.4 状态定义
|
||
|
||
```rust
|
||
/// 客户端信息状态
|
||
struct ClientInfoState {
|
||
/// 客户端 ID(只读)
|
||
client_id: String,
|
||
|
||
/// 密码(掩码状态)
|
||
password_masked: bool,
|
||
}
|
||
|
||
/// 密码修改状态(在设置 Tab - 安全子页面中管理)
|
||
struct PasswordChangeState {
|
||
/// 原密码
|
||
old_password: String,
|
||
|
||
/// 新密码
|
||
new_password: String,
|
||
|
||
/// 确认密码
|
||
confirm_password: String,
|
||
|
||
/// 密码强度
|
||
strength: PasswordStrength,
|
||
|
||
/// 修改状态
|
||
status: PasswordChangeStatus,
|
||
}
|
||
|
||
enum PasswordStrength {
|
||
Weak, // < 8 字符,红色
|
||
Medium, // >= 8 字符,无特殊字符,黄色
|
||
Strong, // >= 8 字符 + 特殊字符,绿色
|
||
}
|
||
|
||
enum PasswordChangeStatus {
|
||
Idle,
|
||
Validating, // 验证原密码
|
||
Changing, // 修改中
|
||
Success, // 修改成功
|
||
Error(String), // 错误信息
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### 2.2.5 修改密码弹框(设置 Tab - 安全子页面)
|
||
|
||
**弹框触发**:在设置 Tab → 安全子页面,点击"修改密码"按钮
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ ┌─────────────────────────┐ │
|
||
│ │ 修改密码 │ │
|
||
│ └─────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ 原密码: [🔒 ****************] [👁 显示/隐藏] │ │
|
||
│ │ │ │
|
||
│ │ 新密码: [🔒 ****************] [👁 显示/隐藏] │ │
|
||
│ │ 密码强度: [弱 ●○○] [中 ○●○] [强 ○○●] │ │
|
||
│ │ 至少 8 位,包含字母和数字 │ │
|
||
│ │ │ │
|
||
│ │ 确认新密码: [🔒 ****************] [👁 显示/隐藏] │ │
|
||
│ │ 两次输入不一致时显示红色提示 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ 错误信息: 原密码验证失败 │ │
|
||
│ │ 成功信息: 密码修改成功! │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ [✗ 取消] [✓ 确认修改] │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**验证规则**:
|
||
1. 原密码必填,调用服务器验证
|
||
2. 新密码长度 >= 8 位
|
||
3. 新密码必须包含字母和数字(可选:特殊字符)
|
||
4. 两次新密码输入必须一致
|
||
5. 新密码不能与原密码相同
|
||
|
||
**验证时机**:
|
||
- 实时验证:输入时检查格式
|
||
- 提交时验证:再次检查所有规则
|
||
|
||
---
|
||
|
||
### 2.3 设置 Tab
|
||
|
||
#### 2.3.1 子页面结构
|
||
|
||
设置 Tab 包含以下子页面(左侧导航 + 右侧内容):
|
||
|
||
| 子页面 | 功能 |
|
||
|--------|------|
|
||
| **服务器** | 配置 data-server 地址 |
|
||
| **安全** | 修改密码、安全设置 |
|
||
| **常规** | 开机自启动、语言 |
|
||
| **外观** | 主题、工作目录 |
|
||
| **日志** | 日志级别、导出日志 |
|
||
|
||
**设置 Tab 布局**:
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 设置 [💾 保存] │
|
||
├──────────────────────────┬──────────────────────────────────────┤
|
||
│ │ │
|
||
│ [≡] 服务器 │ ┌─────────────────────────────┐ │
|
||
│ │ │ │ │
|
||
│ [🔒] 安全 │ │ 服务器配置内容 │ │
|
||
│ │ │
|
||
│ [⚙ │ │ ] 常规 │ │ │ │
|
||
│ │ └─────────────────────────────┘ │
|
||
│ [🎨] 外观 │ │
|
||
│ │ │
|
||
│ [📋] 日志 │ │
|
||
│ │ │
|
||
├──────────────────────────┴──────────────────────────────────────┤
|
||
│ [← 返回上一级] 当前: 设置 / 服务器 │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**面包屑导航**:顶部显示当前路径,如 `设置 / 服务器`,支持点击返回上级
|
||
|
||
#### 2.3.2 服务器配置子页面
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 设置 / 服务器配置 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 信令服务器 (hbbs) │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 地址: [______________________________] [:21116] │ │
|
||
│ │ │ │
|
||
│ │ 例: hbbs.example.com:21116 │ │
|
||
│ │ │ │
|
||
│ │ 用于注册客户端 ID 和协商 P2P 连接 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 中继服务器 (hbbr) │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 地址: [______________________________] [:21117] │ │
|
||
│ │ │ │
|
||
│ │ 例: relay.example.com:21117 │ │
|
||
│ │ │ │
|
||
│ │ 当 P2P 连接失败时,数据将通过此服务器转发 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ API 服务器 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 地址: [______________________________] [:8080] │ │
|
||
│ │ │ │
|
||
│ │ 用于 HTTP API 调用和 SSE 消息推送 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ [💾 保存] [↩️ 恢复默认] │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**服务器配置逻辑**:
|
||
|
||
| 配置项 | 说明 | 默认值 | 验证规则 |
|
||
|--------|------|--------|----------|
|
||
| 信令服务器地址 | hbbs 主机地址或域名 | `127.0.0.1` | 非空,支持域名和 IP |
|
||
| 信令服务器端口 | hbbs 监听端口 | `21116` | 1-65535 |
|
||
| 中继服务器地址 | hbbr 主机地址或域名 | `127.0.0.1` | 非空,支持域名和 IP |
|
||
| 中继服务器端口 | hbbr 监听端口 | `21117` | 1-65535 |
|
||
| API 服务器地址 | API 服务主机地址 | `127.0.0.1` | 非空,支持域名和 IP |
|
||
| API 服务器端口 | API 服务监听端口 | `8080` | 1-65535 |
|
||
|
||
**连接测试逻辑**:
|
||
|
||
1. 用户输入服务器地址后,可点击"连接测试"按钮验证连通性
|
||
2. 测试顺序:
|
||
- 测试 hbbs 连接(TCP)
|
||
- 测试 hbbr 连接(TCP)
|
||
- 测试 API 服务器连接(HTTP)
|
||
3. 测试结果反馈:
|
||
- ✅ 全部成功:显示"所有服务器连接正常"
|
||
- ❌ 部分失败:显示失败的服务器列表及错误原因
|
||
- 超时:显示"连接超时,请检查网络或服务器地址"
|
||
|
||
**连接流程**:
|
||
|
||
```
|
||
客户端启动
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 读取配置文件 │
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 1. 连接 hbbs (信令服务器) │
|
||
│ - TCP 连接: hbbs_addr:hbbs_port │
|
||
│ - 发送 RegisterPeer 注册 ID │
|
||
│ - 建立 WebSocket 长连接用于信令协商 │
|
||
└───────────────────────────┬─────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 2. 连接 hbbr (中继服务器) │
|
||
│ - TCP 连接: hbbr_addr:hbbr_port │
|
||
│ - 用于 P2P 失败时的数据中继 │
|
||
│ - 支持 WebSocket 连接 (兼容防火墙) │
|
||
└───────────────────────────┬─────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 3. 连接 API 服务器 │
|
||
│ - HTTP 连接: api_addr:api_port │
|
||
│ - 用于 SSE 实时消息推送 │
|
||
│ - 用于 HTTP API 调用 │
|
||
└───────────────────────────┬─────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌───────┴───────┐
|
||
│ 连接成功 │
|
||
│ 开始工作 │
|
||
└───────────────┘
|
||
```
|
||
|
||
**配置存储**:
|
||
|
||
- 配置文件路径:`~/Library/Application Support/qiming-agent/config/server.toml` (macOS)
|
||
- Windows: `%LOCALAPPDATA%\qiming-agent\config\server.toml`
|
||
- Linux: `~/.local/share/qiming-agent/config/server.toml`
|
||
|
||
```toml
|
||
# server.toml
|
||
[hbbs]
|
||
host = "hbbs.example.com"
|
||
port = 21116
|
||
|
||
[hbbr]
|
||
host = "relay.example.com"
|
||
port = 21117
|
||
|
||
[api]
|
||
host = "api.example.com"
|
||
port = 8080
|
||
```
|
||
|
||
**恢复默认配置**:将所有服务器地址重置为 `127.0.0.1` 和默认端口
|
||
|
||
#### 2.3.3 安全设置子页面(密码修改)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 设置 / 安全 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 客户端密码 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 此密码用于管理端 (agent-server-admin) 连接时验证 │ │
|
||
│ │ │ │
|
||
│ │ 状态: [✓ 已设置] [🔐 修改密码] │ │
|
||
│ │ 最后修改: 2026-01-15 14:30:00 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 安全建议 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ • 建议定期更换密码(每 90 天) │ │
|
||
│ │ • 使用强密码:至少 8 位,包含字母和数字 │ │
|
||
│ │ • 不要与其他账户使用相同密码 │ │
|
||
│ │ • 不要将密码告知他人 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**修改密码流程**:
|
||
1. 点击"修改密码"按钮 → 弹出密码修改对话框
|
||
2. 用户输入:新密码 → 确认新密码
|
||
3. 点击"确认修改" → 验证格式 → 保存新密码
|
||
4. 修改成功后,管理端需要重新配置新密码才能连接
|
||
|
||
**重要提示**:
|
||
- 密码修改后,所有已连接的管理端会立即断开
|
||
- 管理端需要更新配置文件中的密码才能重新连接
|
||
|
||
#### 2.3.4 常规设置子页面
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 设置 / 常规 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 开机启动 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ [✓] 开机时启动 qiming-agent │ │
|
||
│ │ │ │
|
||
│ │ Windows: 使用注册表 Run 键 │ │
|
||
│ │ macOS: 使用 LaunchAgent │ │
|
||
│ │ Linux: 使用 XDG Autostart │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 语言 / Language │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ [简体中文 ▼] │ │
|
||
│ │ [English ▼] │ │
|
||
│ │ [日本語 ▼] │ │
|
||
│ │ │ │
|
||
│ │ 需要重启应用生效 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 启动行为 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ [✓] 启动时最小化到系统托盘 │ │
|
||
│ │ [ ] 启动时显示主窗口 │ │
|
||
│ │ [ ] 关闭主窗口时最小化到托盘(不退出) │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.3.5 外观设置子页面
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 设置 / 外观 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 主题 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ (●) 浅色主题 │ │
|
||
│ │ ( ) 深色主题 │ │
|
||
│ │ ( ) 跟随系统 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 工作目录 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 用于存储文件传输和 Agent 运行时文件 │ │
|
||
│ │ │ │
|
||
│ │ [____________________________] [📁 浏览...] │ │
|
||
│ │ │ │
|
||
│ │ 当前: ~/Documents/qiming-agent │ │
|
||
│ │ 剩余空间: 256 GB │ │
|
||
│ │ │ │
|
||
│ │ ⚠️ 修改后需要重启应用生效 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 文件传输默认目录 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ (●) 使用工作目录 │ │
|
||
│ │ ( ) 每次询问保存位置 │ │
|
||
│ │ ( ) 指定目录: [________________] [📁 浏览...] │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.3.6 日志设置子页面
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 设置 / 日志 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 日志级别 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ ( ) 错误 (Error) │ │
|
||
│ │ ( ) 警告 (Warn) │ │
|
||
│ │ (●) 信息 (Info) │ │
|
||
│ │ ( ) 调试 (Debug) - 仅开发模式可用 │ │
|
||
│ │ ( ) 跟踪 (Trace) - 仅开发模式可用 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 日志文件 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ [✓] 启用日志文件输出 │ │
|
||
│ │ │ │
|
||
│ │ 日志目录: [________________] [📁 浏览...] │ │
|
||
│ │ │ │
|
||
│ │ 单文件最大: [_____] MB (默认: 10) │ │
|
||
│ │ 保留数量: [_____] 个 (默认: 5) │ │
|
||
│ │ │ │
|
||
│ │ 当前日志: qiming-agent_20260129.log │ │
|
||
│ │ 文件大小: 2.5 MB │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ [📤 导出日志...] [🗑 清空日志] │ │
|
||
│ │ │ │
|
||
│ │ 导出时自动排除敏感信息(密码、Token 等) │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 2.4 依赖管理 Tab
|
||
|
||
#### 2.4.1 依赖状态总览
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 依赖管理 [🔄 刷新] │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 总状态: 3/5 已安装 │ 2 个需要关注 │ │
|
||
│ │ ✓ Node.js v20.10.0 (系统) │ │
|
||
│ │ ✓ npm 10.2.0 (系统) │ │
|
||
│ │ ✗ opencode 未安装 (建议安装) │ │
|
||
│ │ ↑ claude-code 1.0.0 -> 1.0.1 (可更新) │ │
|
||
│ │ ✓ docker 24.0.7 (系统) │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 依赖项 │ 版本 │ 来源 │ 状态 │ 操作 │ │
|
||
│ │ ───────────────────────────────────────────────────────│ │
|
||
│ │ [🟢] Node.js│ v20.10.0 │ 系统全局│ 已安装 │ ─ ─ │ │
|
||
│ │ [🟢] npm │ 10.2.0 │ 系统全局│ 已安装 │ ─ ─ │ │
|
||
│ │ [🔴] opencode│ - │ - │ 未安装 │ [安装]│ │
|
||
│ │ [🟡] claude │ 1.0.0 │ 客户端 │ 可更新 │ [更新]│ │
|
||
│ │ [🟢] docker │ 24.0.7 │ 系统全局│ 已安装 │ ─ ─ │ │
|
||
│ │ [⚪] rustc │ - │ - │ 可选 │ [安装]│ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ───────────────────────────────────────────────────────────── │
|
||
│ │
|
||
│ 状态图标说明: │
|
||
│ 🟢 已安装 - 正常可用 │
|
||
│ 🟡 可更新 - 有新版本可用 │
|
||
│ 🔴 未安装 - 尚未安装 │
|
||
│ 🟠 安装失败 - 上次安装出错 │
|
||
│ 🔵 安装中 - 正在下载/安装 │
|
||
│ ⚪ 可选 - 未安装但不影响核心功能 │
|
||
│ │
|
||
│ 来源图标说明: │
|
||
│ [系统] 系统全局安装 │
|
||
│ [客户端] 客户端隔离目录 │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**改进点**:
|
||
1. 顶部增加总状态卡片,一目了然
|
||
2. 使用颜色圆点代替文字状态
|
||
3. 显示来源类型(系统/客户端)
|
||
4. 可选依赖用灰色显示,不影响总状态统计
|
||
|
||
#### 2.4.2 详细依赖信息卡片
|
||
|
||
**Node.js 详情**:
|
||
```
|
||
┌───────────────────────────────────────────────────────────────┐
|
||
│ Node.js │
|
||
├───────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 状态: ✓ 已安装 (v20.10.0) │
|
||
│ │
|
||
│ 来源: 系统全局 (/usr/local/bin/node) │
|
||
│ 已存在于系统 PATH,无需安装到客户端目录 │
|
||
│ │
|
||
│ 版本信息: │
|
||
│ 当前版本: v20.10.0 │
|
||
│ 最新版本: v20.11.1 (LTS) │
|
||
│ 检查时间: 2026-01-29 10:30:00 │
|
||
│ │
|
||
│ 安装路径: │
|
||
│ macOS: /usr/local/bin/node │
|
||
│ Windows: C:\Program Files\nodejs\node.exe │
|
||
│ Linux: /usr/bin/node │
|
||
│ │
|
||
│ 支持的平台: │
|
||
│ ✓ Windows x64 │
|
||
│ ✓ macOS x64 (Intel) │
|
||
│ ✓ macOS arm64 (Apple Silicon) │
|
||
│ ✓ Linux x64 │
|
||
│ │
|
||
│ 操作: │
|
||
│ [检查更新] [卸载] 需要管理员权限 │
|
||
│ │
|
||
└───────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**未安装依赖详情**:
|
||
```
|
||
┌───────────────────────────────────────────────────────────────┐
|
||
│ opencode (ACP Agent 工具) │
|
||
├───────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 状态: ✗ 未安装 │
|
||
│ │
|
||
│ 用途: 运行 ACP 协议的 AI Agent │
|
||
│ │
|
||
│ 安装方式: │
|
||
│ npm install -g opencode │
|
||
│ │
|
||
│ 安装目录 (客户端隔离): │
|
||
│ ~/Library/Application Support/qiming-agent/tools/npm-global │
|
||
│ │
|
||
│ 前置依赖: │
|
||
│ ✓ Node.js >= 18.0.0 │
|
||
│ │
|
||
│ ─────────────────────────────────────────────────────────── │
|
||
│ │
|
||
│ 安装进度: │
|
||
│ [──────────────────────────] 0% │
|
||
│ │
|
||
│ 安装日志: │
|
||
│ > npm install -g opencode │
|
||
│ > fetching... │
|
||
│ │
|
||
│ ─────────────────────────────────────────────────────────── │
|
||
│ │
|
||
│ 错误信息: │
|
||
│ ✗ 网络连接超时,请检查网络设置 │
|
||
│ │
|
||
│ 操作: │
|
||
│ [重试安装] [手动安装指引] [忽略] │
|
||
│ │
|
||
└───────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.4.3 手动安装指引
|
||
|
||
```
|
||
┌───────────────────────────────────────────────────────────────┐
|
||
│ 手动安装 opencode │
|
||
├───────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 如果自动安装失败,您可以手动安装: │
|
||
│ │
|
||
│ 步骤 1: 安装 Node.js (如果尚未安装) │
|
||
│ - 下载地址: https://nodejs.org/dist/v20.10.0/ │
|
||
│ - macOS: node-v20.10.0.pkg │
|
||
│ - Windows: node-v20.10.0-x64.msi │
|
||
│ - Linux: node-v20.10.0-linux-x64.tar.xz │
|
||
│ │
|
||
│ 步骤 2: 安装 opencode │
|
||
│ 打开终端,执行: │
|
||
│ │
|
||
│ npm install -g opencode │
|
||
│ │
|
||
│ 或指定安装目录: │
|
||
│ │
|
||
│ cd ~/Library/Application\ Support/qiming-agent/tools/ │
|
||
│ mkdir -p npm-global │
|
||
│ npm config set prefix './npm-global' │
|
||
│ npm install -g opencode │
|
||
│ │
|
||
│ 步骤 3: 验证安装 │
|
||
│ │
|
||
│ which opencode │
|
||
│ opencode --version │
|
||
│ │
|
||
│ 完成后,点击"刷新状态"按钮重新检测。 │
|
||
│ │
|
||
│ ─────────────────────────────────────────────────────────── │
|
||
│ │
|
||
│ [✓ 我已手动安装完成] [✗ 取消] │
|
||
│ │
|
||
└───────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
---
|
||
|
||
### 2.5 Agent 状态显示
|
||
|
||
在状态栏显示当前 agent 进程的运行状态:
|
||
|
||
| 状态 | 显示示例 | 说明 |
|
||
|------|----------|------|
|
||
| 无 agent | `Agent: -` | 未检测到 agent 进程 |
|
||
| 运行中 | `Agent: 2 运行中` | 有 2 个 agent 进程在运行 |
|
||
| 空闲 | `Agent: 空闲` | agent 进程正常运行,等待任务 |
|
||
| 执行中 | `Agent: 执行中 (1/3)` | 1 个任务执行中,共 3 个进程 |
|
||
| 错误 | `Agent: ⚠️ 错误` | agent 进程异常 |
|
||
|
||
**状态栏显示位置**:
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
│ [ID: 12345678] ● 已连接 (P2P) - 32ms │ Agent: 2 运行中 │ 10:30:45 │
|
||
└─────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.6 权限设置 Tab
|
||
|
||
#### 2.6.1 权限检查列表
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 权限设置 [🔄 检查] │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ ⚠️ 部分权限未授权,部分功能可能无法正常使用 │ │
|
||
│ │ 需要 2/4 个权限 │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 🟢 辅助功能权限 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 用途: 用于远程控制时模拟键盘鼠标输入 │ │
|
||
│ │ 状态: ✓ 已授权 │ │
|
||
│ │ [详细信息...] │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 🟢 磁盘访问权限 (macOS) │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 用途: 用于访问磁盘文件进行文件传输 │ │
|
||
│ │ 状态: ✓ 已授权 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 🔴 屏幕录制权限 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ 影响: 远程桌面功能无法使用 │ │
|
||
│ │ 用途: 用于远程桌面实时画面传输 │ │
|
||
│ │ 状态: ⚠️ 未授权 │ │
|
||
│ │ │ │
|
||
│ │ [🚀 立即授权] │ │
|
||
│ │ │ │
|
||
│ │ 设置路径: │ │
|
||
│ │ macOS: 系统设置 → 隐私与安全性 → 屏幕录制 │ │
|
||
│ │ 勾选 "qiming-agent" │ │
|
||
│ │ Windows: 系统 → 隐私与安全性 → 屏幕录制 │ │
|
||
│ │ 启用 "qiming-agent" │ │
|
||
│ │ Linux: 设置 → 隐私 → 屏幕录制 (Wayland) │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 🟢 麦克风权限 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ 影响: 语音通话功能(可选) │ │
|
||
│ │ 用途: 用于语音通话功能 │ │
|
||
│ │ 状态: ✓ 已授权 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**改进点**:
|
||
1. 顶部显示总体统计(需要 X/Y 个权限)
|
||
2. 权限卡片按影响程度排序:核心权限在前,可选权限在后
|
||
3. 每个权限卡片标注"影响"(功能影响说明)
|
||
4. 颜色区分状态:🟢 正常、🔴 缺失、🟡 部分
|
||
|
||
#### 2.6.2 权限检测逻辑
|
||
|
||
```rust
|
||
/// 权限管理器
|
||
struct PermissionManager {
|
||
/// 检测所有权限状态
|
||
fn check_all(&self) -> Vec<PermissionCheck> {
|
||
vec![
|
||
self.check_screen_capture(),
|
||
self.check_accessibility(),
|
||
self.check_full_disk_access(),
|
||
self.check_microphone(),
|
||
]
|
||
}
|
||
|
||
/// 屏幕录制权限
|
||
fn check_screen_capture(&self) -> PermissionCheck {
|
||
PermissionCheck {
|
||
permission: PermissionType::ScreenCapture,
|
||
status: self.platform_check_screen_capture(),
|
||
description: "用于远程桌面实时画面传输".to_string(),
|
||
setup_instruction: match current_platform() {
|
||
Platform::MacOS => "系统设置 → 隐私与安全性 → 屏幕录制 → 勾选 qiming-agent",
|
||
Platform::Windows => "系统 → 隐私与安全性 → 屏幕录制 → 启用 qiming-agent",
|
||
Platform::Linux => "设置 → 隐私 → 屏幕录制 (Wayland)",
|
||
}.to_string(),
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2.7 关于 Tab
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 关于 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ [应用图标] │ │
|
||
│ │ │ │
|
||
│ └─────────────────────┘ │
|
||
│ │
|
||
│ qiming-agent │
|
||
│ 版本 1.0.0 (Build 20260129) │
|
||
│ │
|
||
│ ───────────────────────────────────────────────────────────── │
|
||
│ │
|
||
│ 构建信息: │
|
||
│ • Rust 版本: 1.83.0 │
|
||
│ • 构建时间: 2026-01-29 10:30:45 │
|
||
│ • 构建平台: macOS arm64 │
|
||
│ • Commit: abc1234 │
|
||
│ │
|
||
│ 技术支持: │
|
||
│ • 文档: https://docs.example.com │
|
||
│ • 问题反馈: https://github.com/example/issues │
|
||
│ • 官方网站: https://example.com │
|
||
│ │
|
||
│ 许可证: MIT License │
|
||
│ │
|
||
│ ───────────────────────────────────────────────────────────── │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ [📤 导出日志] [🔄 检查更新] [ℹ️ 系统信息] │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 2.8 远程桌面 Tab(远程控制客户端)
|
||
|
||
**注意**:此 Tab 通过 feature `remote-desktop` 控制,默认关闭
|
||
|
||
#### 2.8.1 界面功能
|
||
|
||
**核心功能**:
|
||
1. 查看被管理端远程桌面画面
|
||
2. 发送键盘鼠标输入到被管理端
|
||
3. 画质调节(流畅/标准/高清)
|
||
4. 全屏/窗口模式切换
|
||
5. 文件传输入口
|
||
6. 连接状态显示
|
||
|
||
#### 2.8.2 远程桌面布局
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
│ 远程桌面 [□ 全屏] │
|
||
├─────────────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ │ │
|
||
│ │ 被管理端屏幕实时画面 │ │
|
||
│ │ │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────────────────────────┐ │ │
|
||
│ │ │ │ │ │
|
||
│ │ │ 1920 x 1080 │ │ │
|
||
│ │ │ │ │ │
|
||
│ │ └─────────────────────────────────┘ │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ [📊 画质: 高清 ▼] [📁 传输文件] [🔒 锁定客户端] [⏸ 暂停] [✕ 断开] │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 连接信息: ● 已连接 (P2P) - 延迟: 32ms - 客户端: Client-A (ID: 12345678) │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.8.3 画质调节
|
||
|
||
| 画质 | 分辨率 | 帧率 | 适用场景 |
|
||
|------|--------|------|----------|
|
||
| 流畅 | 1024x768 | 15 fps | 低带宽/高延迟 |
|
||
| 标准 | 1280x720 | 24 fps | 普通网络 |
|
||
| 高清 | 1920x1080 | 30 fps | 高带宽 |
|
||
|
||
#### 2.8.4 键盘快捷键
|
||
|
||
| 快捷键 | 功能 |
|
||
|--------|------|
|
||
| Ctrl + Alt + End | 发送 Ctrl+Alt+Del |
|
||
| Esc | 退出全屏 |
|
||
| Ctrl + Alt + F | 切换全屏 |
|
||
| Ctrl + Alt + S | 切换画质 |
|
||
|
||
---
|
||
|
||
### 2.9 Agent 聊天界面(可选功能)
|
||
|
||
**注意**:此 Tab 通过 feature `chat-ui` 控制,默认关闭
|
||
|
||
#### 2.9.1 界面功能
|
||
|
||
**核心功能**:
|
||
1. 发送 prompt 到 Agent 执行
|
||
2. 查看 Agent 执行过程中的消息输出
|
||
3. 中止正在执行的任务
|
||
4. 查看执行历史
|
||
|
||
#### 2.9.2 聊天界面布局
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Agent 聊天 [📝 新建] │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 会话历史: │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ [📁 会话 1] 2026-01-29 10:30 │ │
|
||
│ │ [📁 会话 2] 2026-01-29 09:15 │ │
|
||
│ │ [📁 会话 3] 2026-01-28 16:45 │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ 🤖 Assistant │ │
|
||
│ │ 你好!我是你的 Agent 助手,有什么可以帮你的? │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 👤 用户 10:30:45 │ │
|
||
│ │ 请帮我写一个 Rust HTTP 服务器 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 🤖 Assistant │ │
|
||
│ │ ⟳ 正在执行... │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ [🔴 中止任务] │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ [📎] [🔒 ******************__________________] [➤ 发送] │ │
|
||
│ │ 附件 Prompt 输入框 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.9.3 执行状态展示
|
||
|
||
| 状态 | 图标 | 说明 |
|
||
|------|------|------|
|
||
| 等待中 | ⏳ | 任务排队中 |
|
||
| 执行中 | ⟳ | 正在运行 Agent |
|
||
| 输出中 | 📤 | 正在回传消息 |
|
||
| 完成 | ✓ | 任务完成 |
|
||
| 错误 | ✗ | 执行出错 |
|
||
| 已中止 | ⏹ | 用户取消 |
|
||
|
||
---
|
||
|
||
### 2.10 文件传输界面
|
||
|
||
#### 2.10.1 发送文件流程
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 发送文件 [✕ 关闭] │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 选择文件: [____________________] [📁 浏览...] │ │
|
||
│ │ 或拖拽文件到此处 │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 文件列表: │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ 📄 report.pdf (2.5 MB) │ │
|
||
│ │ 🖼 screenshot.png (1.2 MB) │ │
|
||
│ │ 📦 data.zip (15.8 MB) │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 目标客户端: │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ [Client-A (ID: 12345678) ▼] │ │
|
||
│ │ │ │
|
||
│ │ 预计传输时间: ~30 秒 │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ [📤 发送] [✕ 取消] │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.10.2 接收文件提示
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ ┌─────────────────────────┐ │
|
||
│ │ 📥 收到文件 │ │
|
||
│ └─────────────────────────┘ │
|
||
│ │
|
||
│ 发送方: admin@example.com │
|
||
│ 文件: qiming-agent-installer.dmg (125 MB) │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 保存位置: │ │
|
||
│ │ [________________________] [📁 浏览...] │ │
|
||
│ │ ~/Downloads/ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ [✓ 接受并保存] [✗ 拒绝] [📂 打开文件夹] │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 2.11 客户端升级
|
||
|
||
#### 2.11.1 检查更新流程
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 关于 / 检查更新 [🔄 检查] │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────────────┐ │ │
|
||
│ │ │ │ │ │
|
||
│ │ │ [应用图标] │ │ │
|
||
│ │ │ │ │ │
|
||
│ │ └─────────────────────┘ │ │
|
||
│ │ │ │
|
||
│ │ qiming-agent │ │
|
||
│ │ 当前版本: 1.0.0 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 检查更新 │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ [🔄 检查更新] │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 更新信息 (发现新版本 v1.1.0) │ │
|
||
│ │ ───────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ 最新版本: v1.1.0 (2026-02-01) │ │
|
||
│ │ 发布日期: 2026-02-01 │ │
|
||
│ │ 文件大小: 125 MB │ │
|
||
│ │ │ │
|
||
│ │ 更新内容: │ │
|
||
│ │ • 新增远程桌面画质调节功能 │ │
|
||
│ │ • 优化文件传输速度 │ │
|
||
│ │ • 修复已知问题 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ [⬇️ 下载更新] [⏰ 稍后提醒] [❌ 忽略此版本] │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.11.2 下载进度
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 正在下载更新... [✕ 后台下载] │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ qiming-agent v1.1.0 │ │
|
||
│ │ │ │
|
||
│ │ [████████████████████░░░░░░░░░░░░░] 75% │ │
|
||
│ │ │ │
|
||
│ │ 93.8 MB / 125 MB - 2.5 MB/s - 剩余 12 秒 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ [❌ 取消下载] │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.11.3 安装更新
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ ┌─────────────────────────┐ │
|
||
│ │ ✅ 下载完成 │ │
|
||
│ └─────────────────────────┘ │
|
||
│ │
|
||
│ qiming-agent v1.1.0 已下载完成 │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ [⬆️ 立即安装并重启] │ │
|
||
│ │ │ │
|
||
│ │ [⏰ 退出时安装] │ │
|
||
│ │ │ │
|
||
│ │ [❌ 稍后提醒] │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 安装后应用将自动重启 │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 2.12 消息通知系统
|
||
|
||
#### 2.12.1 通知类型
|
||
|
||
| 类型 | 图标 | 用途 |
|
||
|------|------|------|
|
||
| 信息 | ℹ️ | 一般信息提示 |
|
||
| 成功 | ✅ | 操作成功 |
|
||
| 警告 | ⚠️ | 需要注意的事项 |
|
||
| 错误 | ❌ | 操作失败 |
|
||
| Agent | 🤖 | Agent 执行状态 |
|
||
|
||
#### 2.12.2 通知示例
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 状态栏 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ [ID: 12345678] ● 已连接 (P2P) - 32ms │ Agent: 空闲 │ 10:30:45│
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 通知区域 (顶部/右下角) │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ ✅ 文件传输完成 │ │
|
||
│ │ report.pdf 已保存到 ~/Downloads/ │ │
|
||
│ │ [✕] │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 🤖 Agent 任务完成 │ │
|
||
│ │ "帮助我写代码" - 执行完成 │ │
|
||
│ │ [查看结果] [✕] │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ ⚠️ 依赖需要更新 │ │
|
||
│ │ Node.js v20.10.0 → v20.11.1 │ │
|
||
│ │ [立即更新] [稍后] [✕] │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 2.13 系统托盘菜单
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ 📌 qiming-agent │
|
||
├─────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 显示窗口 [Show Window] │
|
||
│ │
|
||
│ ───────────────────────────────────────────── │
|
||
│ │
|
||
│ 状态: ● 已连接 (P2P) │
|
||
│ 延迟: 32ms │
|
||
│ ID: 12345678 │
|
||
│ │
|
||
│ ───────────────────────────────────────────── │
|
||
│ │
|
||
│ Agent 状态: 空闲 │
|
||
│ │
|
||
│ ───────────────────────────────────────────── │
|
||
│ │
|
||
│ 设置... [Settings] │
|
||
│ 依赖管理 [Dependencies] │
|
||
│ 远程桌面 [Remote Desktop] │
|
||
│ │
|
||
│ ───────────────────────────────────────────── │
|
||
│ │
|
||
│ 关于 [About] │
|
||
│ │
|
||
│ ───────────────────────────────────────────── │
|
||
│ │
|
||
│ 退出 [Quit] │
|
||
│ │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 组件选型与使用
|
||
|
||
### 3.1 组件使用总览表
|
||
|
||
| 功能需求 | 选用的 Rust 库 | 使用方式 |
|
||
|----------|----------------|----------|
|
||
| **UI 框架** | | |
|
||
| 窗口、渲染、事件 | `gpui` | git 依赖 |
|
||
| 基础 UI 组件 | `gpui-component` | path 依赖 |
|
||
| **系统托盘** | | |
|
||
| 托盘图标 | `tray-icon` | path 依赖 |
|
||
| 菜单系统 | `muda` | path 依赖 |
|
||
| **开机自启动** | | |
|
||
| 自启动管理 | `auto-launch` | path 依赖 |
|
||
| **通信协议** | | |
|
||
| P2P/中继连接 | `qiming-rustdesk` | rlib 静态链接 |
|
||
| 信令服务 | `rustdesk-server` | 服务器端使用 |
|
||
| **远程桌面** | | |
|
||
| 屏幕捕获 | `scrap` (qiming-rustdesk) | rlib 静态链接 |
|
||
| 输入模拟 | `enigo` | path 依赖 |
|
||
| **Agent 运行时** | | |
|
||
| Agent 生命周期 | `agent_runner` (rcoder) | path 依赖 |
|
||
| **打包发布** | | |
|
||
| 跨平台打包 | `cargo-packager` | path 依赖 |
|
||
| **工具库** | | |
|
||
| 异步运行时 | `tokio` | crates.io |
|
||
| 配置管理 | `serde` + `toml` | crates.io |
|
||
| 日志 | `tracing` | crates.io |
|
||
| 错误处理 | `thiserror` | crates.io |
|
||
|
||
### 3.2 各组件详细使用方式
|
||
|
||
#### 3.2.1 qiming-rustdesk
|
||
|
||
**Cargo.toml 配置**:
|
||
```toml
|
||
[dependencies]
|
||
librustdesk = { path = "../vendors/qiming-rustdesk" }
|
||
```
|
||
|
||
**使用模块**:
|
||
|
||
| 模块 | 功能 | 关键类型/方法 |
|
||
|------|------|---------------|
|
||
| `rendezvous_mediator` | 连接管理 | `RendezvousMediator::new()`, `login()`, `logout()` |
|
||
| `hbb_common::conn_type` | 连接类型 | `ConnectionType::P2P`, `ConnectionType::Relay` |
|
||
| `hbb_common::latency` | 延迟检测 | `Latency::measure()` |
|
||
| `platform::input_service` | 输入模拟 | `InputService::new()` |
|
||
| `platform::scrap` | 屏幕捕获 | `Scrap::new()`, `Scrap::capture()` |
|
||
|
||
**连接流程**:
|
||
```rust
|
||
// 1. 创建 rendezvous mediator
|
||
let mediator = RendezvousMediator::new();
|
||
|
||
// 2. 配置服务器地址
|
||
mediator.set_id_server(&config.id_server);
|
||
mediator.set_relay_server(&config.relay_servers);
|
||
|
||
// 3. 登录
|
||
let login_result = mediator.login().await?;
|
||
|
||
// 4. 处理连接结果
|
||
match login_result.conn_type {
|
||
ConnectionType::P2P => {
|
||
// P2P 直连成功
|
||
}
|
||
ConnectionType::Relay => {
|
||
// 使用中继模式
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3.2.2 gpui-component
|
||
|
||
**核心组件使用表**:
|
||
|
||
| 组件 | 用途 | 代码示例 |
|
||
|------|------|----------|
|
||
| `Root` | 窗口根容器 | `Root::new(window)` |
|
||
| `TabPanel` | Tab 切换 | `TabPanel::new().tabs(...)` |
|
||
| `Input` | 文本输入 | `Input::new(text, on_change)` |
|
||
| `PasswordInput` | 密码输入 | `PasswordInput::new(text)` |
|
||
| `Button` | 按钮 | `Button::new("id", cx, handler)` |
|
||
| `IconButton` | 图标按钮 | `IconButton::new("id", IconName::Copy)` |
|
||
| `Switch` | 开关 | `Switch::new(checked, on_change)` |
|
||
| `Select` | 下拉选择 | `Select::new(items, selected)` |
|
||
| `DataTable` | 表格 | `DataTable::new().columns(...)` |
|
||
| `Modal` | 模态框 | `Modal::new(title, content)` |
|
||
| `Notification` | 通知 | `Notification::info(message)` |
|
||
| `Icon` | 图标 | `Icon::new(name)` |
|
||
| `Badge` | 标签 | `Badge::new(text).variant(...)` |
|
||
| `ProgressBar` | 进度条 | `ProgressBar::new(progress)` |
|
||
| `StatusBar` | 状态栏 | `StatusBar::new()` |
|
||
| `Clipboard` | 剪贴板 | `cx.write_to_clipboard()` |
|
||
|
||
#### 3.2.3 tray-icon + muda
|
||
|
||
```rust
|
||
use tray_icon::{TrayIconBuilder, icon::Icon, TrayIconEvent};
|
||
use muda::{Menu, MenuItem, ContextMenu};
|
||
|
||
let menu = ContextMenu::new()
|
||
.add_item(&MenuItem::with_id("show", "显示窗口", true, None)?)?
|
||
.add_separator()
|
||
.add_item(&MenuItem::with_id("settings", "设置...", true, None)?)?
|
||
.add_item(&MenuItem::with_id("about", "关于", true, None)?)?
|
||
.add_separator()
|
||
.add_item(&MenuItem::with_id("quit", "退出", true, None)?)?;
|
||
|
||
let _tray_icon = TrayIconBuilder::new()
|
||
.with_icon(icon)
|
||
.with_tooltip("qiming-agent")
|
||
.with_menu(Box::new(menu))
|
||
.with_menu_on_left_click(true)
|
||
.build()?;
|
||
```
|
||
|
||
#### 3.2.4 auto-launch
|
||
|
||
```rust
|
||
use auto_launch::{AutoLaunch, AutoLaunchBuilder, MacOSLaunchMode};
|
||
|
||
let auto = AutoLaunchBuilder::new()
|
||
.set_app_name("qiming-agent")
|
||
.set_app_path("/Applications/qiming-agent.app")
|
||
.set_macos_launch_mode(MacOSLaunchMode::SMAppService)
|
||
.build()?;
|
||
|
||
// 检查状态
|
||
let is_enabled = auto.is_enabled().await?;
|
||
|
||
// 启用
|
||
auto.enable().await?;
|
||
|
||
// 禁用
|
||
auto.disable().await?;
|
||
```
|
||
|
||
#### 3.2.5 enigo
|
||
|
||
```rust
|
||
use enigo::{Enigo, Key, Button, Direction, Coordinate};
|
||
|
||
let mut enigo = Enigo::new();
|
||
|
||
// 键盘输入
|
||
enigo.key(Key::Layout('a'), Direction::Click)?;
|
||
|
||
// 组合键 Ctrl+C
|
||
enigo.key(Key::Control, Direction::Down)?;
|
||
enigo.key(Key::Layout('c'), Direction::Click)?;
|
||
enigo.key(Key::Control, Direction::Up)?);
|
||
|
||
// 鼠标移动到绝对位置
|
||
enigo.move_mouse(100, 200, Coordinate::Absolute)?;
|
||
|
||
// 鼠标点击
|
||
enigo.button(Button::Left, Direction::Click)?;
|
||
|
||
// 滚轮滚动
|
||
enigo.scroll(100, enigo::Axis::Vertical)?;
|
||
```
|
||
|
||
#### 3.2.6 rcoder/agent_runner(直接调用,不使用 gRPC)
|
||
|
||
**使用方式**:客户端不运行 gRPC 服务,直接通过 path 依赖调用 agent_runner 的函数接口。
|
||
|
||
```rust
|
||
use agent_runner::{
|
||
start_agent, cancel_agent, get_agent_status,
|
||
AgentRequest, AgentOutput, AgentStatus,
|
||
};
|
||
|
||
// 1. 启动 Agent 任务(返回异步迭代器,流式输出)
|
||
let mut output_stream = start_agent(AgentRequest {
|
||
prompt: "帮助我写代码".to_string(),
|
||
work_dir: PathBuf::from("./workspace"),
|
||
env: vec![("KEY".to_string(), "value".to_string())],
|
||
timeout: Some(Duration::from_secs(300)),
|
||
}).await?;
|
||
|
||
// 2. 处理输出流
|
||
while let Some(output) = output_stream.next().await {
|
||
match output.output_type {
|
||
// 标准输出
|
||
AgentOutputType::Stdout => {
|
||
println!("{}", output.content);
|
||
}
|
||
// 标准错误
|
||
AgentOutputType::Stderr => {
|
||
eprintln!("[错误] {}", output.content);
|
||
}
|
||
// 状态更新
|
||
AgentOutputType::Status => {
|
||
println!("[状态] {}", output.content);
|
||
}
|
||
// 执行完成
|
||
AgentOutputType::Complete => {
|
||
println!("[完成] {}", output.content);
|
||
}
|
||
// 执行错误
|
||
AgentOutputType::Error => {
|
||
eprintln!("[执行错误] {}", output.content);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. 取消任务
|
||
cancel_agent(session_id).await?;
|
||
|
||
// 4. 查询状态
|
||
let status = get_agent_status(session_id).await?;
|
||
match status {
|
||
AgentStatus::Idle => println!("空闲"),
|
||
AgentStatus::Active => println!("执行中"),
|
||
AgentStatus::Pending => println!("等待中"),
|
||
AgentStatus::Terminating => println!("停止中"),
|
||
AgentStatus::Error => println!("出错"),
|
||
}
|
||
```
|
||
|
||
**核心函数接口**:
|
||
|
||
| 函数 | 返回类型 | 说明 |
|
||
|------|----------|------|
|
||
| `start_agent(request)` | `AsyncIter<AgentOutput>` | 启动任务,流式返回输出 |
|
||
| `cancel_agent(session_id)` | `Result<()>` | 取消正在执行的任务 |
|
||
| `get_agent_status(session_id)` | `Result<AgentStatus>` | 查询任务状态 |
|
||
| `list_active_agents()` | `Vec<SessionInfo>` | 列出所有活跃任务 |
|
||
|
||
---
|
||
|
||
## 4. 核心模块设计
|
||
|
||
### 4.1 应用状态管理
|
||
|
||
| 结构体/枚举 | 说明 |
|
||
|------------|------|
|
||
| `AppState` | 全局应用状态(Tab 导航、连接状态、配置) |
|
||
| `ActiveTab` | 当前激活的 Tab |
|
||
| `ConnectionState` | 连接状态(Disconnected/Connecting/Connected/Error) |
|
||
| `ConnectionMode` | 连接模式(P2P/Relay) |
|
||
| `SettingsSubTab` | 设置 Tab 子页面 |
|
||
| `PasswordChangeState` | 密码修改状态 |
|
||
|
||
### 4.2 配置管理
|
||
|
||
| 结构体/枚举 | 说明 |
|
||
|------------|------|
|
||
| `AppConfig` | 应用配置(服务器/安全/常规/外观/日志) |
|
||
| `ServerConfig` | 服务器地址配置(ID服务器/中继服务器/API服务器) |
|
||
| `SecurityConfig` | 安全设置(密码策略、自动登出) |
|
||
| `GeneralConfig` | 常规设置(自启动、语言、启动行为) |
|
||
| `AppearanceConfig` | 外观设置(主题、工作目录) |
|
||
| `LoggingConfig` | 日志设置(级别、文件路径) |
|
||
|
||
### 4.3 依赖管理
|
||
|
||
| 结构体/枚举 | 说明 |
|
||
|------------|------|
|
||
| `DependencyManager` | 依赖管理器 |
|
||
| `DependencyInfo` | 依赖信息(名称、版本、来源) |
|
||
| `DependencySource` | 来源类型(System/Client/NotInstalled) |
|
||
| `DependencyStatus` | 安装状态(Installed/UpdateAvailable/NotInstalled/Installing/Failed) |
|
||
|
||
### 4.4 权限管理
|
||
|
||
| 结构体/枚举 | 说明 |
|
||
|------------|------|
|
||
| `PermissionManager` | 权限管理器 |
|
||
| `PermissionCheck` | 权限检查项 |
|
||
| `PermissionType` | 权限类型(ScreenCapture/Accessibility/FullDiskAccess/Microphone) |
|
||
| `PermissionStatus` | 权限状态(Granted/Denied/NotDetermined/Restricted) |
|
||
|
||
### 4.5 跨平台目录管理
|
||
|
||
使用 `dirs` 库获取各平台标准目录:
|
||
|
||
| 函数 | macOS | Windows | Linux |
|
||
|------|-------|---------|-------|
|
||
| `data_dir()` | `~/Library/Application Support/` | `%LOCALAPPDATA%` | `~/.local/share/` |
|
||
| `config_dir()` | `~/Library/Application Support/` | `%LOCALAPPDATA%` | `~/.config/` |
|
||
| `cache_dir()` | `~/Library/Caches/` | `%LOCALAPPDATA%` | `~/.cache/` |
|
||
|
||
**关键路径**:
|
||
|
||
| 路径 | macOS | Windows | Linux |
|
||
|------|-------|---------|-------|
|
||
| 应用数据 | `~/Library/Application Support/qiming-agent/` | `%LOCALAPPDATA%\qiming-agent\` | `~/.local/share/qiming-agent/` |
|
||
| 工具目录 | `tools/` | `tools\` | `tools/` |
|
||
| Node.js | `tools/node/` | `tools\node\` | `tools/node/` |
|
||
| npm 全局 | `tools/npm-global/` | `tools\npm-global\` | `tools/npm-global/` |
|
||
|
||
---
|
||
|
||
## 5. 通信协议设计
|
||
|
||
### 5.1 Protobuf 消息类型
|
||
|
||
| 消息 ID | 类型名称 | 用途 |
|
||
|---------|----------|------|
|
||
| 100 | HandshakeRequest | 握手请求 |
|
||
| 101 | HandshakeResponse | 握手响应 |
|
||
| 102 | HeartbeatPing | 心跳 |
|
||
| 103 | HeartbeatPong | 心跳响应 |
|
||
| 200 | AgentTaskRequest | Agent 任务请求 |
|
||
| 201 | AgentTaskResponse | Agent 任务响应 |
|
||
| 202 | AgentTaskOutput | Agent 任务输出 |
|
||
| 203 | AgentTaskCancel | 取消任务 |
|
||
| 300 | FileTransferRequest | 文件传输请求 |
|
||
| 301 | FileTransferChunk | 文件分片 |
|
||
| 302 | FileTransferComplete | 传输完成 |
|
||
| 400 | ScreenCaptureRequest | 屏幕捕获请求 |
|
||
| 401 | ScreenCaptureFrame | 屏幕帧数据 |
|
||
| 402 | InputEvent | 输入事件 |
|
||
| 500 | UpdatePasswordRequest | 更新密码请求 |
|
||
| 501 | UpdatePasswordResponse | 更新密码响应 |
|
||
|
||
### 5.2 连接流程
|
||
|
||
```
|
||
1. 客户端 → data-server: HandshakeRequest
|
||
- 包含客户端信息(OS 版本、应用版本等)
|
||
- 包含协议版本
|
||
|
||
2. data-server → 客户端: HandshakeResponse
|
||
- 成功: 包含分配的 Client ID、连接模式
|
||
- 失败: 包含错误信息
|
||
|
||
3. 双方保持心跳 (间隔 5 秒)
|
||
- HeartbeatPing → HeartbeatPong
|
||
- 包含延迟信息
|
||
|
||
4. 传输数据
|
||
- 根据消息类型路由到对应处理器
|
||
```
|
||
|
||
### 5.3 完整消息流转流程(Browser → Agent 执行)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────────────┐
|
||
│ 完整消息流转流程 │
|
||
└─────────────────────────────────────────────────────────────────────────────────────┘
|
||
|
||
浏览器 agent-server-admin data-server agent-client
|
||
│ │ │ │
|
||
│ 1. 用户输入 Prompt │ │ │
|
||
│ ──────────────────────────>│ │ │
|
||
│ │ │ │
|
||
│ │ 2. 通过 WebSocket 发送到 data-server │
|
||
│ │────────────────────────────────>│ │
|
||
│ │ │ │
|
||
│ │ │ 3. 路由到目标客户端
|
||
│ │ │───────────────────>│
|
||
│ │ │ │
|
||
│ │ │ │ 4. Agent 执行
|
||
│ │ │ │───> Agent Runner
|
||
│ │ │ │
|
||
│ │ │ 5. 输出回传 │
|
||
│ │ │<───────────────────│
|
||
│ │ │ │
|
||
│ 6. SSE 推送结果 │ 6. SSE 推送结果 │ │
|
||
│<───────────────────────────│────────────────────────────────│ │
|
||
│ │ │ │
|
||
│ 7. 浏览器实时显示 │ │ │
|
||
│ (流式输出) │ │ │
|
||
│ │ │ │
|
||
```
|
||
|
||
**详细步骤说明**:
|
||
|
||
| 步骤 | 发送方 | 接收方 | 消息类型 | 说明 |
|
||
|------|--------|--------|----------|------|
|
||
| 1 | 浏览器 | agent-server-admin | HTTP POST | 用户提交 prompt |
|
||
| 2 | agent-server-admin | data-server | WebSocket + Protobuf | 转发任务请求 |
|
||
| 3 | data-server | agent-client | 路由转发 + Protobuf | 根据 Client ID 定位目标 |
|
||
| 4 | agent-client | agent_runner | 直接调用 | 解析 protobuf,调用内部逻辑执行 |
|
||
| 5 | agent-client | data-server | WebSocket + Protobuf | 流式输出回传 |
|
||
| 6 | data-server | agent-server-admin | WebSocket + Protobuf | 转发输出 |
|
||
| 7 | agent-server-admin | 浏览器 | SSE | 流式推送结果 |
|
||
| 8 | 浏览器 | 用户 | 渲染 | 实时显示执行过程 |
|
||
|
||
### 5.4 客户端 Agent 执行机制
|
||
|
||
**设计原则**:客户端不运行 gRPC 服务端,直接解析 protobuf 消息后调用 agent_runner 内部逻辑。
|
||
|
||
```
|
||
agent-client 内部架构
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ agent-client │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────┐ ┌─────────────────────┐ │
|
||
│ │ Protobuf 消息解析 │───▶│ 消息路由器 │ │
|
||
│ │ (消息反序列化) │ │ (根据 message_id) │ │
|
||
│ └─────────────────────┘ └──────────┬──────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 任务处理器 │ │
|
||
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
||
│ │ │ ChatHandler (对应 gRPC Chat) │ │ │
|
||
│ │ │ - 解析 AgentTaskRequest │ │ │
|
||
│ │ │ - 调用 agent_runner::start_agent() │ │ │
|
||
│ │ │ - 等待响应返回 ChatResponse │ │ │
|
||
│ │ └─────────────────────────────────────────────────┘ │ │
|
||
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
||
│ │ │ SubscribeHandler (对应 gRPC SubscribeProgress)│ │ │
|
||
│ │ │ - 订阅 agent_runner 输出流 │ │ │
|
||
│ │ │ - 转换为 ProgressEvent 推送 │ │ │
|
||
│ │ │ - 支持心跳保活(30秒间隔) │ │ │
|
||
│ │ └─────────────────────────────────────────────────┘ │ │
|
||
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
||
│ │ │ CancelHandler (对应 gRPC CancelSession) │ │ │
|
||
│ │ │ - 解析 AgentTaskCancel │ │ │
|
||
│ │ │ - 调用 agent_runner::cancel() │ │ │
|
||
│ │ │ - 返回 CancelResponse │ │ │
|
||
│ │ └─────────────────────────────────────────────────┘ │ │
|
||
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
||
│ │ │ StatusHandler (对应 gRPC GetStatus) │ │ │
|
||
│ │ │ - 解析 GetStatusRequest │ │ │
|
||
│ │ │ - 调用 agent_runner::get_status() │ │ │
|
||
│ │ │ - 返回 GetStatusResponse │ │ │
|
||
│ │ └─────────────────────────────────────────────────┘ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ agent_runner (rcoder) │ │
|
||
│ │ │ │
|
||
│ │ 使用方式:path 依赖,直接调用函数接口 │ │
|
||
│ │ 核心依赖模块: │ │
|
||
│ │ ├── AgentWorkerManager ─ 任务调度与执行 │ │
|
||
│ │ ├── AGENT_REGISTRY ─ 任务状态注册表 │ │
|
||
│ │ ├── SESSION_CACHE ─ 会话缓存与进度推送 │ │
|
||
│ │ └── LocalSetAgentRequest ─ 任务请求封装 │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 5.4.1 gRPC 方法与客户端调用的映射关系
|
||
|
||
| gRPC 方法 | 客户端调用函数 | 说明 |
|
||
|-----------|----------------|------|
|
||
| `Chat` | `start_agent(request)` | 启动 Agent 任务,返回响应 |
|
||
| `SubscribeProgress` | 订阅 SESSION_CACHE | 流式推送进度事件 |
|
||
| `CancelSession` | `cancel_agent(session_id)` | 取消正在执行的任务 |
|
||
| `GetStatus` | `get_agent_status(project_id)` | 查询任务状态 |
|
||
| `StopAgent` | `stop_agent(project_id)` | 停止 Agent 进程 |
|
||
| `GetContainerStatus` | `get_container_status()` | 查询容器状态 |
|
||
| `GetVncStatus` | `get_vnc_status()` | 查询 VNC 服务状态 |
|
||
|
||
#### 5.4.2 内部状态管理
|
||
|
||
agent_runner 使用 DashMap 管理全局状态,客户端直接调用:
|
||
|
||
```rust
|
||
// AGENT_REGISTRY - 任务注册表
|
||
// key: project_id, value: AgentInfo { status, session_id, cancel_tx, ... }
|
||
AGENT_REGISTRY.set_pending(&project_id); // 设置等待中
|
||
AGENT_REGISTRY.get_agent_info(&project_id); // 查询状态
|
||
AGENT_REGISTRY.remove_by_project(&project_id); // 移除任务
|
||
|
||
// SESSION_CACHE - 会话缓存(用于进度推送)
|
||
// key: session_id, value: SessionData { message_tx, ... }
|
||
SESSION_CACHE.get(&session_id); // 获取会话
|
||
SESSION_CACHE.insert(session_id, session_data); // 创建会话
|
||
```
|
||
|
||
#### 5.4.3 任务状态流转
|
||
|
||
```
|
||
Idle ──────► Pending ──────► Active ──────► Terminating ──────► Idle
|
||
▲ │ │ │ │
|
||
│ │ │ │ │
|
||
│ ▼ │ │ │
|
||
│ 任务已接收 │ │ │
|
||
│ ▼ │ │
|
||
│ 正在执行 │ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 正在停止 │
|
||
│ │
|
||
└───────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
Error (执行出错)
|
||
```
|
||
|
||
#### 5.4.4 完整的 Chat 处理流程(客户端直接调用)
|
||
|
||
```rust
|
||
use agent_runner::{
|
||
start_agent, AgentRequest, AgentStatus,
|
||
AGENT_REGISTRY, SESSION_CACHE,
|
||
};
|
||
use crate::message_converter::{to_agent_request, to_chat_response};
|
||
|
||
pub async fn handle_chat(grpc_request: ChatRequest) -> Result<ChatResponse, Status> {
|
||
let project_id = grpc_request.project_id;
|
||
let session_id = grpc_request.session_id;
|
||
|
||
// 1. 检查 Agent 状态(防止并发)
|
||
if let Some(info) = AGENT_REGISTRY.get_agent_info(&project_id) {
|
||
if info.status == AgentStatus::Active || info.status == AgentStatus::Pending {
|
||
return Ok(ChatResponse {
|
||
success: false,
|
||
error_code: Some("ERR_AGENT_BUSY".to_string()),
|
||
error: Some("Agent 正在执行任务,请等待当前任务完成".to_string()),
|
||
..Default::default()
|
||
});
|
||
}
|
||
}
|
||
|
||
// 2. 预注册 Pending 状态
|
||
AGENT_REGISTRY.set_pending(&project_id);
|
||
|
||
// 3. 转换 gRPC 请求为 AgentRequest
|
||
let agent_request = to_agent_request(grpc_request);
|
||
|
||
// 4. 启动任务(返回异步迭代器)
|
||
let mut output_stream = start_agent(agent_request).await?;
|
||
|
||
// 5. 处理输出流
|
||
let mut session_id = None;
|
||
while let Some(output) = output_stream.next().await {
|
||
// 发送到 SESSION_CACHE(供进度订阅使用)
|
||
if let Some(sid) = &output.session_id {
|
||
session_id = Some(sid.clone());
|
||
SESSION_CACHE.push_message(sid, output.clone());
|
||
}
|
||
}
|
||
|
||
// 6. 构建响应
|
||
Ok(to_chat_response(&project_id, session_id))
|
||
}
|
||
```
|
||
|
||
#### 5.4.5 进度推送实现(对应 gRPC SubscribeProgress)
|
||
|
||
```rust
|
||
pub async fn subscribe_progress(session_id: String) -> impl Stream<Item = ProgressEvent> {
|
||
let (tx, rx) = mpsc::channel(100);
|
||
|
||
tokio::spawn(async move {
|
||
// 从 SESSION_CACHE 订阅消息
|
||
if let Some(session_data) = SESSION_CACHE.get(&session_id) {
|
||
let (mut msg_rx, _) = session_data.create_new_connection(100).await;
|
||
|
||
loop {
|
||
tokio::select! {
|
||
// 接收进度消息
|
||
Some(msg) = msg_rx.recv() => {
|
||
let event = ProgressEvent {
|
||
message_type: format!("{:?}", msg.message_type),
|
||
sub_type: msg.sub_type,
|
||
payload: serde_json::to_string(&msg.data).unwrap_or_default(),
|
||
timestamp: msg.timestamp.timestamp_millis(),
|
||
};
|
||
if tx.send(Ok(event)).await.is_err() {
|
||
break; // 客户端断开
|
||
}
|
||
}
|
||
// 心跳保活(30秒)
|
||
_ = tokio::time::sleep(Duration::from_secs(30)) => {
|
||
let heartbeat = ProgressEvent {
|
||
message_type: "Heartbeat".to_string(),
|
||
sub_type: "ping".to_string(),
|
||
payload: r#"{"type":"heartbeat"}"#.to_string(),
|
||
timestamp: Utc::now().timestamp_millis(),
|
||
};
|
||
if tx.send(Ok(heartbeat)).await.is_err() {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
ReceiverStream::new(rx)
|
||
}
|
||
```
|
||
|
||
#### 5.4.6 取消任务实现(对应 gRPC CancelSession)
|
||
|
||
```rust
|
||
pub async fn cancel_session(
|
||
project_id: String,
|
||
session_id: String,
|
||
reason: String,
|
||
) -> Result<CancelResponse> {
|
||
// 1. 查询 Agent 状态
|
||
let agent_info = match AGENT_REGISTRY.get_agent_info(&project_id) {
|
||
Some(info) => info,
|
||
None => return Ok(CancelResponse {
|
||
success: true,
|
||
message: Some("任务不存在或已完成".to_string()),
|
||
}),
|
||
};
|
||
|
||
// 2. 发送取消通知
|
||
let cancel_tx = agent_info.cancel_tx.clone();
|
||
let cancel_notification = CancelNotification::new(SessionId::new(session_id.as_str()));
|
||
let (result_tx, result_rx) = oneshot::channel();
|
||
|
||
cancel_tx.send(CancelNotificationRequestWrapper {
|
||
cancel_notification,
|
||
result_tx,
|
||
})?;
|
||
|
||
// 3. 等待取消结果(30秒超时)
|
||
match tokio::time::timeout(Duration::from_secs(30), result_rx).await {
|
||
Ok(Ok(result)) => {
|
||
if result.is_success() {
|
||
// 清理资源
|
||
SESSION_CACHE.remove(&session_id);
|
||
AGENT_REGISTRY.clear_pending_if_exists(&project_id);
|
||
}
|
||
Ok(CancelResponse {
|
||
success: result.is_success(),
|
||
message: result.message(),
|
||
})
|
||
}
|
||
_ => Ok(CancelResponse {
|
||
success: false,
|
||
message: Some("取消超时".to_string()),
|
||
}),
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.4.7 状态查询实现(对应 gRPC GetStatus)
|
||
|
||
```rust
|
||
pub async fn get_status(project_id: String, session_id: String) -> GetStatusResponse {
|
||
// 通过 session_id 反查 project_id
|
||
let actual_project_id = if project_id.is_empty() {
|
||
AGENT_REGISTRY.get_project_by_session(&session_id)
|
||
.unwrap_or_else(|| project_id)
|
||
} else {
|
||
project_id
|
||
};
|
||
|
||
// 查询状态
|
||
if let Some(info) = AGENT_REGISTRY.get_agent_info(&actual_project_id) {
|
||
let status_str = match info.status {
|
||
AgentStatus::Pending | AgentStatus::Active | AgentStatus::Terminating => "busy",
|
||
AgentStatus::Idle => "idle",
|
||
};
|
||
GetStatusResponse {
|
||
status: status_str.to_string(),
|
||
is_found: true,
|
||
}
|
||
} else {
|
||
GetStatusResponse {
|
||
status: "idle".to_string(),
|
||
is_found: false,
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.4.8 错误码定义
|
||
|
||
| 错误码 | 说明 | 处理方式 |
|
||
|--------|------|----------|
|
||
| `ERR_AGENT_BUSY` (9010) | Agent 正忙 | 等待当前任务完成 |
|
||
| `ERR_TIMEOUT` | 执行超时 | 检查超时设置 |
|
||
| `ERR_PERMISSION_DENIED` | 权限不足 | 检查目录/文件权限 |
|
||
| `ERR_WORKER_STOPPED` | Worker 已停止 | 重启 Agent 服务 |
|
||
|
||
### 5.5 业务数据通道设计
|
||
|
||
**设计目标**:复用 qiming-rustdesk 的底层 P2P/Relay 数据通道,在上层创建独立的业务数据通道,用于传输 Agent 任务请求/响应等业务数据。
|
||
|
||
**架构概览**:
|
||
|
||
```
|
||
qiming-rustdesk 数据通道
|
||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
│ 物理连接 (TCP/WebSocket/P2P) │
|
||
├─────────────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ Message Router (消息路由器) │ │
|
||
│ │ ───────────────────────────────────────────────────────────────── │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ │
|
||
│ │ │ 视频通道 │ 音频通道 │ 文件传输 │ 业务数据 │ │ │
|
||
│ │ │ Video(0x01) │ Audio(0x02) │ File(0x04) │ Biz(0x08) │ │ │
|
||
│ │ │ 远程桌面 │ 远程桌面 │ 文件传输 │ Agent/自定义│ │ │
|
||
│ │ └─────────────┴─────────────┴─────────────┴─────────────┘ │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 5.5.1 业务通道 API 设计
|
||
|
||
在 qiming-rustdesk 中新增 `business_channel` 模块:
|
||
|
||
```rust
|
||
// vendors/qiming-rustdesk/src/business_channel.rs
|
||
|
||
use std::sync::Arc;
|
||
use tokio::sync::{mpsc, RwLock};
|
||
use hbb_common::{ResultType, Stream};
|
||
|
||
/// 业务通道 ID(固定值)
|
||
pub const BUSINESS_CHANNEL_ID: u32 = 0xBIZ;
|
||
|
||
/// 业务消息类型
|
||
#[derive(Debug, Clone)]
|
||
pub enum BusinessMessageType {
|
||
/// Agent 任务请求
|
||
AgentTask = 0x01,
|
||
/// Agent 任务响应
|
||
AgentResponse = 0x02,
|
||
/// Agent 进度推送
|
||
AgentProgress = 0x03,
|
||
/// Agent 取消请求
|
||
AgentCancel = 0x04,
|
||
/// 自定义业务消息
|
||
Custom(u32),
|
||
}
|
||
|
||
/// 业务通道配置
|
||
#[derive(Clone)]
|
||
pub struct BusinessChannelConfig {
|
||
/// 通道 ID(固定为 BUSINESS_CHANNEL_ID)
|
||
pub channel_id: u32,
|
||
/// 消息发送超时(毫秒)
|
||
pub send_timeout_ms: u64,
|
||
/// 消息接收缓冲区大小
|
||
pub buffer_size: usize,
|
||
}
|
||
|
||
impl Default for BusinessChannelConfig {
|
||
fn default() -> Self {
|
||
Self {
|
||
channel_id: BUSINESS_CHANNEL_ID,
|
||
send_timeout_ms: 5000,
|
||
buffer_size: 100,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 业务数据通道
|
||
#[derive(Clone)]
|
||
pub struct BusinessChannel {
|
||
/// 底层 Stream
|
||
stream: Arc<dyn Stream + Send + Sync>,
|
||
/// 配置
|
||
config: BusinessChannelConfig,
|
||
/// 消息发送器
|
||
sender: Arc<RwLock<Option<mpsc::UnboundedSender<Vec<u8>>>>>,
|
||
/// 消息接收器(用于内部路由)
|
||
receiver: Arc<RwLock<Option<mpsc::UnboundedReceiver<Vec<u8>>>>>,
|
||
}
|
||
|
||
impl BusinessChannel {
|
||
/// 从底层 Stream 创建业务通道
|
||
pub async fn from_stream(
|
||
stream: Arc<dyn Stream + Send + Sync>,
|
||
config: Option<BusinessChannelConfig>,
|
||
) -> ResultType<Self> {
|
||
let config = config.unwrap_or_default();
|
||
|
||
let (tx, rx) = mpsc::unbounded_channel(config.buffer_size);
|
||
|
||
Ok(Self {
|
||
stream,
|
||
config,
|
||
sender: Arc::new(RwLock::new(Some(tx))),
|
||
receiver: Arc::new(RwLock::new(Some(rx))),
|
||
})
|
||
}
|
||
|
||
/// 发送业务消息
|
||
pub async fn send(&self, message_type: BusinessMessageType, payload: &[u8]) -> ResultType<()> {
|
||
let mut sender = self.sender.write().await;
|
||
if let Some(tx) = sender.as_ref() {
|
||
// 封装消息
|
||
let mut data = Vec::with_capacity(1 + 4 + payload.len());
|
||
data.push(message_type as u8);
|
||
data.extend_from_slice(&(payload.len() as u32).to_le_bytes());
|
||
data.extend_from_slice(payload);
|
||
|
||
tx.send(data)?;
|
||
Ok(())
|
||
} else {
|
||
Err(anyhow::anyhow!("Business channel sender not initialized"))
|
||
}
|
||
}
|
||
|
||
/// 接收业务消息
|
||
pub async fn recv(&self) -> ResultType<Option<(BusinessMessageType, Vec<u8>)>> {
|
||
let mut receiver = self.receiver.write().await;
|
||
if let Some(rx) = receiver.as_ref() {
|
||
match rx.recv().await {
|
||
Some(data) => {
|
||
if data.len() < 5 {
|
||
return Err(anyhow::anyhow!("Invalid message format"));
|
||
}
|
||
let msg_type = BusinessMessageType::Custom(data[0] as u32);
|
||
let len = u32::from_le_bytes([data[1], data[2], data[3], data[4]]) as usize;
|
||
let payload = if data.len() > 5 {
|
||
data[5..5 + len].to_vec()
|
||
} else {
|
||
Vec::new()
|
||
};
|
||
Ok(Some((msg_type, payload)))
|
||
}
|
||
None => Ok(None),
|
||
}
|
||
} else {
|
||
Err(anyhow::anyhow!("Business channel receiver not initialized"))
|
||
}
|
||
}
|
||
|
||
/// 订阅业务消息流
|
||
pub fn subscribe(&self) -> mpsc::UnboundedReceiver<(BusinessMessageType, Vec<u8>)> {
|
||
let (tx, rx) = mpsc::unbounded_channel(self.config.buffer_size);
|
||
let mut sender = self.sender.write().await;
|
||
|
||
// 创建一个内部任务来转发消息
|
||
let mut inner_rx = {
|
||
let mut receiver = self.receiver.write().await;
|
||
receiver.take()
|
||
};
|
||
|
||
if let Some(inner_rx_val) = inner_rx {
|
||
tokio::spawn(async move {
|
||
let mut tx = tx;
|
||
let mut inner_rx = inner_rx_val;
|
||
while let Some(data) = inner_rx.recv().await {
|
||
if data.len() < 5 {
|
||
continue;
|
||
}
|
||
let msg_type = BusinessMessageType::Custom(data[0] as u32);
|
||
let len = u32::from_le_bytes([data[1], data[2], data[3], data[4]]) as usize;
|
||
let payload = if data.len() > 5 {
|
||
data[5..5 + len].to_vec()
|
||
} else {
|
||
Vec::new()
|
||
};
|
||
if tx.send((msg_type, payload)).is_err() {
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
rx
|
||
}
|
||
|
||
/// 检查通道是否有效
|
||
pub fn is_valid(&self) -> bool {
|
||
self.sender.read().await.is_some()
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.5.2 业务通道消息格式
|
||
|
||
```protobuf
|
||
// vendors/qiming-rustdesk/proto/business.proto
|
||
|
||
syntax = "proto3";
|
||
|
||
package qiming_business;
|
||
|
||
// 业务消息封装(外层)
|
||
message BusinessEnvelope {
|
||
uint32 channel_id = 1; // 固定为 0xBIZ (十进制 11)
|
||
uint32 message_type = 2; // 消息类型
|
||
bytes payload = 3; // 业务数据(可以是任何 protobuf 或 JSON)
|
||
int64 timestamp = 4; // 时间戳(毫秒)
|
||
}
|
||
|
||
// 业务消息类型枚举
|
||
enum BusinessMessageType {
|
||
BUSINESS_MESSAGE_TYPE_UNKNOWN = 0;
|
||
BUSINESS_MESSAGE_TYPE_AGENT_TASK = 1; // Agent 任务请求
|
||
BUSINESS_MESSAGE_TYPE_AGENT_RESPONSE = 2; // Agent 任务响应
|
||
BUSINESS_MESSAGE_TYPE_AGENT_PROGRESS = 3; // Agent 进度推送
|
||
BUSINESS_MESSAGE_TYPE_AGENT_CANCEL = 4; // 取消任务
|
||
BUSINESS_MESSAGE_TYPE_CUSTOM = 100; // 自定义消息起始
|
||
}
|
||
|
||
// Agent 任务请求(payload)
|
||
message AgentTaskPayload {
|
||
string task_id = 1; // 任务唯一 ID
|
||
string prompt = 2; // 执行指令
|
||
repeated string attachments = 3; // 附件列表
|
||
int32 timeout_seconds = 4; // 超时时间
|
||
map<string, string> env = 5; // 环境变量
|
||
string work_dir = 6; // 工作目录
|
||
}
|
||
|
||
// Agent 任务响应(payload)
|
||
message AgentResponsePayload {
|
||
string task_id = 1;
|
||
bool success = 2;
|
||
string error = 3; // 错误信息
|
||
string result = 4; // 执行结果
|
||
string session_id = 5; // 会话 ID
|
||
}
|
||
|
||
// Agent 进度推送(payload)
|
||
message AgentProgressPayload {
|
||
string task_id = 1;
|
||
ProgressType type = 2;
|
||
string content = 3; // 输出内容
|
||
int64 timestamp = 4;
|
||
|
||
enum ProgressType {
|
||
PROGRESS_TYPE_UNKNOWN = 0;
|
||
PROGRESS_TYPE_STDOUT = 1;
|
||
PROGRESS_TYPE_STDERR = 2;
|
||
PROGRESS_TYPE_STATUS = 3;
|
||
PROGRESS_TYPE_COMPLETE = 4;
|
||
PROGRESS_TYPE_ERROR = 5;
|
||
}
|
||
}
|
||
|
||
// 取消任务请求(payload)
|
||
message AgentCancelPayload {
|
||
string task_id = 1;
|
||
string reason = 2;
|
||
}
|
||
```
|
||
|
||
#### 5.5.3 在 agent-client 中使用业务通道
|
||
|
||
```rust
|
||
// agent-client/src/core/business_channel.rs
|
||
|
||
use librustdesk::{BusinessChannel, BusinessChannelConfig, BUSINESS_CHANNEL_ID};
|
||
use crate::message_converter::{to_agent_request, to_agent_response};
|
||
|
||
/// 业务通道管理器
|
||
pub struct BusinessChannelManager {
|
||
/// 业务通道实例
|
||
channel: Option<BusinessChannel>,
|
||
/// 配置
|
||
config: BusinessChannelConfig,
|
||
}
|
||
|
||
impl BusinessChannelManager {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
channel: None,
|
||
config: BusinessChannelConfig::default(),
|
||
}
|
||
}
|
||
|
||
/// 从 qiming-rustdesk 的 Stream 创建业务通道
|
||
pub async fn create_from_stream(&mut self, stream: Arc<dyn Stream + Send + Sync>) -> Result<()> {
|
||
self.channel = Some(BusinessChannel::from_stream(stream, Some(self.config.clone())).await?);
|
||
Ok(())
|
||
}
|
||
|
||
/// 发送 Agent 任务请求
|
||
pub async fn send_agent_task(&self, task: AgentTaskRequest) -> Result<()> {
|
||
if let Some(channel) = &self.channel {
|
||
let payload = to_agent_request_payload(task);
|
||
channel.send(BusinessMessageType::AgentTask, &payload).await?;
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 接收业务消息并处理
|
||
pub async fn handle_messages(&self) -> Result<()> {
|
||
if let Some(channel) = &self.channel {
|
||
let mut subscriber = channel.subscribe();
|
||
while let Some((msg_type, payload)) = subscriber.recv().await {
|
||
match msg_type {
|
||
BusinessMessageType::AgentTask => {
|
||
self.handle_agent_task(payload).await?;
|
||
}
|
||
BusinessMessageType::AgentCancel => {
|
||
self.handle_cancel(payload).await?;
|
||
}
|
||
_ => {
|
||
warn!("Unknown business message type: {:?}", msg_type);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 处理 Agent 任务
|
||
async fn handle_agent_task(&self, payload: Vec<u8>) -> Result<()> {
|
||
let task: AgentTaskPayload = protobuf::decode(&payload)?;
|
||
let request = to_agent_request(task);
|
||
|
||
// 调用 agent_runner 执行任务
|
||
let mut output_stream = start_agent(request).await?;
|
||
|
||
// 订阅输出并发送进度
|
||
while let Some(output) = output_stream.next().await {
|
||
let progress = to_progress_payload(&output);
|
||
if let Some(channel) = &self.channel {
|
||
channel.send(BusinessMessageType::AgentProgress, &progress).await?;
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.5.4 与 qiming-rustdesk 的集成方式
|
||
|
||
**修改点**:在 qiming-rustdesk 中新增 `business_channel.rs` 模块,导出 `BusinessChannel` 和相关类型。
|
||
|
||
```rust
|
||
// vendors/qiming-rustdesk/src/lib.rs 新增
|
||
mod business_channel;
|
||
pub use self::business_channel::*;
|
||
```
|
||
|
||
**使用方式**:
|
||
|
||
```rust
|
||
// agent-client 使用示例
|
||
use librustdesk::{BusinessChannel, BUSINESS_CHANNEL_ID};
|
||
|
||
// 1. 从 qiming-rustdesk 获取已连接的 Stream
|
||
let stream = rustdesk_connection.get_stream();
|
||
|
||
// 2. 创建业务通道
|
||
let biz_channel = BusinessChannel::from_stream(
|
||
Arc::new(stream),
|
||
None, // 使用默认配置
|
||
).await?;
|
||
|
||
// 3. 发送业务消息
|
||
biz_channel.send(
|
||
BusinessMessageType::AgentTask,
|
||
&agent_task_payload,
|
||
).await?;
|
||
|
||
// 4. 接收业务消息
|
||
let (msg_type, payload) = biz_channel.recv().await?;
|
||
```
|
||
|
||
#### 5.5.5 消息路由流程
|
||
|
||
```
|
||
agent-server-admin data-server agent-client
|
||
│ │ │
|
||
│ 1. 发送业务消息 (AgentTask) │ │
|
||
│ ───────────────────────────────────────>│ │
|
||
│ │ 2. 路由到目标客户端 │
|
||
│ │──────────────────────────────>│
|
||
│ │ │
|
||
│ │ 3. 业务通道处理 │
|
||
│ │ (BusinessChannel) │
|
||
│ │ ────────────────────────>│
|
||
│ │ │ 4. 调用
|
||
│ │ │ agent_runner
|
||
│ │ │
|
||
│ │ 5. 进度回传 (Progress) │
|
||
│ │<──────────────────────────────│
|
||
│ │ │
|
||
│ 6. SSE 推送结果 │ 6. 转发进度 │
|
||
│<───────────────────────────────────────│<──────────────────────────────│
|
||
│ │ │
|
||
```
|
||
|
||
**通道复用**:
|
||
|
||
| 通道 ID | 用途 | 数据类型 |
|
||
|---------|------|----------|
|
||
| 0x01 | 视频通道 | ScreenCaptureFrame |
|
||
| 0x02 | 音频通道 | AudioFrame |
|
||
| 0x04 | 文件传输 | FileChunk |
|
||
| 0x08 | 业务通道 | BusinessEnvelope (Agent/Custom) |
|
||
|
||
#### 5.5.6 qiming-rustdesk 最小修改清单
|
||
|
||
| 文件 | 修改内容 |
|
||
|------|----------|
|
||
| `src/lib.rs` | 新增 `mod business_channel; pub use self::business_channel::*;` |
|
||
| `src/business_channel.rs` | 新增业务通道实现(约 200 行) |
|
||
| `proto/business.proto` | 新增业务消息定义(约 80 行) |
|
||
|
||
**不需要修改的部分**:
|
||
- 不修改 `RendezvousMediator`
|
||
- 不修改 `Client::start()`
|
||
- 不修改现有的消息路由器逻辑
|
||
|
||
### 5.6 远程桌面消息流程
|
||
|
||
```
|
||
agent-server-admin data-server agent-client
|
||
│ │ │
|
||
│ 1. 请求远程桌面连接 │ │
|
||
│────────────────────────────────>│ │
|
||
│ │ 2. 路由请求 │
|
||
│ │───────────────────>│
|
||
│ │ │
|
||
│ │ 3. 同意连接 │
|
||
│ │<───────────────────│
|
||
│ │ │
|
||
│ 4. 建立 P2P 通道 │ │
|
||
│<───────────────────────────────│ │
|
||
│ │ │
|
||
│ 5. 屏幕数据流 (P2P) │ │
|
||
│<───────────────────────────────────────────────────>│
|
||
│ │ │
|
||
│ 6. 输入事件 (P2P) │ │
|
||
│───────────────────────────────────────────────────>│
|
||
│ │ │
|
||
```
|
||
|
||
### 5.7 文件传输消息流程
|
||
|
||
```
|
||
agent-server-admin data-server agent-client
|
||
│ │ │
|
||
│ 1. 发送文件请求 │ │
|
||
│ (文件信息、目标 Client ID) │ │
|
||
│────────────────────────────────>│ │
|
||
│ │ 2. 通知接收方 │
|
||
│ │───────────────────>│
|
||
│ │ │
|
||
│ │ 3. 用户确认接收 │
|
||
│ │<───────────────────│
|
||
│ │ │
|
||
│ 4. 建立传输通道 │ │
|
||
│<───────────────────────────────│ │
|
||
│ │ │
|
||
│ 5. 分片传输数据 │ │
|
||
│<───────────────────────────────────────────────────>│
|
||
│ │ │
|
||
│ 6. 传输完成通知 │ │
|
||
│<───────────────────────────────────────────────────>│
|
||
│ │ │
|
||
```
|
||
|
||
### 5.8 Protobuf 消息定义示例
|
||
|
||
```protobuf
|
||
// message.proto
|
||
|
||
// 协议版本
|
||
syntax = "proto3";
|
||
|
||
package qiming_agent;
|
||
|
||
// 通用消息头
|
||
message MessageHeader {
|
||
uint32 version = 1; // 协议版本号
|
||
uint32 message_id = 2; // 消息唯一 ID
|
||
string client_id = 3; // 客户端 ID
|
||
int64 timestamp = 4; // 时间戳
|
||
}
|
||
|
||
// 握手请求
|
||
message HandshakeRequest {
|
||
MessageHeader header = 1;
|
||
string os_version = 2; // 操作系统版本
|
||
string app_version = 3; // 应用版本
|
||
string device_name = 4; // 设备名称
|
||
map<string, string> extras = 5; // 扩展信息
|
||
}
|
||
|
||
// 握手响应
|
||
message HandshakeResponse {
|
||
MessageHeader header = 1;
|
||
bool success = 2;
|
||
string client_id = 3; // 分配的客户端 ID
|
||
ConnectionType conn_type = 4; // 连接类型
|
||
string error_message = 5; // 错误信息(失败时)
|
||
}
|
||
|
||
enum ConnectionType {
|
||
CONNECTION_TYPE_UNKNOWN = 0;
|
||
CONNECTION_TYPE_P2P = 1;
|
||
CONNECTION_TYPE_RELAY = 2;
|
||
}
|
||
|
||
// Agent 任务请求
|
||
message AgentTaskRequest {
|
||
MessageHeader header = 1;
|
||
string task_id = 2; // 任务唯一 ID
|
||
string prompt = 3; // 执行指令
|
||
repeated string attachments = 4; // 附件列表
|
||
int32 timeout_seconds = 5; // 超时时间(0 表示不超时)
|
||
map<string, string> env = 6; // 环境变量
|
||
string work_dir = 7; // 工作目录
|
||
}
|
||
|
||
// Agent 任务输出(流式)
|
||
message AgentTaskOutput {
|
||
MessageHeader header = 1;
|
||
string task_id = 2;
|
||
OutputType type = 3;
|
||
string content = 4; // 输出内容
|
||
int64 timestamp = 5; // 时间戳
|
||
|
||
enum OutputType {
|
||
OUTPUT_TYPE_UNKNOWN = 0;
|
||
OUTPUT_TYPE_STDOUT = 1; // 标准输出
|
||
OUTPUT_TYPE_STDERR = 2; // 标准错误
|
||
OUTPUT_TYPE_STATUS = 3; // 状态信息
|
||
OUTPUT_TYPE_COMPLETE = 4; // 执行完成
|
||
OUTPUT_TYPE_ERROR = 5; // 执行错误
|
||
}
|
||
}
|
||
|
||
// 屏幕捕获帧
|
||
message ScreenCaptureFrame {
|
||
MessageHeader header = 1;
|
||
uint32 width = 2; // 画面宽度
|
||
uint32 height = 3; // 画面高度
|
||
bytes image_data = 4; // 压缩后的图像数据
|
||
ImageFormat format = 5;
|
||
uint32 sequence = 6; // 帧序列号
|
||
|
||
enum ImageFormat {
|
||
IMAGE_FORMAT_UNKNOWN = 0;
|
||
IMAGE_FORMAT_JPEG = 1;
|
||
IMAGE_FORMAT_PNG = 2;
|
||
}
|
||
}
|
||
|
||
// 输入事件
|
||
message InputEvent {
|
||
MessageHeader header = 1;
|
||
InputType type = 2;
|
||
int32 x = 3; // X 坐标
|
||
int32 y = 4; // Y 坐标
|
||
int32 delta = 5; // 滚轮增量
|
||
repeated KeyCode keys = 6; // 按键列表
|
||
|
||
enum InputType {
|
||
INPUT_TYPE_UNKNOWN = 0;
|
||
INPUT_TYPE_MOUSE_MOVE = 1;
|
||
INPUT_TYPE_MOUSE_DOWN = 2;
|
||
INPUT_TYPE_MOUSE_UP = 3;
|
||
INPUT_TYPE_MOUSE_WHEEL = 4;
|
||
INPUT_TYPE_KEY_DOWN = 5;
|
||
INPUT_TYPE_KEY_UP = 6;
|
||
}
|
||
|
||
enum KeyCode {
|
||
KEY_CODE_UNKNOWN = 0;
|
||
KEY_CODE_ENTER = 1;
|
||
KEY_CODE_ESCAPE = 2;
|
||
// ... 其他按键
|
||
}
|
||
}
|
||
|
||
// 文件传输请求
|
||
message FileTransferRequest {
|
||
MessageHeader header = 1;
|
||
string transfer_id = 2; // 传输 ID
|
||
string file_name = 3; // 文件名
|
||
int64 file_size = 4; // 文件大小(字节)
|
||
string checksum = 5; // 文件校验和
|
||
string target_client_id = 6; // 目标客户端 ID
|
||
}
|
||
|
||
// 文件分片
|
||
message FileChunk {
|
||
MessageHeader header = 1;
|
||
string transfer_id = 2;
|
||
uint32 chunk_index = 3; // 分片索引
|
||
bytes chunk_data = 4; // 分片数据
|
||
bool is_last = 5; // 是否最后一块
|
||
}
|
||
```
|
||
|
||
### 5.9 版本兼容性策略
|
||
|
||
| 场景 | 处理方式 |
|
||
|------|----------|
|
||
| 老客户端 + 新协议 | 忽略未知字段,基础功能可用 |
|
||
| 新客户端 + 老协议 | 使用默认值填充缺失字段 |
|
||
| 协议重大升级 | 版本号递增,拒绝不兼容的旧版本 |
|
||
| 字段删除 | 保留字段编号,标记为 deprecated |
|
||
| 字段类型变更 | 新增字段,逐步迁移 |
|
||
|
||
---
|
||
|
||
### 5.10 安全设计
|
||
|
||
#### 5.10.1 认证机制
|
||
|
||
| 认证方式 | 说明 | 适用场景 |
|
||
|----------|------|----------|
|
||
| 密码认证 | 客户端连接密码(8位以上字母+数字) | 基础认证方式 |
|
||
| Token 认证 | 临时 Token(24小时有效期) | API 调用认证 |
|
||
| Session 认证 | Session ID + 过期时间 | 管理端会话保持 |
|
||
|
||
**密码存储**:
|
||
```rust
|
||
/// 密码存储策略
|
||
trait PasswordStorage {
|
||
/// 使用 Argon2 加密存储密码
|
||
fn hash_password(password: &str) -> Result<String>;
|
||
/// 验证密码
|
||
fn verify_password(password: &str, hash: &str) -> bool;
|
||
/// 密码强度检查
|
||
fn check_strength(password: &str) -> PasswordStrength;
|
||
}
|
||
```
|
||
|
||
**Token 管理**:
|
||
```rust
|
||
/// Token 管理器
|
||
struct TokenManager {
|
||
/// Token 有效期(24小时)
|
||
const TOKEN_TTL_HOURS: u64 = 24;
|
||
/// 刷新提前量(1小时)
|
||
const REFRESH_AHEAD_HOURS: u64 = 1;
|
||
|
||
/// 生成新 Token
|
||
fn generate_token(&self, client_id: &str) -> Token;
|
||
/// 验证 Token
|
||
fn validate_token(&self, token: &Token) -> bool;
|
||
/// 刷新 Token
|
||
fn refresh_token(&self, old_token: &Token) -> Result<Token>;
|
||
}
|
||
```
|
||
|
||
#### 5.10.2 传输安全
|
||
|
||
**TLS 配置要求**:
|
||
| 配置项 | 要求 |
|
||
|--------|------|
|
||
| 最低 TLS 版本 | TLS 1.2 |
|
||
| 推荐 TLS 版本 | TLS 1.3 |
|
||
| 密码套件 | 仅支持前向安全的套件 |
|
||
| 证书验证 | 必须验证服务器证书 |
|
||
|
||
**数据加密**:
|
||
```rust
|
||
/// 敏感数据加密
|
||
trait SensitiveDataEncryption {
|
||
/// 加密敏感配置(密码、Token 等)
|
||
fn encrypt_sensitive(&self, data: &[u8]) -> Vec<u8>;
|
||
/// 解密敏感配置
|
||
fn decrypt_sensitive(&self, encrypted: &[u8]) -> Vec<u8>;
|
||
/// 敏感信息脱敏(日志输出时)
|
||
fn redact_sensitive(&self, data: &str) -> String;
|
||
}
|
||
```
|
||
|
||
#### 5.10.3 审计日志
|
||
|
||
**审计事件类型**:
|
||
| 事件类型 | 说明 | 记录内容 |
|
||
|----------|------|----------|
|
||
| 连接事件 | 客户端连接/断开 | 时间、客户端ID、连接类型、IP地址 |
|
||
| 认证事件 | 登录成功/失败 | 时间、客户端ID、认证方式、结果 |
|
||
| 任务事件 | Agent 任务开始/完成/失败 | 时间、任务ID、任务内容摘要 |
|
||
| 文件事件 | 文件传输开始/完成 | 时间、文件ID、文件名、大小 |
|
||
| 配置变更 | 密码修改等配置变更 | 时间、变更类型、变更结果 |
|
||
|
||
```rust
|
||
/// 审计日志结构
|
||
struct AuditLog {
|
||
timestamp: DateTime<Utc>,
|
||
event_type: AuditEventType,
|
||
client_id: String,
|
||
user_id: String,
|
||
action: String,
|
||
details: serde_json::Value,
|
||
ip_address: Option<String>,
|
||
result: AuditResult,
|
||
}
|
||
|
||
enum AuditEventType {
|
||
Connection,
|
||
Authentication,
|
||
TaskExecution,
|
||
FileTransfer,
|
||
ConfigChange,
|
||
SecurityEvent,
|
||
}
|
||
```
|
||
|
||
#### 5.10.4 敏感信息脱敏规则
|
||
|
||
| 数据类型 | 脱敏规则 | 示例 |
|
||
|----------|----------|------|
|
||
| 密码 | 替换为 `******` | `******` |
|
||
| Token | 保留首尾字符 | `sk-abc...xyz` |
|
||
| API Key | 保留前4后4位 | `abcd...wxyz` |
|
||
| 文件路径 | 提取文件名 | `C:\...\file.txt` → `file.txt` |
|
||
| IP 地址 | 部分隐藏 | `192.168.**.**` |
|
||
|
||
---
|
||
|
||
### 5.11 网络策略
|
||
|
||
#### 5.11.1 重连机制
|
||
|
||
**重连策略**:
|
||
| 参数 | 默认值 | 可配置范围 |
|
||
|------|--------|------------|
|
||
| 初始重连延迟 | 1秒 | 1-10秒 |
|
||
| 最大重连延迟 | 30秒 | 10-300秒 |
|
||
| 重连指数基数 | 2 | 1.5-3.0 |
|
||
| 最大重试次数 | 0(无限) | 0-100 |
|
||
|
||
```rust
|
||
/// 重连策略配置
|
||
struct ReconnectConfig {
|
||
/// 初始延迟(毫秒)
|
||
initial_delay_ms: u64,
|
||
/// 最大延迟(毫秒)
|
||
max_delay_ms: u64,
|
||
/// 指数退避基数
|
||
backoff_base: f64,
|
||
/// 最大重试次数(0 表示无限)
|
||
max_retries: u32,
|
||
/// 是否随机抖动
|
||
jitter: bool,
|
||
}
|
||
|
||
impl Default for ReconnectConfig {
|
||
fn default() -> Self {
|
||
Self {
|
||
initial_delay_ms: 1000,
|
||
max_delay_ms: 30000,
|
||
backoff_base: 2.0,
|
||
max_retries: 0, // 无限重试
|
||
jitter: true,
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**重连流程**:
|
||
```
|
||
连接断开
|
||
│
|
||
▼
|
||
检查是否达到最大重试次数?
|
||
│
|
||
├── 是 ───► 进入离线模式,缓存消息
|
||
│
|
||
└── 否
|
||
│
|
||
▼
|
||
计算延迟时间(指数退避 + 随机抖动)
|
||
│
|
||
▼
|
||
等待延迟时间
|
||
│
|
||
▼
|
||
尝试重连
|
||
│
|
||
├── 成功 ───► 重置重试计数,恢复消息队列
|
||
│
|
||
└── 失败 ───► 返回步骤 2
|
||
```
|
||
|
||
#### 5.11.2 离线消息队列
|
||
|
||
**队列配置**:
|
||
| 配置项 | 默认值 | 说明 |
|
||
|--------|--------|------|
|
||
| 最大队列大小 | 1000 条 | 离线消息最大缓存数量 |
|
||
| 消息保留时间 | 24 小时 | 消息过期时间 |
|
||
| 单消息最大大小 | 1 MB | 超过则拒绝入队 |
|
||
|
||
```rust
|
||
/// 离线消息队列
|
||
struct OfflineMessageQueue {
|
||
/// 队列容量
|
||
capacity: usize,
|
||
/// 消息保留时间
|
||
ttl: Duration,
|
||
/// 待发送消息
|
||
pending: Arc<RwLock<Vec<QueuedMessage>>>,
|
||
}
|
||
|
||
struct QueuedMessage {
|
||
/// 消息内容
|
||
payload: Vec<u8>,
|
||
/// 消息类型
|
||
message_type: u32,
|
||
/// 入队时间
|
||
enqueued_at: DateTime<Utc>,
|
||
/// 重试次数
|
||
retry_count: u32,
|
||
}
|
||
```
|
||
|
||
#### 5.11.3 带宽控制
|
||
|
||
**带宽配置**:
|
||
| 配置项 | 默认值 | 说明 |
|
||
|--------|--------|------|
|
||
| 最大上传速度 | 10 MB/s | 仅远程桌面/文件传输 |
|
||
| 最大下载速度 | 10 MB/s | 仅远程桌面/文件传输 |
|
||
| 心跳间隔 | 30 秒 | 心跳包发送间隔 |
|
||
| 连接超时 | 10 秒 | TCP 连接超时 |
|
||
|
||
```rust
|
||
/// 带宽管理器
|
||
struct BandwidthManager {
|
||
/// 最大上传速度(字节/秒)
|
||
max_upload: u64,
|
||
/// 最大下载速度(字节/秒)
|
||
max_download: u64,
|
||
/// 令牌桶
|
||
upload_bucket: TokenBucket,
|
||
download_bucket: TokenBucket,
|
||
}
|
||
|
||
struct TokenBucket {
|
||
capacity: u64,
|
||
tokens: f64,
|
||
refill_rate: f64, // 令牌填充速度
|
||
}
|
||
```
|
||
|
||
**动态带宽调整**:
|
||
```rust
|
||
impl BandwidthManager {
|
||
/// 根据网络状况动态调整带宽
|
||
async fn adapt_to_network(&self, latency_ms: u64, loss_rate: f64) {
|
||
match (latency_ms, loss_rate) {
|
||
(latency, loss) if latency > 500 || loss > 0.05 => {
|
||
// 高延迟或高丢包,降低带宽
|
||
self.set_limit(1024 * 1024, 1024 * 1024).await; // 1 MB/s
|
||
}
|
||
(latency, loss) if latency > 200 || loss > 0.02 => {
|
||
// 中等延迟或丢包,中等带宽
|
||
self.set_limit(5 * 1024 * 1024, 5 * 1024 * 1024).await; // 5 MB/s
|
||
}
|
||
_ => {
|
||
// 正常网络,使用全带宽
|
||
self.set_limit(self.max_upload, self.max_download).await;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.11.4 网络状态监听
|
||
|
||
```rust
|
||
/// 网络状态监听器
|
||
trait NetworkStatusListener {
|
||
/// 网络状态变化回调
|
||
fn on_status_change(&self, status: NetworkStatus);
|
||
|
||
/// 延迟变化回调
|
||
fn on_latency_change(&self, latency_ms: u64);
|
||
|
||
/// 连接质量评估
|
||
fn evaluate_quality(&self) -> ConnectionQuality {
|
||
// 基于延迟和丢包率评估
|
||
}
|
||
}
|
||
|
||
enum NetworkStatus {
|
||
Connected(ConnectionType), // P2P 或 Relay
|
||
Connecting,
|
||
Disconnected,
|
||
Offline,
|
||
}
|
||
|
||
enum ConnectionQuality {
|
||
Excellent, // < 50ms, < 1% loss
|
||
Good, // < 150ms, < 2% loss
|
||
Fair, // < 300ms, < 5% loss
|
||
Poor, // > 300ms 或 > 5% loss
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5.12 国际化方案
|
||
|
||
#### 5.12.1 字符串资源结构
|
||
|
||
```
|
||
i18n/
|
||
├── en-US/
|
||
│ └── messages.toml
|
||
├── zh-CN/
|
||
│ └── messages.toml
|
||
├── ja-JP/
|
||
│ └── messages.toml
|
||
└── fallback.toml
|
||
```
|
||
|
||
**资源文件格式**:
|
||
```toml
|
||
# en-US/messages.toml
|
||
|
||
[common]
|
||
ok = "OK"
|
||
cancel = "Cancel"
|
||
save = "Save"
|
||
close = "Close"
|
||
settings = "Settings"
|
||
about = "About"
|
||
|
||
[status]
|
||
connected = "Connected"
|
||
disconnected = "Disconnected"
|
||
connecting = "Connecting..."
|
||
|
||
[client_info]
|
||
title = "Client Information"
|
||
client_id = "Client ID"
|
||
password = "Password"
|
||
copy_id = "Copy ID"
|
||
copy_password = "Copy Password"
|
||
change_password = "Change Password"
|
||
```
|
||
|
||
```toml
|
||
# zh-CN/messages.toml
|
||
|
||
[common]
|
||
ok = "确定"
|
||
cancel = "取消"
|
||
save = "保存"
|
||
close = "关闭"
|
||
settings = "设置"
|
||
about = "关于"
|
||
|
||
[status]
|
||
connected = "已连接"
|
||
disconnected = "已断开"
|
||
connecting = "连接中..."
|
||
|
||
[client_info]
|
||
title = "客户端信息"
|
||
client_id = "客户端 ID"
|
||
password = "连接密码"
|
||
copy_id = "复制 ID"
|
||
copy_password = "复制密码"
|
||
change_password = "修改密码"
|
||
```
|
||
|
||
#### 5.12.2 i18n 基础设施
|
||
|
||
```rust
|
||
/// 国际化管理器
|
||
struct I18nManager {
|
||
/// 当前语言
|
||
current_lang: RwLock<Language>,
|
||
/// 语言资源
|
||
resources: DashMap<Language, ResourceBundle>,
|
||
/// 回退资源
|
||
fallback: ResourceBundle,
|
||
}
|
||
|
||
impl I18nManager {
|
||
/// 加载语言资源
|
||
async fn load_language(&self, lang: Language) -> Result<()> {
|
||
let resource = ResourceBundle::from_file(format!("i18n/{}/messages.toml", lang.code())).await?;
|
||
self.resources.insert(lang, resource);
|
||
Ok(())
|
||
}
|
||
|
||
/// 获取翻译字符串
|
||
fn t(&self, key: &str) -> String {
|
||
let lang = *self.current_lang.read();
|
||
self.resources.get(&lang)
|
||
.or(Some(&self.fallback))
|
||
.and_then(|r| r.get(key))
|
||
.unwrap_or_else(|| key.to_string())
|
||
}
|
||
|
||
/// 格式化带参数的字符串
|
||
fn t_with_args(&self, key: &str, args: &[(&str, &str)]) -> String {
|
||
let mut result = self.t(key);
|
||
for (name, value) in args {
|
||
result = result.replace(&format!("{{{name}}}"), value);
|
||
}
|
||
result
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.12.3 日期/时间/数字格式化
|
||
|
||
```rust
|
||
/// 区域格式化器
|
||
struct LocaleFormatter {
|
||
locale: Language,
|
||
}
|
||
|
||
impl LocaleFormatter {
|
||
/// 格式化日期
|
||
fn format_date(&self, date: &DateTime<Utc>) -> String {
|
||
match self.locale {
|
||
Language::ZhCN => date.format("%Y年%m月%d日").to_string(),
|
||
Language::JaJP => date.format("%Y年%m月%d日").to_string(),
|
||
_ => date.format("%B %d, %Y").to_string(),
|
||
}
|
||
}
|
||
|
||
/// 格式化时间
|
||
fn format_time(&self, time: &DateTime<Utc>) -> String {
|
||
match self.locale {
|
||
Language::ZhCN => time.format("%H:%M:%S").to_string(),
|
||
Language::JaJP => time.format("%H:%M:%S").to_string(),
|
||
_ => time.format("%H:%M:%S").to_string(),
|
||
}
|
||
}
|
||
|
||
/// 格式化文件大小
|
||
fn format_file_size(&self, bytes: u64) -> String {
|
||
const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
|
||
let mut size = bytes as f64;
|
||
let mut unit_index = 0;
|
||
|
||
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
|
||
size /= 1024.0;
|
||
unit_index += 1;
|
||
}
|
||
|
||
match self.locale {
|
||
Language::ZhCN => format!("{:.1} {}", size, UNITS[unit_index]),
|
||
_ => format!("{} {:.1} {}", size, UNITS[unit_index]),
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.12.4 UI 文本动态切换
|
||
|
||
```rust
|
||
/// 支持动态切换语言的 UI 组件
|
||
trait LocalizedComponent {
|
||
/// 更新所有文本
|
||
fn update_localization(&mut self, cx: &mut Cx, lang: Language);
|
||
|
||
/// 获取当前语言
|
||
fn current_language(&self) -> Language;
|
||
}
|
||
|
||
/// 状态栏语言显示示例
|
||
struct StatusBar {
|
||
language: Language,
|
||
}
|
||
|
||
impl Render for StatusBar {
|
||
fn render(&mut self, cx: &mut Cx) {
|
||
// 根据语言显示对应文本
|
||
let lang_text = match self.language {
|
||
Language::ZhCN => "中文",
|
||
Language::EnUS => "English",
|
||
Language::JaJP => "日本語",
|
||
};
|
||
Label::new(cx, lang_text);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5.13 会话管理
|
||
|
||
#### 5.13.1 任务会话状态
|
||
|
||
```rust
|
||
/// Agent 任务会话状态
|
||
enum AgentSessionState {
|
||
/// 空闲(无任务)
|
||
Idle,
|
||
/// 等待中(任务已接收,等待执行)
|
||
Pending {
|
||
received_at: DateTime<Utc>,
|
||
task_id: String,
|
||
},
|
||
/// 执行中
|
||
Active {
|
||
started_at: DateTime<Utc>,
|
||
session_id: String,
|
||
task_id: String,
|
||
},
|
||
/// 正在停止
|
||
Terminating {
|
||
started_at: DateTime<Utc>,
|
||
reason: String,
|
||
},
|
||
/// 执行出错
|
||
Error {
|
||
error_type: ErrorType,
|
||
message: String,
|
||
},
|
||
}
|
||
|
||
enum ErrorType {
|
||
Timeout,
|
||
PermissionDenied,
|
||
WorkerStopped,
|
||
Unknown,
|
||
}
|
||
```
|
||
|
||
#### 5.13.2 会话超时策略
|
||
|
||
| 会话类型 | 超时时间 | 可配置范围 | 处理方式 |
|
||
|----------|----------|------------|----------|
|
||
| 任务执行超时 | 30 分钟 | 5-120 分钟 | 自动取消 |
|
||
| 空闲超时(无操作) | 2 小时 | 30 分钟 - 24 小时 | 断开连接 |
|
||
| Token 过期 | 24 小时 | 1-72 小时 | 刷新 Token |
|
||
| 消息确认超时 | 30 秒 | 10-120 秒 | 重试 3 次 |
|
||
|
||
```rust
|
||
/// 超时管理器
|
||
struct TimeoutManager {
|
||
/// 任务执行超时
|
||
task_timeout: Duration,
|
||
/// 空闲超时
|
||
idle_timeout: Duration,
|
||
/// Token 过期时间
|
||
token_ttl: Duration,
|
||
/// 消息确认超时
|
||
ack_timeout: Duration,
|
||
}
|
||
|
||
impl TimeoutManager {
|
||
/// 检查任务是否超时
|
||
fn is_task_timed_out(&self, session: &AgentSessionState) -> bool {
|
||
if let AgentSessionState::Active { started_at, .. } = session {
|
||
let elapsed = Utc::now().signed_duration_since(*started_at);
|
||
elapsed > self.task_timeout
|
||
} else {
|
||
false
|
||
}
|
||
}
|
||
|
||
/// 检查空闲超时
|
||
fn is_idle_timed_out(&self, last_activity: &DateTime<Utc>) -> bool {
|
||
let elapsed = Utc::now().signed_duration_since(*last_activity);
|
||
elapsed > self.idle_timeout
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.13.3 并发控制
|
||
|
||
```rust
|
||
/// 并发任务管理器
|
||
struct ConcurrencyManager {
|
||
/// 最大并发任务数
|
||
max_concurrent: usize,
|
||
/// 当前运行的任务
|
||
running_tasks: Arc<RwLock<HashMap<String, AgentSessionState>>>,
|
||
/// 等待队列
|
||
waiting_queue: Arc<RwLock<Vec<TaskRequest>>>,
|
||
}
|
||
|
||
impl ConcurrencyManager {
|
||
/// 提交任务
|
||
async fn submit(&self, request: TaskRequest) -> Result<SubmissionResult> {
|
||
let running_count = self.running_tasks.read().len();
|
||
|
||
if running_count >= self.max_concurrent {
|
||
// 加入等待队列
|
||
self.waiting_queue.write().push(request);
|
||
Ok(SubmissionResult::Queued)
|
||
} else {
|
||
// 直接执行
|
||
self.start_task(request).await?;
|
||
Ok(SubmissionResult::Accepted)
|
||
}
|
||
}
|
||
|
||
/// 任务完成回调
|
||
fn on_task_completed(&self, task_id: &str) {
|
||
// 从运行中移除
|
||
self.running_tasks.write().remove(task_id);
|
||
|
||
// 检查等待队列,启动下一个任务
|
||
if let Some(next_task) = self.waiting_queue.write().pop() {
|
||
tokio::spawn(async move {
|
||
self.start_task(next_task).await;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.13.4 僵尸会话检测与清理
|
||
|
||
```rust
|
||
/// 会话清理策略
|
||
struct SessionCleanupPolicy {
|
||
/// 清理间隔
|
||
cleanup_interval: Duration,
|
||
/// 僵尸判定时间(超过此时间无活动视为僵尸)
|
||
zombie_threshold: Duration,
|
||
/// 保留历史记录时间
|
||
history_retention: Duration,
|
||
}
|
||
|
||
impl SessionCleanupPolicy {
|
||
/// 执行清理
|
||
async fn cleanup(&self, sessions: &DashMap<String, AgentSessionState>) -> CleanupResult {
|
||
let mut zombie_sessions = Vec::new();
|
||
let now = Utc::now();
|
||
|
||
for (session_id, session) in sessions.iter() {
|
||
if let Some(last_activity) = self.get_last_activity(session).await {
|
||
let elapsed = now.signed_duration_since(last_activity);
|
||
if elapsed > self.zombie_threshold {
|
||
zombie_sessions.push(session_id.clone());
|
||
}
|
||
}
|
||
}
|
||
|
||
// 清理僵尸会话
|
||
for session_id in &zombie_sessions {
|
||
sessions.remove(session_id);
|
||
}
|
||
|
||
CleanupResult {
|
||
cleaned_count: zombie_sessions.len(),
|
||
timestamp: now,
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5.14 系统集成
|
||
|
||
#### 5.14.1 全局快捷键
|
||
|
||
```rust
|
||
/// 全局快捷键管理器
|
||
struct GlobalShortcutManager {
|
||
/// 快捷键注册表
|
||
shortcuts: Arc<RwLock<HashMap<Shortcut, ShortcutHandler>>>,
|
||
}
|
||
|
||
impl GlobalShortcutManager {
|
||
/// 注册全局快捷键
|
||
fn register(&self, shortcut: Shortcut, handler: ShortcutHandler) -> Result<()> {
|
||
match current_platform() {
|
||
Platform::MacOS => {
|
||
// 使用 CGEventTap 或 MASShortcutBundle
|
||
}
|
||
Platform::Windows => {
|
||
// 使用 RegisterHotKey
|
||
}
|
||
Platform::Linux => {
|
||
// 使用 X11 GrabKey 或 GTK AccelGroup
|
||
}
|
||
}
|
||
self.shortcuts.write().insert(shortcut, handler);
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
/// 默认快捷键
|
||
const DEFAULT_SHORTCUTS: &[Shortcut] = &[
|
||
Shortcut::new(vec![ModifierKey::Ctrl, ModifierKey::Alt], KeyCode::A),
|
||
// 显示窗口
|
||
Shortcut::new(vec![ModifierKey::Ctrl, ModifierKey::Alt], KeyCode::S),
|
||
// 隐藏窗口
|
||
Shortcut::new(vec![ModifierKey::Ctrl, ModifierKey::Alt], KeyCode::H),
|
||
// 快速连接
|
||
Shortcut::new(vec![ModifierKey::Ctrl, ModifierKey::Alt], KeyCode::C),
|
||
];
|
||
```
|
||
|
||
#### 5.14.2 系统通知集成
|
||
|
||
```rust
|
||
/// 系统通知管理器
|
||
trait SystemNotification {
|
||
/// 发送通知
|
||
async fn send(&self, notification: NotificationRequest) -> Result<NotificationId>;
|
||
|
||
/// 取消通知
|
||
async fn cancel(&self, id: NotificationId);
|
||
|
||
/// 设置通知回调
|
||
fn set_callback(&self, callback: NotificationCallback);
|
||
}
|
||
|
||
struct NotificationRequest {
|
||
title: String,
|
||
body: String,
|
||
icon: Option<PathBuf>,
|
||
urgency: NotificationUrgency,
|
||
timeout: Duration,
|
||
actions: Vec<NotificationAction>,
|
||
/// 点击通知时的回调
|
||
on_click: Option<CallbackTarget>,
|
||
}
|
||
|
||
enum NotificationUrgency {
|
||
Low,
|
||
Normal,
|
||
Critical,
|
||
}
|
||
|
||
struct NotificationAction {
|
||
id: String,
|
||
label: String,
|
||
icon: Option<PathBuf>,
|
||
}
|
||
```
|
||
|
||
#### 5.14.3 系统事件处理
|
||
|
||
```rust
|
||
/// 系统事件监听器
|
||
trait SystemEventListener {
|
||
/// 关机事件
|
||
fn on_shutdown(&self);
|
||
|
||
/// 睡眠事件
|
||
fn on_sleep(&self);
|
||
|
||
/// 唤醒事件
|
||
fn on_wake(&self);
|
||
|
||
/// 屏幕锁定事件
|
||
fn on_screen_lock(&self);
|
||
|
||
/// 屏幕解锁事件
|
||
fn on_screen_unlock(&self);
|
||
|
||
/// 网络状态变化事件
|
||
fn on_network_change(&self, status: NetworkStatus);
|
||
}
|
||
|
||
impl SystemEventListener for App {
|
||
fn on_shutdown(&self) {
|
||
// 保存状态
|
||
// 清理资源
|
||
// 断开连接
|
||
info!("System shutdown, saving state and disconnecting...");
|
||
}
|
||
|
||
fn on_sleep(&self) {
|
||
// 暂停远程桌面流
|
||
// 保存临时数据
|
||
info!("System going to sleep, pausing operations...");
|
||
}
|
||
|
||
fn on_wake(&self) {
|
||
// 恢复远程桌面流
|
||
// 检查连接状态
|
||
info!("System woke up, resuming operations...");
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.14.4 系统集成配置
|
||
|
||
```rust
|
||
/// 系统集成配置
|
||
struct SystemIntegrationConfig {
|
||
/// 是否启用全局快捷键
|
||
enable_global_shortcuts: bool,
|
||
/// 是否启用系统通知
|
||
enable_system_notifications: bool,
|
||
/// 通知优先级
|
||
notification_priority: NotificationPriority,
|
||
/// 系统事件处理
|
||
handle_shutdown: bool,
|
||
handle_sleep: bool,
|
||
handle_network_change: bool,
|
||
}
|
||
|
||
impl Default for SystemIntegrationConfig {
|
||
fn default() -> Self {
|
||
Self {
|
||
enable_global_shortcuts: true,
|
||
enable_system_notifications: true,
|
||
notification_priority: NotificationPriority::Normal,
|
||
handle_shutdown: true,
|
||
handle_sleep: true,
|
||
handle_network_change: true,
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.14.5 开机自启动与系统托盘集成
|
||
|
||
```rust
|
||
/// 启动行为配置
|
||
struct StartupBehavior {
|
||
/// 启动时最小化到托盘
|
||
minimize_to_tray_on_startup: bool,
|
||
/// 关闭窗口时最小化到托盘(不退出)
|
||
minimize_to_tray_on_close: bool,
|
||
/// 托盘双击显示窗口
|
||
show_window_on_tray_double_click: bool,
|
||
/// 退出时确认
|
||
confirm_on_quit: bool,
|
||
}
|
||
|
||
impl Default for StartupBehavior {
|
||
fn default() -> Self {
|
||
Self {
|
||
minimize_to_tray_on_startup: false,
|
||
minimize_to_tray_on_close: true,
|
||
show_window_on_tray_double_click: true,
|
||
confirm_on_quit: false,
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 系统架构
|
||
|
||
### 6.1 整体架构
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
│ agent-client │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ UI Layer (gpui-component) │ │
|
||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||
│ │ │客户端信息│ │ 设置 │ │依赖管理 │ │ 权限 │ │ 关于 │ │ │
|
||
│ │ │ Tab │ │ Tab │ │ Tab │ │ Tab │ │ Tab │ │ │
|
||
│ │ │(ID/密码)│ │(子页面) │ │ │ │ │ │ │ │ │
|
||
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
|
||
│ └───────┼───────────┼───────────┼───────────┼───────────┼───────────┘ │
|
||
│ │ │ │ │ │ │
|
||
│ ┌───────┴───────────┴───────────┴───────────┴───────────┴───────────┐ │
|
||
│ │ AppStateManager │ │
|
||
│ │ - Tab 导航 │ │
|
||
│ │ - 连接状态 │ │
|
||
│ │ - 配置状态 │ │
|
||
│ │ - 错误状态 │ │
|
||
│ └───────────────────────────────┬─────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌───────────────────────────────┴─────────────────────────────────────┐ │
|
||
│ │ Core Layer │ │
|
||
│ │ │ │
|
||
│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │
|
||
│ │ │ ConfigManager │ │ConnectionManager│ │ AgentManager │ │ │
|
||
│ │ │ - TOML 读写 │ │ - qiming-rustdesk│ │ - agent_runner │ │ │
|
||
│ │ │ - 配置验证 │ │ - P2P/Relay │ │ - 任务管理 │ │ │
|
||
│ │ └────────────────┘ └────────────────┘ └────────────────┘ │ │
|
||
│ │ │ │
|
||
│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │
|
||
│ │ │DependencyManager│ │FileTransfer │ │ PlatformAdapter│ │ │
|
||
│ │ │ - Node.js 安装 │ │ - 分片传输 │ │ - 权限检测 │ │ │
|
||
│ │ │ - npm 包管理 │ │ - 断点续传 │ │ - 自启动 │ │ │
|
||
│ │ └────────────────┘ └────────────────┘ └────────────────┘ │ │
|
||
│ └──────────────────────────────────────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌──────────────────────────────┴──────────────────────────────────────┐ │
|
||
│ │ Vendor Libraries │ │
|
||
│ │ │ │
|
||
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
|
||
│ │ │ qiming-rustdesk │ │ │
|
||
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
|
||
│ │ │ │Rendezvous│ │ P2P │ │ 文件 │ │ 屏幕 │ │ │ │
|
||
│ │ │ │ 连接器 │ │ 通道 │ │ 传输 │ │ 捕获 │ │ │ │
|
||
│ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │
|
||
│ │ └────────────────────────────────────────────────────────────────┘ │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────┐ ┌─────────────┐ ┌──────── ┌─────────────┐ ─────┐ │ │
|
||
│ │ │ tray-icon │ │ auto-launch │ │ enigo │ │ agent_runner│ │ │
|
||
│ │ │ 托盘图标 │ │ 开机自启动 │ │ 输入模拟 │ │ Agent 管理 │ │ │
|
||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
||
│ │ │ │
|
||
│ └────────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 目录结构
|
||
|
||
```
|
||
qiming-agent/
|
||
├── Cargo.toml
|
||
├── crates/
|
||
│ ├── agent-client/
|
||
│ │ ├── src/
|
||
│ │ │ ├── main.rs # 程序入口
|
||
│ │ │ ├── app.rs # 应用状态管理
|
||
│ │ │ ├── state.rs # 全局状态定义
|
||
│ │ │ ├── ui/ # UI 组件
|
||
│ │ │ │ ├── app_window.rs # 主窗口
|
||
│ │ │ │ ├── tabs/ # Tab 面板
|
||
│ │ │ │ │ ├── client_info.rs # 客户端信息 Tab (ID/密码)
|
||
│ │ │ │ │ ├── settings/ # 设置 Tab
|
||
│ │ │ │ │ │ ├── mod.rs # 设置 Tab 入口
|
||
│ │ │ │ │ │ ├── server.rs # 服务器配置子页面
|
||
│ │ │ │ │ │ ├── security.rs # 安全设置子页面 (密码修改)
|
||
│ │ │ │ │ │ ├── general.rs # 常规设置子页面
|
||
│ │ │ │ │ │ ├── appearance.rs # 外观设置子页面
|
||
│ │ │ │ │ │ └── logging.rs # 日志设置子页面
|
||
│ │ │ │ │ ├── dependencies.rs # 依赖管理 Tab
|
||
│ │ │ │ │ ├── permissions.rs # 权限设置 Tab
|
||
│ │ │ │ │ └── about.rs # 关于 Tab
|
||
│ │ │ │ ├── status_bar.rs # 状态栏
|
||
│ │ │ │ ├── tray_menu.rs # 托盘菜单
|
||
│ │ │ │ └── components/ # 公共 UI 组件
|
||
│ │ │ │ ├── input.rs # 输入组件封装
|
||
│ │ │ │ ├── button.rs # 按钮组件封装
|
||
│ │ │ │ ├── status_indicator.rs # 状态指示器
|
||
│ │ │ │ └── notification.rs # 通知组件
|
||
│ │ │ ├── core/ # 核心逻辑
|
||
│ │ │ │ ├── config.rs # 配置管理
|
||
│ │ │ │ ├── connection.rs # 连接管理 (qiming-rustdesk)
|
||
│ │ │ │ ├── agent.rs # Agent 管理 (rcoder)
|
||
│ │ │ │ ├── dependency.rs # 依赖管理
|
||
│ │ │ │ ├── file_transfer.rs # 文件传输
|
||
│ │ │ │ └── platform/ # 平台适配
|
||
│ │ │ │ ├── mod.rs # 平台抽象
|
||
│ │ │ │ ├── permissions.rs # 权限检测
|
||
│ │ │ │ ├── auto_launch.rs # 自启动
|
||
│ │ │ │ ├── windows.rs
|
||
│ │ │ │ ├── macos.rs
|
||
│ │ │ │ └── linux.rs
|
||
│ │ │ └── utils/ # 工具函数
|
||
│ │ │ ├── clipboard.rs # 剪贴板操作
|
||
│ │ │ ├── version.rs # 版本信息
|
||
│ │ │ └── path.rs # 路径处理
|
||
│ │ └── Cargo.toml
|
||
│ │
|
||
│ ├── agent-server-admin/
|
||
│ │ ├── src/
|
||
│ │ │ ├── main.rs
|
||
│ │ │ ├── api/ # HTTP API
|
||
│ │ │ ├── sse/ # SSE 推送
|
||
│ │ │ ├── client_manager/ # 客户端管理
|
||
│ │ │ └── ws/ # WebSocket
|
||
│ │ └── Cargo.toml
|
||
│ │
|
||
│ ├── data-server/
|
||
│ │ ├── src/
|
||
│ │ │ └── main.rs # 基于 rustdesk-server 封装
|
||
│ │ └── Cargo.toml
|
||
│ │
|
||
│ └── agent-protocol/
|
||
│ ├── proto/ # protobuf 定义
|
||
│ │ ├── message.proto
|
||
│ │ ├── common.proto
|
||
│ │ └── file.proto
|
||
│ ├── src/
|
||
│ │ ├── lib.rs
|
||
│ │ └── generated/ # 生成的代码
|
||
│ └── Cargo.toml
|
||
│
|
||
├── vendors/ # 第三方库
|
||
│ ├── qiming-rustdesk/
|
||
│ ├── rustdesk-server/
|
||
│ ├── gpui-component/
|
||
│ ├── rcoder/
|
||
│ ├── auto-launch/
|
||
│ ├── tray-icon/
|
||
│ ├── enigo/
|
||
│ └── cargo-packager/
|
||
│
|
||
├── assets/ # 资源文件
|
||
│ ├── icons/
|
||
│ │ ├── icon.png
|
||
│ │ ├── icon.ico
|
||
│ │ ├── icon.icns
|
||
│ │ └── tray_icon.png
|
||
│ ├── locales/ # 国际化文件
|
||
│ │ ├── en.json
|
||
│ │ └── zh.json
|
||
│ └── tray/ # 托盘图标资源
|
||
│
|
||
└── specs/
|
||
├── agent-client.md # 需求文档
|
||
├── agent-client-spec.md # 本文档
|
||
└── vendorsdoc/ # 第三方库文档
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 阶段一验收测试用例
|
||
|
||
### 8.1 测试类型说明
|
||
|
||
| 类型 | 说明 | 执行方式 |
|
||
|------|------|----------|
|
||
| 单元测试 | 模块级别的逻辑验证 | `cargo test` 自动执行 |
|
||
| 集成测试 | 跨模块的功能验证 | `cargo test --test integration` |
|
||
| 端到端测试 | 完整流程验证 | 需启动 data-server 和 agent-server-admin |
|
||
| 手动验收测试 | 人工执行的 UI 和功能验收 | 按测试清单逐项验证 |
|
||
|
||
### 8.2 单元测试用例
|
||
|
||
#### Phase 1 单元测试
|
||
|
||
| 测试 ID | 测试模块 | 测试用例 | 预期结果 |
|
||
|---------|----------|----------|----------|
|
||
| UT-1.1 | config | 配置文件加载 | 正确解析 TOML 配置 |
|
||
| UT-1.2 | config | 配置文件加密存储 | 敏感信息加密后可解密还原 |
|
||
| UT-1.3 | protocol | Protobuf 序列化/反序列化 | 消息正确编解码 |
|
||
| UT-1.4 | protocol | 协议版本协商 | 版本不兼容时返回错误 |
|
||
| UT-1.5 | tray | 托盘菜单构建 | 菜单项正确创建 |
|
||
| UT-1.6 | auto_launch | 自启动配置读写 | 各平台正确配置 |
|
||
|
||
#### Phase 2 单元测试
|
||
|
||
| 测试 ID | 测试模块 | 测试用例 | 预期结果 |
|
||
|---------|----------|----------|----------|
|
||
| UT-2.1 | connection | 连接状态机转换 | 状态正确切换 |
|
||
| UT-2.2 | connection | 重连策略(指数退避) | 延迟符合预期 |
|
||
| UT-2.3 | connection | 心跳超时检测 | 超时后触发重连 |
|
||
| UT-2.4 | business_channel | 消息路由 | 按类型正确分发 |
|
||
| UT-2.5 | security | 密码 bcrypt 哈希 | 哈希可验证 |
|
||
| UT-2.6 | message_queue | 离线消息队列 | 消息正确入队出队 |
|
||
| UT-2.7 | chunked_transfer | 大消息分片 | 分片后可重组 |
|
||
|
||
#### Phase 3 单元测试
|
||
|
||
| 测试 ID | 测试模块 | 测试用例 | 预期结果 |
|
||
|---------|----------|----------|----------|
|
||
| UT-3.1 | dependency | Node.js 版本检测 | 正确解析版本号 |
|
||
| UT-3.2 | dependency | 路径查找逻辑 | 各平台路径正确 |
|
||
| UT-3.3 | dependency | 版本比较 | 大于/小于/等于正确判断 |
|
||
| UT-3.4 | installer | 下载进度计算 | 百分比正确 |
|
||
|
||
### 8.3 集成测试用例
|
||
|
||
| 测试 ID | 测试场景 | 前置条件 | 测试步骤 | 预期结果 |
|
||
|---------|----------|----------|----------|----------|
|
||
| IT-1 | 配置持久化 | 无 | 1. 修改配置 2. 重启应用 3. 读取配置 | 配置保持修改后的值 |
|
||
| IT-2 | 托盘与窗口交互 | 应用运行中 | 1. 点击托盘图标 2. 选择"显示窗口" | 主窗口显示 |
|
||
| IT-3 | 依赖检测流程 | Node.js 已安装 | 1. 打开依赖管理 2. 点击刷新 | 显示 Node.js 版本 |
|
||
| IT-4 | 协议兼容性 | 无 | 1. 发送旧版本消息 2. 检查处理 | 向后兼容正确处理 |
|
||
|
||
### 8.4 端到端测试用例
|
||
|
||
| 测试 ID | 测试场景 | 前置条件 | 测试步骤 | 预期结果 |
|
||
|---------|----------|----------|----------|----------|
|
||
| E2E-1 | 客户端注册 | data-server 运行 | 1. 启动 agent-client 2. 观察状态栏 | 显示"已连接"和分配的 ID |
|
||
| E2E-2 | P2P 直连 | 同一局域网 | 1. 启动客户端和管理端 2. 管理端连接客户端 | 连接模式显示 "P2P" |
|
||
| E2E-3 | Relay 中继 | 不同网络 | 1. 配置不同网络 2. 管理端连接客户端 | 连接模式显示 "Relay" |
|
||
| E2E-4 | 双向消息 | 已连接 | 1. 管理端发送消息 2. 客户端接收并回复 | 双向消息正确传递 |
|
||
| E2E-5 | 心跳保活 | 已连接 | 1. 保持连接 3 分钟 2. 观察状态 | 连接保持正常 |
|
||
| E2E-6 | 断线重连 | 已连接 | 1. 断开网络 2. 恢复网络 3. 等待重连 | 自动重连成功 |
|
||
| E2E-7 | 密码验证 | 已配置密码 | 1. 管理端使用错误密码连接 | 连接被拒绝 |
|
||
|
||
### 8.5 手动验收测试清单
|
||
|
||
#### UI 验收
|
||
|
||
- [ ] 主窗口正常显示,无布局错乱
|
||
- [ ] Tab 切换流畅,内容正确
|
||
- [ ] 状态栏实时更新连接状态
|
||
- [ ] 托盘图标正常显示
|
||
- [ ] 托盘菜单功能正常
|
||
- [ ] 深色/浅色主题正确应用
|
||
|
||
#### 功能验收
|
||
|
||
- [ ] 客户端 ID 显示正确且只读
|
||
- [ ] 密码可切换显示/隐藏
|
||
- [ ] 复制到剪贴板功能正常
|
||
- [ ] 密码修改流程完整
|
||
- [ ] 服务器配置可保存并生效
|
||
- [ ] 开机自启动可配置
|
||
- [ ] 依赖状态检测正确
|
||
- [ ] 依赖安装流程完整
|
||
|
||
#### 跨平台验收
|
||
|
||
| 平台 | 安装 | 启动 | 托盘 | 自启动 | 连接 |
|
||
|------|------|------|------|--------|------|
|
||
| macOS (Intel) | ☐ | ☐ | ☐ | ☐ | ☐ |
|
||
| macOS (Apple Silicon) | ☐ | ☐ | ☐ | ☐ | ☐ |
|
||
| Windows 10 | ☐ | ☐ | ☐ | ☐ | ☐ |
|
||
| Windows 11 | ☐ | ☐ | ☐ | ☐ | ☐ |
|
||
| Ubuntu 22.04 | ☐ | ☐ | ☐ | ☐ | ☐ |
|
||
| Fedora 39 | ☐ | ☐ | ☐ | ☐ | ☐ |
|
||
|
||
### 8.6 测试覆盖率要求
|
||
|
||
| 模块 | 最低覆盖率 |
|
||
|------|-----------|
|
||
| core/ | 80% |
|
||
| message/ | 90% |
|
||
| utils/ | 70% |
|
||
| 整体 | 75% |
|
||
|
||
### 8.7 性能验收指标
|
||
|
||
| 指标 | 要求 |
|
||
|------|------|
|
||
| 应用启动时间 | < 3 秒 |
|
||
| 内存占用(空闲) | < 100 MB |
|
||
| CPU 占用(空闲) | < 1% |
|
||
| 连接建立时间 | < 5 秒 |
|
||
| 心跳延迟 | < 100ms (局域网) |
|
||
|
||
---
|
||
|
||
## 参考文档
|
||
|
||
| 文档 | 路径 |
|
||
|------|------|
|
||
| 需求文档 | [@agent-client.md](agent-client.md) |
|
||
| qiming-rustdesk | [@vendorsdoc/qiming-rustdesk.md](vendorsdoc/qiming-rustdesk.md) |
|
||
| rustdesk-server | [@vendorsdoc/rustdesk-server.md](vendorsdoc/rustdesk-server.md) |
|
||
| rcoder | [@vendorsdoc/rcoder.md](vendorsdoc/rcoder.md) |
|
||
| gpui-component | [@vendorsdoc/gpui-component.md](vendorsdoc/gpui-component.md) |
|
||
| auto-launch | [@vendorsdoc/auto-launch.md](vendorsdoc/auto-launch.md) |
|
||
| tray-icon | [@vendorsdoc/tray-icon.md](vendorsdoc/tray-icon.md) |
|
||
| enigo | [@vendorsdoc/enigo.md](vendorsdoc/enigo.md) |
|
||
| cargo-packager | [@vendorsdoc/cargo-packager.md](vendorsdoc/cargo-packager.md) |
|