--- date: "2026-06-13" status: active depth: standard origin: null --- # feat: Tauri 2.x Desktop Client ## Summary 创建 Tauri 2.x 桌面客户端外壳,将现有 Vue 3 GUI 包装为跨平台桌面应用(Windows/macOS/Linux)。Tauri 管理窗口和 Python 后端进程生命周期,前端代码仅做最小适配(动态 API baseURL + Splash 启动流程),不修改任何 GUI 组件、布局、样式或业务逻辑。 ## Problem Frame 当前 AgentKit GUI 只能通过浏览器访问(`agentkit gui` 启动后自动打开浏览器),用户需要: 1. 手动启动后端进程 2. 在浏览器中操作,缺乏原生体验(无系统托盘、无桌面集成、无自动更新) 3. 无法像桌面应用一样安装、启动、关闭 竞品(Cursor、Devin)均提供原生桌面客户端。AgentKit 需要对齐这一体验标准。 ## Key Technical Decisions **KTD-1: Tauri 2.x 作为桌面壳。** Tauri 使用系统 WebView(不打包 Chromium),安装包 ~5-10MB + PyInstaller 产物 ~30-80MB,总计远小于 Electron 方案(~200MB+)。启动快(窗口 <500ms),内存低(~110-200MB vs Electron ~300-500MB)。Tauri 2.x 插件化架构更灵活,capabilities 权限模型更安全,且是长期维护版本。 **KTD-2: PyInstaller 打包 Python 后端为 sidecar。** PyInstaller 将 Python 后端打包为独立单文件二进制,Tauri 通过 `tauri-plugin-shell` sidecar API 管理其生命周期。比 Nuitka 兼容性更好,社区更成熟。 **KTD-3: 端口 0 + stdout 解析实现端口发现。** Python 后端以 `--port 0` 启动,OS 分配随机可用端口,uvicorn 启动后通过 stdout 输出 `AGENTKIT_PORT=XXXXX`,Rust 侧解析后通过 Tauri IPC 传递给前端。避免端口冲突,无需用户配置。 **KTD-4: 前端最小适配策略。** 仅修改 `api/base.ts`(动态 baseURL)和 `App.vue`(Splash 启动流程),新增 `api/tauri.ts`(Tauri invoke 封装)和 `TitleBar.vue`(自定义标题栏)。所有现有 GUI 组件、布局、样式、业务逻辑不做任何修改。 **KTD-5: 双窗口 Splash 方案。** Tauri 配置两个窗口:splash(小窗口,加载动画)和 main(主窗口,初始隐藏)。后端就绪后 Rust 侧关闭 splash、显示 main。比前端内 Splash 更干净,不侵入现有 App.vue 的布局逻辑。 ## High-Level Technical Design ``` ┌──────────────────────────────────────────────────────┐ │ Tauri Application (Rust) │ │ │ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ │ │ Splash │ │ Main Window │ │ System Tray │ │ │ │ (loading) │ │ (Vue 3 SPA) │ │ (minimize) │ │ │ └─────────────┘ └──────┬───────┘ └─────────────┘ │ │ │ HTTP/WS │ │ ┌───────────────────────▼──────────────────────────┐│ │ │ Python Backend (sidecar process) ││ │ │ FastAPI + uvicorn on random port ││ │ └──────────────────────────────────────────────────┘│ │ │ │ Rust Modules: │ │ - sidecar.rs: start/stop/health Python process │ │ - tray.rs: system tray menu │ │ - lib.rs: Tauri Builder + plugin registration │ └──────────────────────────────────────────────────────┘ ``` **启动序列:** ``` 1. 用户双击桌面图标 → Tauri 启动 2. Rust setup() → 创建系统托盘 3. Splash 窗口显示(加载动画) 4. 前端 invoke('start_backend') 5. Rust spawn sidecar: agentkit-server --port 0 6. Python 绑定随机端口 → stdout: AGENTKIT_PORT=XXXXX 7. Rust 解析端口 → 存入 BackendState → emit('backend-ready') 8. 前端设置 apiBaseURL → 健康检查通过 9. Rust 关闭 splash → 显示 main 窗口 ``` **关闭序列:** ``` 1. 用户点击关闭按钮 → 窗口隐藏到托盘(不退出) 2. 托盘菜单"退出" → Rust 发送 SIGTERM 给 Python 进程 3. Python 优雅关闭 → Tauri app.exit(0) ``` ## Scope Boundaries **在范围内:** - Tauri 2.x 项目脚手架和配置 - Rust 侧 sidecar 进程管理(启动/停止/健康检查/端口发现) - 前端最小适配(api/base.ts 动态 baseURL、api/tauri.ts 封装、App.vue Splash 流程、TitleBar.vue) - 系统托盘(显示/隐藏窗口、退出) - 单实例锁 - 自定义标题栏(无边框窗口) - PyInstaller 打包脚本 - 跨平台 CI 构建(GitHub Actions) **延迟到后续迭代:** - 自动更新器(tauri-plugin-updater 集成 + 签名密钥 + 发布服务器) - 应用签名和公证(macOS code signing / Windows Authenticode) - 多语言支持 - 离线模式 - 深度链接(deep link / URL scheme 注册) - 文件关联 **不在范围内:** - 修改现有 GUI 组件、布局、样式 - 修改后端 API 或业务逻辑 - 移动端适配(Tauri 2.x 支持 iOS/Android,但不在本次范围) - 多用户协作 ## Implementation Units ### U1. Tauri 2.x 项目脚手架 **Goal:** 创建 Tauri 2.x 项目结构,配置构建管线,验证 dev 模式可运行。 **Requirements:** KTD-1, KTD-4 **Dependencies:** 无 **Files:** - `src-tauri/Cargo.toml` — Rust 依赖(tauri 2, tauri-plugin-shell, tauri-plugin-single-instance, serde, tokio, reqwest) - `src-tauri/tauri.conf.json` — Tauri 核心配置(窗口、sidecar 声明、bundle 设置) - `src-tauri/capabilities/default.json` — 权限声明 - `src-tauri/src/main.rs` — Rust 入口 - `src-tauri/src/lib.rs` — Tauri Builder(占位,后续单元填充) - `src-tauri/build.rs` — 构建脚本 - `src-tauri/icons/` — 应用图标(占位) - `package.json` — 添加 Tauri CLI 和 API 依赖 **Approach:** 1. 在项目根目录初始化 Tauri 2.x:`npm create tauri-app@latest` 或手动创建 `src-tauri/` 目录 2. `tauri.conf.json` 配置双窗口(splash + main),main 初始 `visible: false` 3. `build.beforeDevCommand` 指向前端 dev 脚本,`build.beforeBuildCommand` 指向前端 build 脚本 4. `build.frontendDist` 指向前端构建输出目录 5. `bundle.externalBin` 声明 sidecar 二进制名 6. 安装前端依赖:`@tauri-apps/api@^2`, `@tauri-apps/plugin-shell@^2` 7. 验证 `npm run tauri dev` 能启动窗口 **Patterns to follow:** Tauri 2.x 官方项目结构 **Test scenarios:** - `npm run tauri dev` 启动后显示 Tauri 窗口(前端内容可能为空,因为后端未启动) - `npm run tauri build` 能生成安装包(可能因缺少 sidecar 二进制而失败,此为预期) **Verification:** `npm run tauri dev` 能启动 Tauri 窗口并加载前端页面 --- ### U2. Rust Sidecar 进程管理 **Goal:** 实现 Python 后端进程的启动、停止、健康检查和端口发现。 **Requirements:** KTD-2, KTD-3 **Dependencies:** U1 **Files:** - `src-tauri/src/sidecar.rs` — sidecar 管理模块(新建) - `src-tauri/src/lib.rs` — 注册 invoke handler 和 BackendState - `src-tauri/Cargo.toml` — 添加 `regex` 依赖 **Approach:** 1. 定义 `BackendState` 结构体:`port: Mutex>`, `pid: Mutex>` 2. 实现 `start_backend` 命令: - 通过 `app.shell().sidecar("agentkit-server")` 创建 sidecar 命令 - 传入 `--port 0` 和 `AGENTKIT_GUI_MODE=1` 环境变量 - spawn 后监听 stdout/stderr,用正则解析 `AGENTKIT_PORT=(\d+)` - 端口就绪后 emit `backend-ready` 事件 - 带超时等待(30s) 3. 实现 `get_backend_port` 命令:返回当前端口 4. 实现 `stop_backend` 命令:发送 SIGTERM(Unix)或 taskkill(Windows) 5. 实现 `check_backend_health` 命令:HTTP GET `http://127.0.0.1:{port}/api/v1/health` 6. 在 `lib.rs` 中注册所有命令和 `BackendState` managed state **Technical design:** ``` start_backend() flow: sidecar("agentkit-server").args(["--port", "0"]).env("AGENTKIT_GUI_MODE", "1") → spawn → tokio::spawn listen stdout/stderr → parse AGENTKIT_PORT=XXXXX → store in BackendState.port → emit("backend-ready", { port }) → return port stop_backend() flow: BackendState.pid → SIGTERM/taskkill → clear port & pid ``` **Patterns to follow:** `tauri-plugin-shell` sidecar API 文档 **Test scenarios:** - 启动 sidecar 后 `get_backend_port` 返回有效端口号 - `check_backend_health` 在后端就绪后返回 true - `stop_backend` 能成功终止 Python 进程 - 后端启动超时(30s)时返回错误 - 后端意外崩溃时 Rust 侧能检测到并清理状态 **Verification:** 手动测试 `invoke('start_backend')` → `invoke('get_backend_port')` → `invoke('check_backend_health')` → `invoke('stop_backend')` 全流程 --- ### U3. 前端适配 — 动态 API baseURL + Splash 启动流程 **Goal:** 前端支持 Tauri 环境,动态设置 API baseURL,添加 Splash 启动流程。 **Requirements:** KTD-3, KTD-4, KTD-5 **Dependencies:** U2 **Files:** - `src/agentkit/server/frontend/src/api/base.ts` — 修改:支持 Tauri 环境动态 baseURL - `src/agentkit/server/frontend/src/api/tauri.ts` — 新增:Tauri invoke 封装 - `src/agentkit/server/frontend/src/App.vue` — 修改:添加 Splash 启动流程 - `src/agentkit/server/frontend/src/components/layout/SplashScreen.vue` — 新增:Splash 加载组件 - `src/agentkit/server/frontend/src/components/layout/TitleBar.vue` — 新增:自定义标题栏 - `src/agentkit/server/frontend/src/views/AgentLayout.vue` — 修改:集成 TitleBar - `src/agentkit/server/frontend/vite.config.ts` — 修改:Tauri 环境适配 **Approach:** 1. **`api/tauri.ts`**:封装 Tauri invoke 调用 - `isTauri()` 检测是否在 Tauri 环境中(`'__TAURI_INTERNALS__' in window`) - `startBackend()` → `invoke('start_backend')` - `getBackendPort()` → `invoke('get_backend_port')` - `stopBackend()` → `invoke('stop_backend')` - `checkBackendHealth()` → `invoke('check_backend_health')` 2. **`api/base.ts`**:动态 baseURL - 新增 `initApiBaseURL()` 函数 - Tauri 环境:从 `getBackendPort()` 获取端口,设置 `apiBaseURL = http://127.0.0.1:{port}` - 浏览器环境:`apiBaseURL = ''`(保持现有相对路径行为) - `BaseApiClient` 的 `request()` 和 `createWebSocket()` 方法使用动态 baseURL 3. **`App.vue`**:Splash 启动流程 - 新增 `loading` 和 `loadingProgress` 响应式状态 - `onMounted` 时:如果 `isTauri()`,调用 `startBackend()` → `initApiBaseURL()` → `checkBackendHealth()` - 加载完成前显示 `SplashScreen`,完成后显示 `RouterView` - 浏览器环境保持现有行为不变 4. **`SplashScreen.vue`**:加载动画组件 - 品牌 Logo + 加载进度条 + 状态文字("正在启动后端..."、"正在连接...") - 使用现有 Design Token 变量 5. **`TitleBar.vue`**:自定义标题栏 - `data-tauri-drag-region` 使标题栏可拖拽 - 最小化/最大化/关闭按钮(关闭时隐藏到托盘) - 仅在 Tauri 环境中显示 6. **`vite.config.ts`**:Tauri 适配 - 读取 `TAURI_DEV_HOST` 环境变量 - `envPrefix` 添加 `TAURI_` - `server.watch.ignored` 排除 `src-tauri/` **Patterns to follow:** 现有 `api/base.ts` 的 `BaseApiClient` 模式;现有 Design Token 体系 **Test scenarios:** - 浏览器模式:所有 API 调用正常工作(相对路径不变) - Tauri 模式:`initApiBaseURL()` 设置正确的 `http://127.0.0.1:{port}` - Tauri 模式:WebSocket 连接到正确的 `ws://127.0.0.1:{port}/api/v1/...` - Splash 在后端就绪前显示,就绪后消失 - TitleBar 拖拽移动窗口,按钮功能正确 - 后端启动失败时显示错误信息 **Verification:** `npm run tauri dev` 启动后:Splash 显示 → 后端启动 → 主界面加载 → API 调用正常 --- ### U4. 系统托盘 + 单实例 + 窗口管理 **Goal:** 实现系统托盘、单实例锁和窗口关闭行为(最小化到托盘)。 **Requirements:** KTD-1 **Dependencies:** U2 **Files:** - `src-tauri/src/tray.rs` — 新增:系统托盘模块 - `src-tauri/src/lib.rs` — 修改:注册托盘、单实例插件、窗口事件 - `src-tauri/Cargo.toml` — 添加 `tauri-plugin-single-instance` **Approach:** 1. **系统托盘**(`tray.rs`): - 菜单项:显示主窗口 / 分隔线 / 退出 - 左键点击托盘图标:显示并聚焦主窗口 - 退出菜单项:先 `stop_backend()`,再 `app.exit(0)` 2. **单实例锁**: - 注册 `tauri-plugin-single-instance` - 第二个实例启动时:聚焦已有主窗口 3. **窗口关闭行为**: - `CloseRequested` 事件中 `api.prevent_close()` + `window.hide()` - 窗口隐藏到托盘而非退出 4. **Splash → Main 切换**: - `start_backend` 成功后,Rust 侧关闭 splash 窗口、显示 main 窗口 **Patterns to follow:** Tauri 2.x tray API 文档 **Test scenarios:** - 点击关闭按钮后窗口隐藏,托盘图标仍在 - 托盘"显示"菜单恢复窗口 - 托盘"退出"菜单停止后端并退出应用 - 左键点击托盘图标恢复窗口 - 启动第二个实例时聚焦已有窗口 **Verification:** 完整的窗口生命周期测试:启动 → 关闭到托盘 → 从托盘恢复 → 托盘退出 --- ### U5. PyInstaller 打包脚本 + Python 侧适配 **Goal:** 创建 PyInstaller 打包脚本,Python 后端支持 `--port 0` 并输出端口信息。 **Requirements:** KTD-2, KTD-3 **Dependencies:** U2 **Files:** - `scripts/build-backend.sh` — 新增:PyInstaller 打包脚本 - `scripts/build-backend.ps1` — 新增:Windows 版打包脚本 - `src/agentkit/cli/main.py` — 修改:`gui` 命令支持 `--port 0` - `src/agentkit/server/runner.py` — 修改:uvicorn 启动后输出 `AGENTKIT_PORT=XXXXX` **Approach:** 1. **Python 侧适配**: - `gui` 命令的 `--port` 参数支持 `0`(让 OS 分配随机端口) - uvicorn 启动后,在 lifespan startup 中检测实际绑定端口 - 通过 stdout 输出 `AGENTKIT_PORT={port}`(flush=True),供 Rust 解析 - 当 `--port 0` 时,host 默认改为 `127.0.0.1`(仅本地访问,安全) 2. **PyInstaller 打包脚本**: - `--onefile` 模式打包 - `--name agentkit-server` 命名 - 包含必要的 `--hidden-import`(uvicorn.logging, agentkit.server 等) - `--add-data` 包含 agentkit.yaml 和 configs 目录 - 输出到 `src-tauri/binaries/` 并添加平台后缀 3. **平台后缀命名**: - macOS ARM: `agentkit-server-aarch64-apple-darwin` - macOS Intel: `agentkit-server-x86_64-apple-darwin` - Windows: `agentkit-server-x86_64-pc-windows-msvc.exe` - Linux: `agentkit-server-x86_64-unknown-linux-gnu` **Patterns to follow:** 现有 `agentkit gui` 命令的启动流程 **Test scenarios:** - `python -m agentkit gui --port 0` 启动后 stdout 输出 `AGENTKIT_PORT=XXXXX` - PyInstaller 打包的二进制能独立运行 - 打包的二进制支持 `--port 0` 和 `AGENTKIT_GUI_MODE=1` - 打包的二进制在无 Python 环境的机器上能运行 **Verification:** 在当前平台执行 `scripts/build-backend.sh`,产物放入 `src-tauri/binaries/`,`npm run tauri dev` 能通过 sidecar 启动后端 --- ### U6. 跨平台 CI 构建 **Goal:** 配置 GitHub Actions 自动构建 Windows/macOS/Linux 安装包。 **Requirements:** KTD-1 **Dependencies:** U1, U5 **Files:** - `.github/workflows/release-desktop.yml` — 新增:桌面客户端发布工作流 **Approach:** 1. 触发条件:推送 `v*` tag 2. 构建矩阵:macOS (aarch64 + x86_64)、Windows (x86_64)、Linux (x86_64) 3. 每个平台步骤: - Setup Node.js 20 + Rust stable + Python 3.11 - 安装 Linux 系统依赖(libwebkit2gtk-4.1-dev 等) - `pip install pyinstaller` + 执行 `scripts/build-backend.sh` - `npm ci` + `npm run tauri build` 4. 使用 `tauri-apps/tauri-action@v0` 构建和发布 5. 产物上传到 GitHub Release(draft) **Patterns to follow:** Tauri 官方 GitHub Actions 模板 **Test scenarios:** - 推送 tag 后工作流在所有 4 个目标上成功运行 - 产物正确上传到 GitHub Release **Verification:** 手动触发工作流或推送 tag,检查 GitHub Release 页面 --- ## Risks & Dependencies | 风险 | 影响 | 缓解 | |------|------|------| | PyInstaller 打包后部分依赖缺失 | 后端无法启动 | 逐步测试,添加 `--hidden-import` | | Linux WebView 兼容性差异 | GUI 渲染异常 | 测试主流发行版(Ubuntu 22.04+),文档说明依赖 | | Python sidecar 启动慢(3-5s) | 用户等待时间长 | Splash 加载画面 + 进度反馈 | | macOS 签名/公证缺失 | 用户安装时被 Gatekeeper 阻止 | 延迟到后续迭代,文档说明绕过方法 | | Tauri 2.x API 变更 | 代码需调整 | 锁定 Tauri 2.x 稳定版 | ## Assumptions - 用户机器上有 WebView2(Windows 10+ 自带)、WebKit(macOS 自带)、WebKitGTK(Linux 需安装) - Python 后端不需要 GPU 加速或其他特殊硬件 - 单用户本地使用场景,不需要多用户认证 - 首个版本不需要应用签名和公证 ## Open Questions - 自动更新服务器的部署方案(S3 / GitHub Releases / 自建)— 延迟到后续迭代 - 是否需要支持 Linux ARM64 — 视用户需求决定 - PyInstaller 产物体积优化(是否排除不必要的依赖)— 实现时评估