添加中文文档

This commit is contained in:
2026-05-12 13:14:17 +00:00
parent 61d7ca6bba
commit 38bb076ac6
24 changed files with 6876 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
# 构建与运行时
NanoClaw 运行的是分拆式技术栈:宿主机使用 Node + pnpmagent 容器使用 Bun。它们之间仅通过每个 session会话的两个 SQLite 文件来通信——它们之间没有共享模块,这也是它们能干净地使用不同运行时的原因。
## 为什么分拆
- **宿主机保持使用 Node**,因为 BaileysWhatsApp依赖 `libsignal-node` 原生绑定和久经考验的 WebSocket/HTTP 栈。Bun 的 Node-API 兼容性已经有所改善,但这不是我们想冒险的地方。
- **容器使用 Bun**,因为 `bun:sqlite` 是内置的(无需每次镜像重建都编译 `better-sqlite3` 原生模块),源码直接运行(无需在镜像构建或 session 唤醒时做 tsc 构建步骤),而且 `bun install``npm install` 快大约 5-10 倍。
宿主机和容器各有自己的包树:
```
/ pnpm + Node 22
pnpm-lock.yaml 宿主机依赖频道、Chat SDK、Baileys、better-sqlite3 等)
pnpm-workspace.yaml minimumReleaseAge + onlyBuiltDependencies 策略
/container/agent-runner/ Bun 1.3+
bun.lock agent-runner 运行时依赖Claude Agent SDK、MCP SDK、zod 等)
package.json @types/bun、typescript 开发依赖用于类型检查
```
容器镜像内部也有 pnpm + Node用于全局 CLI`@anthropic-ai/claude-code``agent-browser``vercel`)。这些是 agent 在运行时调用的 Node 二进制文件,不是库依赖。将它们保持在 pnpm 上可以保留 CLI 版本的供应链策略。
## 锁文件
| 树 | 锁文件 | 管理器 | 依赖变更后重新生成 |
|------|----------|---------|----------------------------|
| 宿主机 | `pnpm-lock.yaml` | pnpm 10 | `pnpm install` |
| Agent-runner | `container/agent-runner/bun.lock` | Bun 1.3+ | `cd container/agent-runner && bun install` |
两者都已提交。CI 和 Dockerfile 运行 `--frozen-lockfile` 变体——`package.json` 与锁文件之间的任何偏差都会导致构建失败。
## 供应链
- **宿主机 + 全局 CLI**pnpm`minimumReleaseAge: 4320`(新版本需要等待 3 天),`onlyBuiltDependencies` 白名单用于 postinstall 脚本。见 `pnpm-workspace.yaml``docs/SECURITY.md`
- **Agent-runner**Bun无发布年龄策略——Bun 目前没有等效机制。防御措施是 `bun.lock` 锁定版本加上通过 Dockerfile ARG 固定版本的 CLI/Bun 本身。当升级 `@anthropic-ai/claude-agent-sdk` 或任何运行时依赖时,请查看 npm 上的发布日期并有意识地升级,而不是通过 `bun update`
## 镜像构建范围
`container/Dockerfile` 是基于 `node:22-slim` 的单阶段构建:
- **固定的 ARG**——`BUN_VERSION``CLAUDE_CODE_VERSION``AGENT_BROWSER_VERSION``VERCEL_VERSION`。在 PR 中有意识地升级。
- **CJK 字体**——`ARG INSTALL_CJK_FONTS=false``container/build.sh``.env` 读取 `INSTALL_CJK_FONTS` 并传递。默认构建节省约 200MB当用户处理中文/日文/韩文内容时选择加入。
- **BuildKit 缓存挂载**——`/var/cache/apt``/var/lib/apt``/root/.bun/install/cache``/root/.cache/pnpm`。当 `package.json`/`bun.lock` 未变更时的重建速度很快。需要 BuildKitDocker 23+、Apple Container 兼容下默认启用)。
- **`tini` 作为 init**——回收 Chromium 僵尸进程,转发信号以便在 SIGTERM 时完成正在进行的 `outbound.db` 写入。
- **`entrypoint.sh`**(已提取)——`exec bun run /app/src/index.ts` 在 tini 下运行。可读且可 diff。
- **无编译后的 `/app/dist`**——Bun 直接运行 TS。宿主机在 session 启动时还会将最新源码挂载到 `/app/src` 上,因此宿主机的编辑无需重建镜像即可生效。
## Session 唤醒(两条路径)
1. **基础镜像 ENTRYPOINT**——用于 stdin 管道测试调用,如 `container/build.sh` 中的示例:`tini --> entrypoint.sh` 捕获 stdin 到 `/tmp/input.json`,然后 `exec bun run src/index.ts`
2. **宿主机生成的 session**——`src/container-runner.ts` 第 301 行附近使用 `--entrypoint bash``-c 'exec bun run /app/src/index.ts'`。绕过 tiniDocker 默认的 PID 1 处理生效。stdin 未使用;所有 IO 通过挂载的 session DB 流动。
两条路径最终都由 Bun 运行同一个源码文件 `/app/src/index.ts`
## CI 流程
`.github/workflows/ci.yml` 安装 Node带 pnpm 缓存)和 Bun然后按顺序运行
1. `pnpm install --frozen-lockfile`(宿主机)
2. `bun install --frozen-lockfile``container/agent-runner/` 中(容器)
3. `pnpm run format:check`
4. `pnpm exec tsc --noEmit`(宿主机类型检查)
5. `pnpm exec tsc -p container/agent-runner/tsconfig.json --noEmit`(容器类型检查)
6. `pnpm exec vitest run`(宿主机测试)
7. `bun test``container/agent-runner/` 中(容器测试)
任何失败都会导致 PR 失败。
## 关键不变条件
- **Session DB 必须使用 `journal_mode=DELETE`。** WAL 的 `-shm` 内存映射不会跨宿主机和客户机之间的 VirtioFS 传递。参见 `container/agent-runner/src/db/connection.ts` 顶部的文档注释和 `src/session-manager.ts`
- **容器中的命名 SQL 参数要求在 JS 对象键中使用前缀。** `bun:sqlite` 不会像宿主机上的 `better-sqlite3` 那样自动去除 `@`/`$`/`:`。在 SQL 和键中都使用 `$name``.run({ $id: msg.id })`。位置 `?` 参数正常工作。
- **Agent-runner 测试在 `bun:test` 下运行,而非 vitest。** `vitest.config.ts` 排除了 `container/agent-runner/` 树,因为 vitest 在 Node 上运行,无法加载 `bun:sqlite`
- **容器镜像中没有 tsc 构建步骤。** 重新添加一个会重新引入我们已移除的每个 session 唤醒约 200-500ms 的成本。
- **全局容器 CLI 保持使用 pnpm而非 Bun。** `agent-browser``@anthropic-ai/claude-code``vercel` 以及 agent 调用的任何未来 Node CLI 都应在 Dockerfile 的 pnpm 全局安装块下固定版本。`bun install -g` 会绕过 pnpm 供应链策略。
## 迁移历史
这一结构替换了统一的 npm-on-Node 技术栈(宿主机和容器均为 Node。pnpm 迁移首先落地PR #1771),使宿主机纳入供应链策略,然后容器迁移到 Bun 以消除原生模块编译和每次唤醒的 tsc 步骤。选择分拆而非完全转用 Bun是因为 Baileys 的原生依赖是宿主机上的主要风险面——容器没有这样的依赖,因此可以在不承担风险的情况下受益于 Bun。