把 Pi 做成 Windows 应用:Electron 还是 Tauri?
原创 · 约 26 分钟阅读 · 阅读 --

把 Pi 做成 Windows 应用:Electron 还是 Tauri?

作者: 字与码

AI 工程

古董级程序员,从大厂到创业公司,现在还在一线做 AI 相关开发。微信公众号「字与码」会继续更新工程实践、新技术判断,以及这些年踩过的坑。文章若对你有用,欢迎顺手关注。

Pi 是一个很有意思的 Coding Agent。它没有急着把所有功能都塞进核心,而是提供 extensions、skills、prompt templates、RPC 和 SDK,让使用者自己决定它最后长成什么样。

于是很自然会冒出一个产品问题:能不能把 Pi 封装成 Windows 独立应用,让用户像安装普通桌面软件一样使用,而不是先装 Node.js、再跑 npm、最后打开终端?

答案是可以。真正需要选择的不是“能不能封装”,而是桌面壳和 Pi Runtime 之间怎么连接。

我把可行方案收敛成两条:

  • Electron + React,直接在 Node.js 进程里调用 Pi SDK。
  • Tauri + React,把 Pi 做成 Node Sidecar,通过 RPC 通信。

两条路都能做到最终用户不预装 Pi。差别主要落在开发复杂度、进程隔离、安装体积和扩展兼容性上。

先说清楚:不是把 pi.dev 套进 WebView

pi.dev 是官网和文档站,不是一个等待套壳的在线 IDE。单纯用 WebView 打开它,只会得到一个网站窗口,不会得到能读本地仓库、执行命令、保存会话的 Coding Agent。

真正需要封装的是 @earendil-works/pi-coding-agent 提供的运行时能力。Pi 官方给了四种运行形态:

  • 交互式终端;
  • print / JSON 事件流;
  • 基于 stdin/stdout 的 RPC;
  • 可嵌入 Node.js 应用的 SDK。

这让桌面端有了两种很自然的边界。Electron 自带 Node.js,可以直接吃 SDK;Tauri 的 WebView 没有 Node.js Runtime,更适合把 Pi 放进独立 Sidecar。

Electron 直接嵌入 Pi SDK,Tauri 通过 RPC Sidecar 连接 Pi Runtime

Electron:Pi SDK 直接进主进程

Electron 同时带 Chromium 和 Node.js。它的主进程就是一个 Node.js 环境,可以加载 npm 包、访问文件系统,也可以创建额外的 Utility Process。官方的进程模型文档对这条边界写得很清楚:渲染进程负责界面,主进程和 Utility Process 承担系统能力与高风险工作。

开发时只需要把 Pi 作为项目依赖:

npm install @earendil-works/pi-coding-agent

然后在主进程或单独的 Agent Utility Process 里创建会话:

import {
  createAgentSession,
  ModelRuntime,
  SessionManager,
} from "@earendil-works/pi-coding-agent"

const modelRuntime = await ModelRuntime.create()
const { session } = await createAgentSession({
  sessionManager: SessionManager.inMemory(),
  modelRuntime,
})

await session.prompt("检查当前项目并给出测试建议")

这里有一个容易说错的点:开发项目安装了 Pi npm 包,不等于最终用户要先安装 Pi。

Electron 打包时会把 Node Runtime、应用代码和依赖一起放进安装包。用户拿到 .exe、NSIS 或 MSIX 安装包后,不需要执行 npm install,也不需要让全局 pi 出现在 PATH 里。Electron 官方的应用打包说明本来就是把运行时和应用资源一起交付。

我不会让 Pi 直接跑在渲染进程里。更稳妥的边界是:

React Renderer
      │ 受限 IPC
Electron Main
      │ MessagePort
Agent Utility Process

Pi SDK + workspace + provider

React 页面只拿到经过白名单约束的 IPC,例如发送 prompt、批准一次工具调用、读取会话摘要。API Key、文件读写、命令执行都留在 Agent 进程。这样即使前端出现 XSS,也不至于直接获得完整 Node 权限。

Electron 这条路最大的优点是直接。Pi SDK、界面和桌面运行时都在 TypeScript 生态里,事件对象不用再翻译成另一套协议,extensions 和动态 npm 依赖也更容易兼容。

代价同样明显:Chromium 和 Node.js 都要随应用发布,包会更大;如果 Agent 卡死或某个扩展崩溃,必须做好 Utility Process 的退出、重启和会话恢复,不能把所有工作都压在 Electron 主进程里。

Tauri:不要硬嵌 SDK,走 RPC Sidecar

Tauri 的前端也是 Web 技术,但窗口底层使用系统 WebView,核心进程是 Rust。它没有给 React 页面提供一个完整 Node.js Runtime,所以不能照搬 Electron 的 SDK 接法。

最稳妥的结构是:

React WebView
      │ Tauri IPC
Rust Core
      │ stdin / stdout
Pi Node Sidecar
      │ pi --mode rpc
Git Bash + workspace + provider

Pi 的 RPC 模式就是为 IDE、自定义 UI 和非 Node 应用准备的。客户端向 stdin 写一行 JSON 命令,Pi 从 stdout 返回响应并持续推送事件。prompt、steer、follow-up、abort、模型切换、会话状态、工具执行和图片输入都已经有协议定义。

启动形式很简单:

pi --mode rpc --session-dir <app-data>/sessions

但桌面应用不能假设用户已经全局安装了 pi。正确做法是把 Node 应用打成自包含的 Sidecar,再由 Tauri 一起打包。Tauri 官方不仅支持在 externalBin 中嵌入外部二进制,还专门提供了将 Node.js 应用打成自包含 Sidecar的说明,目标就是让最终用户无需安装 Node.js。

这样一来,用户安装的仍然是一个应用:

MyPiDesktop.exe
resources/
  pi-sidecar.exe
  bash/
  defaults/

Tauri 负责窗口、系统菜单、更新和安全权限;Sidecar 负责 Pi Runtime。Pi 崩溃时可以独立重启,不会带走窗口;前端也不需要接触模型密钥或文件系统。

RPC 有一个不太起眼但必须做对的细节:它采用严格的 LF 分隔 JSONL。官方明确提醒,不要用会把 Unicode 行分隔符也当换行的通用 line reader。客户端应维护字节缓冲区,只按 \n 切记录,必要时去掉末尾的 \r

Tauri 的麻烦不在“能不能启动进程”,而在打包以后还要支持什么。把固定版本 Pi 编进单个 Sidecar 不难;如果还希望用户安装任意 Pi Package、动态 TypeScript extension 和原生 npm 依赖,单文件打包就可能和动态模块加载冲突。此时要么随 Sidecar 携带一个受控 Node Runtime,要么限制扩展范围,不能一边承诺任意插件,一边把运行环境裁得只剩一个不可变 exe。

用户不需要装 Pi,但 Bash 还在

无论 Electron 还是 Tauri,Pi 的 npm 包都可以随应用发布。Windows 上真正绕不过去的是 Bash。

Pi 官方的 Windows 配置说明列出了查找顺序:

  1. settings.json 指定的自定义 shell;
  2. C:\Program Files\Git\bin\bash.exe
  3. PATH 中其他可用的 bash.exe

对于开发者工具,最省事的方案是首次启动检查 Git for Windows,缺少时给出安装引导。要做面向普通用户的“一次安装即可用”,则可以随应用携带 Portable Git Bash,并通过 Pi 设置指向固定路径。

这套方案不依赖 WSL。开发机、构建机和最终用户的电脑都只需要 Windows;Pi 使用应用自带的 Git Bash,用户不必启用 WSL、安装 Linux 发行版或完成额外初始化。

所以“无需预装”的完整含义应该是:

组件Electron + SDKTauri + Sidecar
全局 Pi CLI不需要不需要
Node.jsElectron 自带编进 Sidecar 或随应用携带
ChromiumElectron 自带使用系统 WebView2
Bash检测 Git Bash 或随包携带检测 Git Bash 或随包携带
模型凭证用户仍需登录或配置用户仍需登录或配置

Tauri 的壳通常更轻,但如果最终同时带上 Node Sidecar、Pi、Portable Git Bash 和模型相关资源,体积优势会比一个普通 CRUD 桌面应用小。不能只拿空项目安装包比较。

真正费时间的是安全边界

桌面窗口做出来并不难,难的是让一个能执行命令的 Agent 进入普通用户电脑后仍然可控。

Pi 的安全文档明确说明,它没有内置沙箱。Pi 以启动它的用户权限运行,能读写该用户可访问的文件,也能执行普通本地进程。Project Trust 只控制项目资源是否自动加载,不是命令和文件访问的隔离边界。

因此桌面版至少要补上这些能力:

  • 工作区选择与路径白名单,默认不允许跨工作区写入;
  • 命令、写文件、删除文件和外部网络访问的审批;
  • 工具调用、退出码、diff 和用户决策的审计记录;
  • API Key 写入 Windows Credential Manager,而不是 LocalStorage;
  • extension 和 package 安装前展示来源、版本和能力;
  • 无人值守任务运行在 Windows Sandbox、容器或远端受控环境。

Electron 和 Tauri 都不会自动替你解决这些问题。Tauri 的 capability 配置能缩小“前端可以调用哪些本地命令”,Electron 的 context isolation 和 IPC 白名单也能缩小渲染进程权限,但 Pi Runtime 自己仍然是一个拥有本地权限的进程。

Electron 和 Tauri 怎么选

如果目标是尽快做出一个可用原型,我会选 Electron。

原因不是 Electron 更先进,而是它和 Pi SDK 的边界最短:同一套 TypeScript 类型、同一个 npm 生态、最少的协议胶水。先把项目选择、聊天流、工具调用、diff 审批、模型切换和会话恢复跑通,比一开始优化几十 MB 安装体积更有价值。

如果目标是长期维护的正式 Windows 产品,我会认真考虑 Tauri + RPC Sidecar。

Sidecar 带来的进程边界很清楚:Rust Core 管应用生命周期,Pi Runtime 可以独立升级、崩溃和重启;前端只接触经过授权的命令。代价是多了一层 JSONL 协议、Sidecar 构建矩阵和 Node 动态生态兼容。

维度Electron + Pi SDKTauri + Pi RPC Sidecar
接入速度较慢
Pi API 使用直接调用RPC 映射
进程隔离需要主动拆 Utility ProcessSidecar 天然独立
动态扩展兼容更好取决于 Sidecar 打包策略
UI Runtime自带 Chromium系统 WebView2
Rust 要求
故障恢复自己管理 Agent 子进程独立重启 Sidecar
适合阶段MVP、快速验证正式产品、长期维护

还有一条折中路线:即使第一版选 Electron,也不要让 React 组件直接依赖 Pi SDK 的所有类型。先定义一层自己的 AgentRuntime 接口:

interface AgentRuntime {
  prompt(input: PromptInput): Promise<void>
  abort(): Promise<void>
  approve(requestId: string): Promise<void>
  subscribe(listener: (event: AgentEvent) => void): () => void
  getSession(): Promise<SessionSnapshot>
}

Electron 版实现 SdkAgentRuntime,以后如果需要迁移 Tauri,再实现 RpcAgentRuntime。这样迁移的是运行时适配层,而不是重写整套 UI。

Tauri 版本直接在 Windows 里开发

如果已经决定做 Tauri 版本,我建议直接在 Windows 原生环境里使用 Codex,不把 WSL 作为主开发环境。

这不是编辑器偏好,而是构建目标决定的。Tauri 的 Windows 开发环境需要 Microsoft C++ Build Tools、WebView2 和 Rust stable-msvc 工具链;Pi Sidecar 最后也必须生成 Windows .exe。Tauri 打包外部二进制时还会按目标 triple 查找文件,例如:

src-tauri/binaries/
  pi-sidecar-x86_64-pc-windows-msvc.exe

在 WSL 里直接构建,默认得到的是 Linux 目标,不是可以放进 Windows 安装包的 Sidecar。即使再做交叉编译,后面仍要回到 Windows 验证 WebView2、文件路径、进程退出、Credential Manager、Portable Git Bash、NSIS / MSI 和代码签名,省不下真正麻烦的部分。

我会把项目放在 Windows 文件系统,例如 D:\work\pi-desktop,让 Codex 从 PowerShell 或 Windows Terminal 中工作:

pnpm install
pnpm tauri dev
pnpm test
pnpm tauri build

开发机安装 Node.js LTS、Rust stable-msvc、Visual Studio C++ Build Tools 和 WebView2。项目的构建脚本再把 Pi 编成自包含的 Windows Sidecar,并把 Portable Git Bash 一起放入安装包。最终交付物从开发到运行都不需要 WSL。

CI 也使用 Windows Runner 生成安装包。正式对外发布时再接入 Windows 代码签名;否则用户从浏览器下载安装包时,可能遇到 SmartScreen 的未受信任提示。Tauri 的 Windows 签名文档也把签名和 Windows 安装包构建放在同一条发布链路里。

我会怎么落第一版

第一版只做一条完整闭环:

选择项目
  → 登录或配置模型
  → 创建会话
  → 展示流式输出和工具调用
  → 审批危险动作
  → 查看 diff 与测试结果
  → 恢复历史会话

不急着做插件市场、多 Agent 调度和云同步。先验证普通开发者能否在不打开终端的情况下,理解 Pi 正在做什么,并在关键节点接管。

如果团队主要是 TypeScript,首版直接 Electron + Pi SDK。等产品边界稳定,再根据安装体积、内存、崩溃隔离和原生能力的真实数据决定是否迁移 Tauri。

如果一开始就明确要做长期维护的 Windows 客户端,团队也有 Rust 经验,那就直接在 Windows 原生环境采用 Tauri + Pi RPC Sidecar。但要把 Sidecar 当成正式子产品:独立版本、独立日志、健康检查、优雅退出、崩溃恢复和协议兼容都不能省。

最终用户不该关心里面有没有 Pi、Node 或 Bash。他只应该安装一个经过签名的应用,选择一个项目,然后清楚地看到 Agent 读了什么、改了什么、为什么需要一次授权。

这才是“把 Pi 做成 Windows 应用”真正要完成的工作。

参考资料

打开原图 ↗