把 pi-web(pi 编程智能体的网页界面,npm 包 @agegr/pi-web)打包成一个开箱即用的桌面应用:
双击即用,没有浏览器、没有地址栏、没有常驻终端窗口,目标机器无需预装任何运行时。
它不只是「pi-web 套壳」——而是一台开箱即用的 AI 桌面工作站:内置独立 Node 运行时,拷到空电脑双击即可使用。
核心特性
- 🧳 内置 Node.js 运行时 —— 目标机器无需 Node/npm,拷到空电脑双击即用。
- ⚡ 就地运行,首启秒开 —— 直接从(可写的)安装目录跑 pi-web,不做首启复制。
- 🔄 运行时自更新 —— App 内「检查更新」直接装
@agegr/pi-web@latest(npm 包自带预构建.next,免编译),独立更新 pi-web + pi-coding-agent,无需重新发版、不碰外壳代码。安装走 staging + 校验通过才原子换入,更新失败/断网/中途被杀都不会损坏正在用的运行时。 - 🩺 启动自检与自愈 —— 每次启动先验证运行时的原生模块是否真的能加载;发现是安装被中断留下的残缺文件,自动按锁定版本重装修复(不趁机升级),而不是让用户对着
server not ready in time干瞪眼。 - 🛡️ 无网络端口管道化通信 —— 1:1 复刻 DeepSeek Harness 架构,通过 FD 3/4 匿名字节分帧管道与
pi-app://特权协议直连 Next.js,整机完全不开放任何 127.0.0.1 端口,高安全、免端口冲突。 - 🪟 原生窗口 —— 关窗即停,进程树彻底回收无残留。
pi-web-desktop/
├── electron/
│ ├── main.js # 主进程:解析运行时、起内置 node 服务、注册 pi-app:// 协议、开窗、检查更新、退出清理
│ ├── host-bridge.js # 无头 Next.js 宿主桥接器:零端口监听,通过 MemorySocket 双工流直驱 Next.js
│ ├── host-process.js # 进程管道控制器:管理子进程生命周期,基于 FD 3/4 承载二进制分帧流
│ ├── host-protocol.js # 二进制分帧协议编解码器 (魔数 0x44534833)
│ ├── http-response-parser.js # HTTP/1.1 响应解析状态机:精准处理 chunked 与 content-length
│ ├── updater.js # npm 层:用内置 npm 查询版本 / 装到指定目录(installInto)
│ ├── runtime-guard.js # 运行时完整性:启动校验、staging 安装、原子换入、崩溃恢复
│ ├── preload.js # 最小安全桥(contextIsolation 开启)—— 自定义能力的暴露入口
│ ├── features/ # directory-picker / native-theme 等外壳后端逻辑
│ ├── loading.html / updating.html / healing.html / error.html
│ └── ui/ # ★ 自定义能力的前端页面(可选,见「开发约束」)
├── vendor/node/ # 内置 Node.js 运行时(node.exe + npm) → resources/node ← 构建输入(npm run seed:node)
├── runtime-seed/ # @agegr/pi-web 的 npm 生产安装(含 .next) → resources/runtime-seed ← 构建输入(npm run seed)
├── scripts/ # seed-node.ps1(供给 vendor/node)
│ # + test-runtime-guard.js(运行时守卫 / 版本比较回归测试,npm run test:guard)
│ # + test-framed-pipeline.js(FD 3/4 管道端到端并发测试)
│ # + hot-update-installed.js(一键热更本地已安装客户端)
├── build/ # 应用图标的 SVG 源 + 生成脚本(_make_icons.js)与产物(png/ico)
├── electron-builder.yml
└── package.json
构建输入 vs 入库源码:
vendor/、runtime-seed/都体积大、已 gitignore,需按下文重新准备。本地若存在pi-web/目录,那是已退役的 fork 工作副本(cking000bigdemon/pi-web,曾发布为@cking000/pi-web),桌面端已回归上游包,不再是构建输入。
应用叫 Pi(productName,package.json 和 electron-builder.yml 各一份——前者决定 dev 模式的 userData,后者决定打包产物)。appId 保持 com.agegr.piwebdesktop 不变,这样新安装包是就地升级,而不是并排装两份。
名字必须匹配
/^[-_+0-9a-zA-Z .]+$/。 electron-builder 只在productName满足这个正则时拿它当安装目录名,否则回退用 package.json 的name,NSIS 的instFilesPre发现$INSTDIR里不含那个串就再追加一层。
图标由 build/_make_icons.js 从 SVG 一次生成全部 PNG + ICO(借 pi runtime 的 sharp,外壳不新增依赖;ICO 是 hand-rolled PNG-compressed 格式,sharp 没有 ico 编码器):
| 资源 | 用在哪 | 长什么样 |
|---|---|---|
icon.* |
应用身份:exe / 安装包 / 快捷方式 / 窗口图标 | 奶白底色的精致几何 Pi 标志 |
升级数据迁移:Electron 的
userData路径来自productName。若用户从历史测试版本(Pi Agent、Pi Dsh或Pi&Dsh)升级过来,main.js的migrateLegacyUserData()在启动时会一次性自动搬运扩展状态(extensions-state.json)与主题偏好(theme-state.json),实现无感平滑升级。
- 解析运行时目录(
runtimeDir()):- 安装目录里的
resources/runtime-seed可写 → 就地运行(默认,秒开,无复制); - 只读(如装到
C:\Program Files)→ 回退:用 robocopy(长路径安全)把种子复制到%APPDATA%/pi-web-desktop/runtime,写.seeded标记(只复制一次)。
- 安装目录里的
- 运行时完整性预检(
runtime-guard.js,在启动服务之前):- 先用 swap 日志把上次中断的原子切换收敛掉(完成向前 or 回滚,绝不会留下"运行时目录不存在");
- 再校验运行时是否真的能用:结构文件(
nextCLI /.next/BUILD_ID/ react)、本平台原生模块能否require(在内置 node 的子进程里探测——主进程 require 既会因 ABI 不同而失配,也会锁住 DLL 导致后续切换失败)、以及node_modules里有没有 npm 的.<包名>-<随机>临时目录(安装被中断的指纹); - 判定为可修复(文件截断/缺失)→ 自动走下面第 6 步同一条原子安装路径重装当前锁定版本(不趁机升级);判定为环境问题(ABI 不符、缺系统 DLL)→ 直接报错,不做无意义的重装循环。
- 同步默认扩展与技能(启动时,非阻塞、失败不挡启动):
- 扩展:首次启动弹选择器让用户勾选装哪些,之后每次启动做非破坏性同步(不覆盖用户改过的文件),见下「内置的扩展与技能」;
ensureBundledSkills()把技能同步进~/.pi/agent/skills/(见下「内置的扩展与技能」)。
- 启动服务:用
resources/node/node.exe启动无头host-bridge.js,通过 FD 3/4 匿名字节管道与pi-app://协议流式直连,完全不监听任何 TCP 端口。 - 加载窗口:服务就绪后直接加载
pi-app://app/。 - 检查更新(菜单
App → 检查更新…,或启动后自动静默检查):用内置 npmview对比版本,有新版则原子安装:- 装进兄弟目录
.runtime-seed.staging(同卷,保证 rename 是原子移动),期间旧服务照常运行; - 用与第 2 步完全相同的校验做验收,不通过就丢弃 staging,线上运行时一字节不动;
- 通过后才停服务 →
rename换入(失败自动回滚)→ 重启服务并刷新窗口。 - 自愈与更新共用同一把锁,不会并发;刚自愈过 2 分钟内会跳过这次自动检查,避免让用户连等两次安装。
- 装进兄弟目录
- 退出:
taskkill /T(Windows)结束服务进程树,不留僵尸进程。
第 2、7 步的机制由
electron/runtime-guard.js实现,回归测试npm run test:guard。 背景:早先"就地npm install"被中断过两次,把正在使用的@next/swc-*.node写成了截断文件(PE 头合法、尾部缺失),Windows 拒绝加载 →next.config.ts加载失败 → 服务起不来,用户只看到无从下手的server not ready in time。
数据目录沿用 pi 的 ~/.pi/agent(会话、models.json、模型凭证),与终端 pi、全局 pi-web 共享。
| 装到哪 | 可写? | 首启 |
|---|---|---|
默认位置 %LOCALAPPDATA%\Programs\pi-web,或任意用户可写目录(如 D:\Apps\pi-web) |
是 | 就地运行,秒开 |
C:\Program Files\...(无管理员权限时只读) |
否 | 回退复制运行时种子到 AppData,首次约 1–2 分钟(仅第一次,之后秒开) |
安装时保持默认目录即可秒开。装到 Program Files 不是坏掉,只是首启被迫做一次复制。
ppt-master首次部署约上万文件(含图标库)到~/.pi/agent/skills/,约十几秒,仅第一次;之后靠.seed-version签名秒级跳过。
- 不需要 Node / npm(已全部内置)。
- 需要在 App 内配置一个模型提供商的 API Key(侧边栏 Models / 登录面板)才能真正对话;空机器首次没有任何凭证。
- 更新功能、首次模型调用、联网取数需要联网。
- 仅 Windows x64(内置运行时为 win-x64);未签名,SmartScreen 提示「未知发布者」点「仍要运行」。
# 1. 安装 Electron 壳依赖
npm install
# 2. 运行时种子(@agegr/pi-web 生产安装,含预构建 .next)
mkdir runtime-seed; cd runtime-seed; npm init -y
npm install @agegr/pi-web@latest --omit=dev --registry=https://registry.npmmirror.com
cd ..
# 3. 内置 Node 运行时(win-x64) —— 全自动
npm run seed:node之后日常只需
npm run seed把运行时种子升到最新发布版再打包。
npm start开发态直接就地从项目里的 runtime-seed 运行,秒开;关窗自动结束后台服务。
排障:主进程把关键步骤写到 %TEMP%/pi-web-desktop-debug.log(看 ensureBundledExtensions/Skills done、startOrRestartServer returned ok)。
可选环境变量:
PI_WEB_REGISTRY—— 自更新使用的 npm registry(默认https://registry.npmmirror.com)。PI_WEB_AUTO_UPDATE_CHECK=0—— 关闭启动后的自动检查更新。PI_CODING_AGENT_DIR—— 指定 pi 会话数据目录(默认~/.pi/agent)。
开发默认扩展:改 extensions-seed/*.ts 后 npm start。注意扩展同步现在是非破坏性的——只有 ~/.pi/agent/extensions/ 里那份仍与上次部署时一模一样(你没手改过)才会被刷新;否则你的版本被保留,只在「扩展管理」里标「有新版可用」。开发时更省事的做法:直接在 ~/.pi/agent/extensions/ 里改(不会再被启动覆盖了),改完再拷回 extensions-seed/ 入库;或者在扩展管理里点「恢复内置版本」强制拉取仓库版(会先备份你的改动)。
新增一个扩展:把 .ts 放进 extensions-seed/ 并在 extensions-seed/manifest.json 里登记(未登记的文件不会被部署,也不出现在选择器里);default: true 的新扩展会在用户升级后自动装上。新增/变更 npm 依赖则改 extensions-seed/package.json + 在 manifest 对应条目的 deps 里声明,然后跑 npm run seed:extensions。
开发默认技能:改 skills-seed/<skill>/,npm start 启动时按 .seed-version 签名同步进 ~/.pi/agent/skills/(文件 mtime 变即重新部署);Python 技能用 $PI_BUNDLED_PYTHON 调用脚本,新增重依赖请加进 scripts/vendor-python-requirements.txt 并 npm run seed:python 重供给。
确保图标产物已生成(node build/_make_icons.js),且 vendor/node(npm run seed:node)、runtime-seed 已就绪,然后:
npm run dist # 生成 dist/Pi Setup x.x.x.exe (NSIS)
npm run dist:dir # 仅生成解包目录(调试更快)国内首次打包会从 npmmirror 拉 electron / nsis 二进制(
.npmrc已配镜像)。 若遇 winCodeSign「无法创建符号链接」,是 Windows 软链权限问题——预先手动解压其缓存即可。
本仓库只开发 Electron 外壳层。pi-web 和 pi-coding-agent 一律以上游 npm 包形式获取,本仓库不包含、不修改它们的源码。
职责严格分离:
| 归属 | 职责 | 改动流向 |
|---|---|---|
| 上游 agegr/pi-web | pi-web 网页端本身的功能/页面/接口 | 需要改 pi-web 时给上游提 PR → 上游合并发版 → 本项目 runtime-seed / 自更新从 npm 拉到 |
| 本仓库 pi-web-desktop | Electron 外壳:窗口、内置运行时、自更新、IPC、自定义能力 | 在 electron/ 改 → 重新打包安装程序 |
两条铁律:
- pi-web 的任何修改不在本仓库做——通用改动给上游 agegr/pi-web 提 PR,合并发版后由
runtime-seed/ 自更新吃到。绝不在本仓库或runtime-seed里直接改 pi-web 源码 /.next——那会被下一次npm install @agegr/pi-web@latest冲掉。 - pi-web 和 pi-coding-agent 只从上游 npm 获取:
- pi-web = 上游
@agegr/pi-web; - pi-coding-agent = 上游
@earendil-works/pi-coding-agent(作为 pi-web 的依赖随之安装,不 fork、不改)。 - 本仓库不 vendoring、不内联它们的源码;
runtime-seed只是这两个 npm 包的一次生产安装。
- pi-web = 上游
历史注:2026-06~07 期间桌面端曾消费自有 fork
@cking000/pi-web(Metro 磁贴皮肤 + 若干修复,仓库 cking000bigdemon/pi-web)。fork 的两个功能性修复(扩展工具丢失、slash 命令面板)先后被上游 0.6.18 / 0.7.0 吸收后,2026-07-21 桌面端回归上游包,fork 退役(仅 DMIT 健康助手部署仍在用)。
你拥有、随便改 ─┐ electron/ · scripts/ ← 本仓库
│
pi-web 的功能 ─┤ 给上游 agegr/pi-web 提 PR → 上游发版 @agegr/pi-web
│
内置运行时 ─┤ resources/node
│
只读、不在此改 ─┘ resources/runtime-seed = @agegr/pi-web(npm 包) · ~/.pi 数据目录
- ✅ 本仓库允许:在
electron/下加能力(Node 全权限)、加 IPC、加 preload API、加 UI。 - ✅ pi-web 的改动:给上游提 PR,合并发版后这里通过升级 npm 包吃到。
- ❌ 禁止:在本仓库 /
runtime-seed里改 pi-web 源码或编译产物;fork、修改或内联@earendil-works/pi-coding-agent。 - 需要"后端能力"且不属于 pi-web 网页层时,放在 Electron main 里用 IPC 暴露(等价于你自己的后端)。
- 数据访问 —— 放
electron/features/<name>.js。取数优先直接读~/.pi(稳定),或用内置 nodespawn运行时里的piCLI 兜底。 - 暴露通道 ——
ipcMain.handle("<域>:<动作>", …)+preload.js里contextBridge.exposeInMainWorld("piDesktop", { … })。pi-web 本体不受影响。 - 展示界面 —— 三选一(按耦合度):菜单 + 独立窗口(推荐,零耦合)/ preload 注入悬浮入口(体验一体,依赖注入点)/ 托盘 · 全局快捷键(轻量触发)。
- 你的
electron/全在外壳层,自更新只换runtime-seed,碰不到。 - pi-web 的修复/功能走上游 PR;上游发版后
npm run seed(打包)或应用内「检查更新」(已装机器)即可跟进。
- 安装包体积:精简 Python 后的安装包仅 约 80MB(大幅瘦身近 80%);换来空电脑「装完即用、零依赖」。
- 只读目录安装首启较慢(复制运行时种子,仅第一次);可写目录安装则秒开。
- 自更新粒度是 pi-web 这一层;Electron 外壳(含内置运行时)更新仍需重新发安装包。
- 定制受限:不再持有 fork,pi-web 层的改动需上游接受 PR 才能获得(换来零同步维护成本;历史 Metro 定制版存于
cking000bigdemon/pi-web,已退役)。