Skip to main content
扣瓦是一个四层 monorepo。理解这四层怎么分,比读任何单个文件都有用。

分层

apps/desktop

桌面前端,Next.js + assistant-ui。 主要目录: Rust 宿主负责 Rust 侧的事:窗口与自定义标题栏、系统钥匙串、 sidecar 子进程的拉起与回收,以及把业务表的读写以 host RPC 的形式 提供给 sidecar。
业务数据不直接由 sidecar 写文件或开 SQLite 连接,而是通过 stdout 上的 host RPC 交给宿主执行。这是为什么在没有 Rust 宿主时 (例如 bun run smoke)sidecar 会回退到本地 SQLite 存储。

apps/sidecar/pi-agent

Agent 运行时,Bun + TypeScript。入口 src/index.ts, 对外是 stdin/stdout 上的 NDJSON 协议,按域分目录:

apps/mobile

Expo SDK 57 + React Native 0.86。无后端—— 经桌面远程网关的 /ws 连 sidecar,配对码换 token 存 Keychain/Keystore。 详见 远程与移动端。

packages

  • pi-protocol:前后端共享的跨端契约定义。桌面前端、sidecar 与移动端 都从这里取类型,避免三端各写一套。
  • shared:桌面侧的共享工具与组件。

plugins

见 插件与工作台。

数据流向:一次工具调用

1

前端发出请求

会话流渲染时,前端通过 NDJSON 向 sidecar 发一条消息。
2

sidecar 决定调什么

agent 循环把消息交给模型,模型返回工具调用。 sidecar 按当前会话模式与权限档位检查这次调用是否放行。
3

可能要问你

如果需要审批,sidecar 推一条审批事件给前端, 前端弹审批卡。你的选择回传 sidecar。
4

执行并落库

工具在 sidecar 内执行。涉及业务表的写入通过 stdout 上的 host RPC 交给 Rust 宿主执行,宿主访问系统钥匙串与 SQLite。
5

回推事件

工具结果与流式增量以事件形式推回前端, 渲染成对话流里的一次工具调用记录,并落检查点。

为什么把 agent 拆成独立进程

  • 界面不卡:长任务和大量工具输出不会阻塞渲染进程;
  • 崩溃隔离:sidecar 出问题不会带走整个窗口;
  • 多端复用:移动端和网页端接的是同一个 WS 端点与同一套运行时, 不需要第二份实现;
  • 可独立测试:bun run smoke 能在没有 GUI 的环境里跑完整的协议握手。

站在别人的肩膀上

会话转录、上下文压缩、子代理等核心设计移植/参考自 Earendil Works 的 pi-agent。 代码注释里大量出现的 PI-Desktop 同设计 标注就指向这些出处。

下一步

在这个分层之上加你自己的东西。