# 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 { 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` | 启动任务,流式返回输出 | | `cancel_agent(session_id)` | `Result<()>` | 取消正在执行的任务 | | `get_agent_status(session_id)` | `Result` | 查询任务状态 | | `list_active_agents()` | `Vec` | 列出所有活跃任务 | --- ## 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 { 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 { 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 { // 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, /// 配置 config: BusinessChannelConfig, /// 消息发送器 sender: Arc>>>>, /// 消息接收器(用于内部路由) receiver: Arc>>>>, } impl BusinessChannel { /// 从底层 Stream 创建业务通道 pub async fn from_stream( stream: Arc, config: Option, ) -> ResultType { 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)>> { 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)> { 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 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, /// 配置 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) -> 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) -> 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 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 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; /// 验证密码 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; } ``` #### 5.10.2 传输安全 **TLS 配置要求**: | 配置项 | 要求 | |--------|------| | 最低 TLS 版本 | TLS 1.2 | | 推荐 TLS 版本 | TLS 1.3 | | 密码套件 | 仅支持前向安全的套件 | | 证书验证 | 必须验证服务器证书 | **数据加密**: ```rust /// 敏感数据加密 trait SensitiveDataEncryption { /// 加密敏感配置(密码、Token 等) fn encrypt_sensitive(&self, data: &[u8]) -> Vec; /// 解密敏感配置 fn decrypt_sensitive(&self, encrypted: &[u8]) -> Vec; /// 敏感信息脱敏(日志输出时) fn redact_sensitive(&self, data: &str) -> String; } ``` #### 5.10.3 审计日志 **审计事件类型**: | 事件类型 | 说明 | 记录内容 | |----------|------|----------| | 连接事件 | 客户端连接/断开 | 时间、客户端ID、连接类型、IP地址 | | 认证事件 | 登录成功/失败 | 时间、客户端ID、认证方式、结果 | | 任务事件 | Agent 任务开始/完成/失败 | 时间、任务ID、任务内容摘要 | | 文件事件 | 文件传输开始/完成 | 时间、文件ID、文件名、大小 | | 配置变更 | 密码修改等配置变更 | 时间、变更类型、变更结果 | ```rust /// 审计日志结构 struct AuditLog { timestamp: DateTime, event_type: AuditEventType, client_id: String, user_id: String, action: String, details: serde_json::Value, ip_address: Option, 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>>, } struct QueuedMessage { /// 消息内容 payload: Vec, /// 消息类型 message_type: u32, /// 入队时间 enqueued_at: DateTime, /// 重试次数 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, /// 语言资源 resources: DashMap, /// 回退资源 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) -> 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) -> 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, task_id: String, }, /// 执行中 Active { started_at: DateTime, session_id: String, task_id: String, }, /// 正在停止 Terminating { started_at: DateTime, 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) -> 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>>, /// 等待队列 waiting_queue: Arc>>, } impl ConcurrencyManager { /// 提交任务 async fn submit(&self, request: TaskRequest) -> Result { 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) -> 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>>, } 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; /// 取消通知 async fn cancel(&self, id: NotificationId); /// 设置通知回调 fn set_callback(&self, callback: NotificationCallback); } struct NotificationRequest { title: String, body: String, icon: Option, urgency: NotificationUrgency, timeout: Duration, actions: Vec, /// 点击通知时的回调 on_click: Option, } enum NotificationUrgency { Low, Normal, Critical, } struct NotificationAction { id: String, label: String, icon: Option, } ``` #### 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) |