196 KiB
Agent Client 技术设计规格文档
版本:v1.1.0 最后更新:2026-01-29 状态:设计中
目录
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 界面功能
核心功能:
- 显示客户端唯一 ID(只读,服务器分配,不可修改)
- 显示连接密码(可切换显示/隐藏)
- 复制 ID 和密码到剪贴板
- 密码修改入口:点击按钮跳转到设置 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 状态定义
/// 客户端信息状态
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 位,包含字母和数字 │ │
│ │ │ │
│ │ 确认新密码: [🔒 ****************] [👁 显示/隐藏] │ │
│ │ 两次输入不一致时显示红色提示 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 错误信息: 原密码验证失败 │ │
│ │ 成功信息: 密码修改成功! │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ [✗ 取消] [✓ 确认修改] │
│ │
└─────────────────────────────────────────────────────────────────┘
验证规则:
- 原密码必填,调用服务器验证
- 新密码长度 >= 8 位
- 新密码必须包含字母和数字(可选:特殊字符)
- 两次新密码输入必须一致
- 新密码不能与原密码相同
验证时机:
- 实时验证:输入时检查格式
- 提交时验证:再次检查所有规则
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 |
连接测试逻辑:
- 用户输入服务器地址后,可点击"连接测试"按钮验证连通性
- 测试顺序:
- 测试 hbbs 连接(TCP)
- 测试 hbbr 连接(TCP)
- 测试 API 服务器连接(HTTP)
- 测试结果反馈:
- ✅ 全部成功:显示"所有服务器连接正常"
- ❌ 部分失败:显示失败的服务器列表及错误原因
- 超时:显示"连接超时,请检查网络或服务器地址"
连接流程:
客户端启动
│
▼
┌─────────────────┐
│ 读取配置文件 │
└────────┬────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 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
# 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 位,包含字母和数字 │ │
│ │ • 不要与其他账户使用相同密码 │ │
│ │ • 不要将密码告知他人 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
修改密码流程:
- 点击"修改密码"按钮 → 弹出密码修改对话框
- 用户输入:新密码 → 确认新密码
- 点击"确认修改" → 验证格式 → 保存新密码
- 修改成功后,管理端需要重新配置新密码才能连接
重要提示:
- 密码修改后,所有已连接的管理端会立即断开
- 管理端需要更新配置文件中的密码才能重新连接
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 │ - │ - │ 可选 │ [安装]│ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ 状态图标说明: │
│ 🟢 已安装 - 正常可用 │
│ 🟡 可更新 - 有新版本可用 │
│ 🔴 未安装 - 尚未安装 │
│ 🟠 安装失败 - 上次安装出错 │
│ 🔵 安装中 - 正在下载/安装 │
│ ⚪ 可选 - 未安装但不影响核心功能 │
│ │
│ 来源图标说明: │
│ [系统] 系统全局安装 │
│ [客户端] 客户端隔离目录 │
│ │
└─────────────────────────────────────────────────────────────────┘
改进点:
- 顶部增加总状态卡片,一目了然
- 使用颜色圆点代替文字状态
- 显示来源类型(系统/客户端)
- 可选依赖用灰色显示,不影响总状态统计
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) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 🟢 麦克风权限 │ │
│ │ ───────────────────────────────────────────────────── │ │
│ │ 影响: 语音通话功能(可选) │ │
│ │ 用途: 用于语音通话功能 │ │
│ │ 状态: ✓ 已授权 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
改进点:
- 顶部显示总体统计(需要 X/Y 个权限)
- 权限卡片按影响程度排序:核心权限在前,可选权限在后
- 每个权限卡片标注"影响"(功能影响说明)
- 颜色区分状态:🟢 正常、🔴 缺失、🟡 部分
2.6.2 权限检测逻辑
/// 权限管理器
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 界面功能
核心功能:
- 查看被管理端远程桌面画面
- 发送键盘鼠标输入到被管理端
- 画质调节(流畅/标准/高清)
- 全屏/窗口模式切换
- 文件传输入口
- 连接状态显示
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 界面功能
核心功能:
- 发送 prompt 到 Agent 执行
- 查看 Agent 执行过程中的消息输出
- 中止正在执行的任务
- 查看执行历史
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 配置:
[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() |
连接流程:
// 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
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
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
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 的函数接口。
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 管理全局状态,客户端直接调用:
// 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 处理流程(客户端直接调用)
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)
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)
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)
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 模块:
// 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 业务通道消息格式
// 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 中使用业务通道
// 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 和相关类型。
// vendors/qiming-rustdesk/src/lib.rs 新增
mod business_channel;
pub use self::business_channel::*;
使用方式:
// 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 消息定义示例
// 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 + 过期时间 | 管理端会话保持 |
密码存储:
/// 密码存储策略
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 管理:
/// 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 |
| 密码套件 | 仅支持前向安全的套件 |
| 证书验证 | 必须验证服务器证书 |
数据加密:
/// 敏感数据加密
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、文件名、大小 |
| 配置变更 | 密码修改等配置变更 | 时间、变更类型、变更结果 |
/// 审计日志结构
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 |
/// 重连策略配置
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 | 超过则拒绝入队 |
/// 离线消息队列
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 连接超时 |
/// 带宽管理器
struct BandwidthManager {
/// 最大上传速度(字节/秒)
max_upload: u64,
/// 最大下载速度(字节/秒)
max_download: u64,
/// 令牌桶
upload_bucket: TokenBucket,
download_bucket: TokenBucket,
}
struct TokenBucket {
capacity: u64,
tokens: f64,
refill_rate: f64, // 令牌填充速度
}
动态带宽调整:
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 网络状态监听
/// 网络状态监听器
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
资源文件格式:
# 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"
# 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 基础设施
/// 国际化管理器
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 日期/时间/数字格式化
/// 区域格式化器
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 文本动态切换
/// 支持动态切换语言的 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 任务会话状态
/// 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 次 |
/// 超时管理器
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 并发控制
/// 并发任务管理器
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 僵尸会话检测与清理
/// 会话清理策略
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 全局快捷键
/// 全局快捷键管理器
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 系统通知集成
/// 系统通知管理器
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 系统事件处理
/// 系统事件监听器
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 系统集成配置
/// 系统集成配置
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 开机自启动与系统托盘集成
/// 启动行为配置
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 |
| qiming-rustdesk | @vendorsdoc/qiming-rustdesk.md |
| rustdesk-server | @vendorsdoc/rustdesk-server.md |
| rcoder | @vendorsdoc/rcoder.md |
| gpui-component | @vendorsdoc/gpui-component.md |
| auto-launch | @vendorsdoc/auto-launch.md |
| tray-icon | @vendorsdoc/tray-icon.md |
| enigo | @vendorsdoc/enigo.md |
| cargo-packager | @vendorsdoc/cargo-packager.md |