---
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<Option<u16>>`, `pid: Mutex<Option<u32>>`
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 产物体积优化（是否排除不必要的依赖）— 实现时评估
